5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AndroidでバックグラウンドでUDP受信を待ち受ける

Posted at

Androidスマホにおいて、常にUDPパケット受信を待ち受けるバックグラウンドサービスを作成します。
UDPパケットをAndroidのフォアグラウンドサービスとして常に待機状態とし、受信したUDPパケットの内容に応じて、処理をして、また待ち受けます。
できる処理は多くはないですが、今回は、トースト表示とNotification通知をします。

ソースコードもろもろをGitHubに上げておきました。

poruruba/UdpBackgroundService

フォアグラウンドサービスの立ち上げ

フォアグラウンドサービスの立ち上げには、MainActivityから以下を実行します。

Android\BackgroundUdpServer\app\src\main\java\jp\or\myhome\sample\backgroundudpserver\MainActivity.java
                Intent intent;
                intent = new Intent(this, UdpBackgroundService.class);
                intent.putExtra("udpPort", 1234);
                startForegroundService(intent);

待ち受けるポート番号を渡してあげるようにしました。

詳細は以下をご参照ください。
 M5StickCのボタン押下でスマホから現在地をしゃべらせる(1/2)

停止は以下です。

Android\BackgroundUdpServer\app\src\main\java\jp\or\myhome\sample\backgroundudpserver\MainActivity.java
                Intent intent = new Intent(this, UdpBackgroundService.class);
                stopService(intent);

フォアグラウンドサービスの実装

Serviceを派生させたクラスとして実装します。以下のメソッドが大事です。

・onCreate
 このクラスが生成されたときに呼ばれます。最初の共通処理として使います。
・onStartCommand
 フォアグラウンドサービスとして開始するときに呼び出されます。
・onDestroy
 フォアグラウンドサービスの停止を要求されたときに呼び出されます。

onCreate

Notificationのチャネルを設定します。フォアグラウンドサービスとして立ち上げるときには必須です。2つのチャネルを設定しますが、1つ目が必須の方です。後者は必須ではないですが、UDPパケットを受信したときにする処理として使います。

Android\BackgroundUdpServer\app\src\main\java\jp\or\myhome\sample\backgroundudpserver\UdpBackgroundService.java
        Context context = getApplicationContext();
        notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);

        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, NOTIFICATION_TITLE , NotificationManager.IMPORTANCE_DEFAULT);
        // 通知音を消さないと毎回通知音が出てしまう
        // この辺りの設定はcleanにしてから変更
        channel.setSound(null,null);
        channel.enableVibration(false);
        notificationManager.createNotificationChannel(channel);

        NotificationChannel channel2 = new NotificationChannel(CHANNEL_ID2, NOTIFICATION_MESSAGE , NotificationManager.IMPORTANCE_DEFAULT);
        notificationManager.createNotificationChannel(channel2);

onStartCommand

フォアグラウンドサービスとして開始するときに呼び出されます。ここが一番大事です。
やっていることは、UDPの準備をしてからノーティフィケーションをしています。

Android\BackgroundUdpServer\app\src\main\java\jp\or\myhome\sample\backgroundudpserver\UdpBackgroundService.java
        int udpPort = intent.getIntExtra("udpPort", MainActivity.DEFAULT_UDP_PORT);
        Log.d(TAG, "udpPort: " + udpPort);
        try{
            if( udpReceive != null ) {
                udpReceive.close();
                udpReceive = null;
            }
            udpReceive = new DatagramSocket(udpPort);
        }catch(Exception ex){
            Log.d(TAG, ex.getMessage());
        }

        force_stop = false;
        Thread thread = new Thread(new UdpReceiveThread());
        thread.start();

        Intent notifyIntent = new Intent(this, MainActivity.class);
//        notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);

        Notification notification = new Notification.Builder(getApplicationContext(), CHANNEL_ID)
                .setContentTitle(NOTIFICATION_TITLE)
                .setSmallIcon(android.R.drawable.btn_star)
                .setContentText(NOTIFICATION_CONTENT)
                .setAutoCancel(true)
                .setContentIntent(pendingIntent)
                .setWhen(System.currentTimeMillis())
                .build();
        startForeground(NOTIFICATION_ID, notification);

        return START_NOT_STICKY;

UDP受信処理

UDP受信処理について、補足します。
UDP受信はネットワーク処理なので、メインスレッドとは別のスレッドで実行する必要があります。そのため、Runnableを派生させたスレッドクラスを作成しました。

Android\BackgroundUdpServer\app\src\main\java\jp\or\myhome\sample\backgroundudpserver\UdpBackgroundService.java
    private class UdpReceiveThread implements Runnable{
        @Override
        public void run() {
            Log.d(TAG, "UdpReceiveThread run");

            try {
                byte[] buff = new byte[UDP_BUFFER_SIZE];
                while(!force_stop) {
                    Arrays.fill(buff, (byte)0);
                    DatagramPacket packet = new DatagramPacket(buff, buff.length);
                    udpReceive.setSoTimeout(0);
                    udpReceive.receive(packet);
                    Log.d(TAG, "received from " + packet.getAddress());

                    try {
                        JSONObject json = new JSONObject(new String(buff));
                        String type = "";
                        try{
                            type = json.getString("type");
                        }catch(Exception ex){
                            Log.d(TAG, ex.getMessage());
                        }
                        switch(type){
                            case "toast":{
                                Handler mainHandler = new Handler(getMainLooper());
                                mainHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        try {
                                            String message = json.getString("message");
                                            Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
                                        } catch (Exception e) {
                                            e.printStackTrace();
                                        }
                                    }
                                });
                                break;
                            }
                            case "notification":{
                                String title = json.getString("title");
                                String message = json.getString("message");

                                NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID2);
                                builder.setContentTitle(title);
                                builder.setContentText(message);
                                builder.setSmallIcon(android.R.drawable.ic_popup_reminder);
                                builder.setAutoCancel(true);
                                notificationManager.notify(0, builder.build());
                                break;
                            }
                        }
                    }catch(Exception ex2){
                        Log.d(TAG, ex2.getMessage());
                    }
                }
            } catch (Exception ex) {
                Log.d(TAG, ex.getMessage());
            } finally {
                Log.d(TAG, "UdpReceiveThread end");
            }
        }
    }

UDPパケットの受信ポート番号を指定してインスタンス化した後、ひたすら待ち受けて、受信したら処理をして、また待ち受ける、を繰り返しています。
UDPパケットは、JSON形式であることを想定しています。今回は以下の2種類です。

・トースト表示
type: "toast",
message: ”任意のメッセージ"

・ノーティフィケーション
type: "notification",
title: "任意のタイトル",
message: "任意のメッセージ"

onDestroy

フォアグラウンドサービスを停止します。force_stopをtrueにして受信ループを抜け出させます。

Android\BackgroundUdpServer\app\src\main\java\jp\or\myhome\sample\backgroundudpserver\UdpBackgroundService.java
        force_stop = true;
        if( udpReceive != null ) {
            udpReceive.close();
            udpReceive = null;
        }

AndroidManifest.xml

以下のサービスの設定をapplicationのところに追記します。

Android\BackgroundUdpServer\app\src\main\AndroidManifest.xml
        <service
            android:name=".UdpBackgroundService"
            android:parentActivityName=".MainActivity"
            android:enabled="true"
            android:exported="true"></service>

また、必要なパーミッションがありますので、それも追記しておきます。それぞれ、フォアグラウンドサービスのためと、WiFiのIPアドレスの取得のための、UDPパケット受信のためです。

Android\BackgroundUdpServer\app\src\main\AndroidManifest.xml
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

UDP送信のためのWebページ

参考までに、UDP送信のためのWebページも用意しておきました。

image.png

nodejs\public\udpsend_console\index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com 'unsafe-eval' 'unsafe-inline'; style-src * 'unsafe-inline'; media-src *; img-src * data: content: blob:;">
  <meta name="format-detection" content="telephone=no">
  <meta name="msapplication-tap-highlight" content="no">
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">

  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  <script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
  <!-- Optional theme -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
  <!-- Latest compiled and minified JavaScript -->
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>

  <link rel="stylesheet" href="css/start.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/spinkit/2.0.1/spinkit.min.css" />
  <script src="js/methods_bootstrap.js"></script>
  <script src="js/components_bootstrap.js"></script>
  <script src="js/components_utils.js"></script>
  <script src="js/vue_utils.js"></script>
  <script src="js/gql_utils.js"></script>

  <script src="js/remoteconsole.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vconsole/dist/vconsole.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vuex@3.x/dist/vuex.min.js"></script>

  <script src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script>

  <title>UDP送信コンソール</title>
</head>
<body>
<!--
    <div id="loader-background">
      <div class="sk-plane sk-center"></div>
    </div>
-->
    <div id="top" class="container">
      <div class="jumbotron">
        <h2>UDP送信コンソール</h2>
      </div>

      <div class="form-inline">
        <label>IPアドレス</label> <input type="text" class="form-control" v-model="ipaddress">
        <label>ポート番号</label> <input type="number" class="form-control" v-model.number="port">
      </div>

      <br>

      <div class="panel panel-primary">
        <div class="panel-heading"><h2 class="panel-title">toast</h2></div>
        <div class="panel-body">
          <label>message</label> <input type="text" class="form-control" v-model="param_toast.message">
        </div>
        <div class="panel-footer text-right">
          <button class="btn btn-default" v-on:click="send_toast">送信</button>
        </div>
      </div>

      <div class="panel panel-primary">
        <div class="panel-heading"><h2 class="panel-title">notification</h2></div>
        <div class="panel-body">
          <label>title</label> <input type="text" class="form-control" v-model="param_notification.title">
          <label>message</label> <input type="text" class="form-control" v-model="param_notification.message">
        </div>
        <div class="panel-footer text-right">
          <button class="btn btn-default" v-on:click="send_notification">送信</button>
        </div>
      </div>

      <div class="panel panel-primary">
        <div class="panel-heading"><h2 class="panel-title">free</h2></div>
        <div class="panel-body">
          <textarea class="form-control" rows="5" v-model="param_free"></textarea>
        </div>
        <div class="panel-footer text-right">
          <button class="btn btn-default" v-on:click="send_free">送信</button>
        </div>
      </div>


      <!-- for progress-dialog -->
        <progress-dialog v-bind:title="progress_title"></progress-dialog>
    </div>

    <script src="js/store.js"></script>
    <script src="js/start.js"></script>
</body>

以上

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?