質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

ループ

ループとは、プログラミングにおいて、条件に合致している間、複数回繰り返し実行される箇所や、その制御構造を指します

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

Q&A

解決済

1回答

2479閲覧

Android-AsyncTaskのキャンセルの仕方が分かりません

Jhon_McClane

総合スコア48

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

ループ

ループとは、プログラミングにおいて、条件に合致している間、複数回繰り返し実行される箇所や、その制御構造を指します

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

0グッド

0クリップ

投稿2021/08/20 17:02

編集2021/08/22 12:44

UDPのパケットを受信するため,AsyncTaskでループ処理を実行しています.
メインアクティビティが閉じられたときに,onPause()メソッド内から非同期タスク終了するように要求したいのですが,タスクが終了しません.
呼び出しの仕方が誤っているのでしょうか.

試したこと

参考記事の最後の回答が,自分と同じ状況だと思い,見よう見まねで書いてみたのですが,上手くいきませんでした.
また,参考にした回答で,receiveTask.requestTermination();の処理が実装できなかったのでここに問題があるのでしょうか?
さらに,
cancelの「mayInterruptIfRunning boolean:」真偽値の意味がいまいち分かりません.
公式より→ 「このタスクを実行しているスレッドを中断する必要がある場合はtrue。それ以外の場合は、進行中のタスクを完了できます。」
このタスクを実行しているスレッドとは,非同期処理のスレッドでしょうか.

protected void onCreate(Bundle savedInstanceState) { receiveTask = new ReceiveTask(this); receiveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } // リスナー解放 レシーバを中断する処理は,onClick() protected void onPause() { super.onPause(); getMembers = false; Log.d("onPause()", "位置情報と方角取得リスナをリリース"); mSensorManager.unregisterListener(this); //stop location updates when Activity is no longer active if (fusedLocationClient != null) { fusedLocationClient.removeLocationUpdates(mLocationCallback); } } @Override public void onClick(View v) { if (R.id.exitGroup == v.getId()) { receiveTask.cancel(false); /***********追記**************************************************/ UtilCommon utilCommon = (UtilCommon) UtilCommon.getAppContext(); DatagramSocket socket = utilCommon.getDatagramSocket(); /*****************************************************************/ new Thread(()->{ try(DatagramSocket s = new DatagramSocket();) { // 宛先ポートが「s.getLocalPort()」としていた DatagramPacket p = new DatagramPacket(new byte[]{0}, 1, InetAddress.getLocalHost(), socket.getLocalPort()); s.send(p); Log.d("onClick()", "ダミーパケット送信"); } catch(IOException e) { e.printStackTrace(); } }).start(); exitAndCloseProcess(); finish(); } else if (R.id.send == v.getId()) {        //... } else if (R.id.mute == v.getId()) { //... } }
public class ReceiveTask extends AsyncTask<Void, Void, Void> { private boolean running = true;   // ... @Override protected Void doInBackground(Void... strings) { Log.d("ReceiveTask", "レシーバの処理を開始"); DatagramPacket receivePacket = new DatagramPacket(new byte[2048], 2048); UtilCommon utilCommon = (UtilCommon) UtilCommon.getAppContext(); DatagramSocket socket = utilCommon.getDatagramSocket(); while(running) { if (isCancelled()){ Log.d("ReceiveTask", "isCancelled:レシーバの処理を中断します"); break; } try { socket.receive(receivePacket); // } catch (IOException e) { Log.d("ReceiveTask", "受信エラー:" + e); } String receivedResult = new String(receivePacket.getData(), 0, receivePacket.getLength()); try { // ↓のコードで Warning JSONObject jsonObject = new JSONObject(receivedResult); //受け取ったデータを基に処理          //... } catch (JSONException e) { e.printStackTrace(); Log.d("Receiver(シグナリングとPeer)", "エラー:" + e); } } return null; } @Override protected void onCancelled() { Log.d("ReceiveTask", "onCancelled:レシーバの処理を中断します"); running = false; } }

発生している警告

ReceiveTask.java:70がJSONObject jsonObject = new JSONObject(receivedResult); です.
この警告は,無視して問題ないでしょうか。

D/onClick(): ダミーパケット送信 W/System.err: org.json.JSONException: Value �� of type java.lang.String cannot be converted to JSONObject W/System.err: at org.json.JSON.typeMismatch(JSON.java:111) at org.json.JSONObject.<init>(JSONObject.java:163) at org.json.JSONObject.<init>(JSONObject.java:176) at com.example.g_lockonbyaw.signaling.receiver.ReceiveTask.doInBackground(ReceiveTask.java:70) at com.example.g_lockonbyaw.signaling.receiver.ReceiveTask.doInBackground(ReceiveTask.java:27) at android.os.AsyncTask$2.call(AsyncTask.java:333) at java.util.concurrent.FutureTask.run(FutureTask.java:266) W/System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) at java.lang.Thread.run(Thread.java:764) D/Receiver(シグナリングとPeer): エラー:org.json.JSONException: Value �� of type java.lang.String cannot be converted to JSONObject D/ReceiveTask: isCancelled:レシーバの処理を中断します D/ReceiveTask: onCancelled:レシーバの処理を中断します

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

いくらフラグを操作した所で、socket.receive() が戻ってこなければ何にもなりません。

cancel(true) とすると, AsyncTask 上で Thread.interrupted() が呼ばれたと思います。

Thread.interrupted() で socket.receive() に割り込みが掛からないのであれば、 socket を close() してしまえば良いのではないでしょうか。(例外が発生するはずです。)


「停止用のダミーパケットを送り着ける」方法で停止させてみました。
レイアウトに Button(id=stopButton) があるとしまして、実行後ログに"レシーバの処理を開始" が出たのを確認してからボタンを押すと、"レシーバの処理を中断" が出ると思います。
(ダミーパケットの内容はテキトウです。)

MainActivity.java

java

1import androidx.appcompat.app.AppCompatActivity; 2 3import android.content.Context; 4import android.os.*; 5import android.util.Log; 6import android.widget.Button; 7 8import java.io.IOException; 9import java.net.*; 10 11public class MainActivity extends AppCompatActivity { 12 13 @Override 14 protected void onCreate(Bundle savedInstanceState) { 15 super.onCreate(savedInstanceState); 16 setContentView(R.layout.activity_main); 17 18 ReceiveTask receiveTask = new ReceiveTask(this); 19 receiveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 20 21 Button button = findViewById(R.id.stopButton); 22 button.setOnClickListener(view -> { 23 receiveTask.cancel(false); 24 new Thread(()->{ 25 try(DatagramSocket s = new DatagramSocket();) { 26 DatagramPacket p = new DatagramPacket(new byte[]{0}, 1, InetAddress.getLocalHost(), socket.getLocalPort()); 27 s.send(p); 28 } catch(IOException e) { 29 e.printStackTrace(); 30 } 31 }).start(); 32 }); 33 } 34 35 private DatagramSocket socket; 36 37 class ReceiveTask extends AsyncTask<Void, Void, Void> { 38 private boolean running = true; 39 40 ReceiveTask(Context context) { 41 try { 42 socket = new DatagramSocket(33333); 43 } catch(SocketException e) { 44 e.printStackTrace(); 45 } 46 } 47 48 @Override 49 protected Void doInBackground(Void... strings) { 50 DatagramPacket receivePacket = new DatagramPacket(new byte[2048], 2048); 51 Log.d("ReceiveTask", "レシーバの処理を開始"); 52 while(running) { 53 if (isCancelled()){ 54 Log.d("ReceiveTask", "isCancelled:レシーバの処理を中断します"); 55 break; 56 } 57 try { 58 socket.receive(receivePacket); 59 } catch (IOException e) { 60 Log.d("ReceiveTask", "受信エラー:" + e); 61 } 62 } 63 Log.d("ReceiveTask", "レシーバの処理を中断"); 64 return null; 65 } 66 67 @Override 68 protected void onCancelled() { 69 Log.d("ReceiveTask", "onCancelled:レシーバの処理を中断します"); 70 running = false; 71 } 72 } 73}

投稿2021/08/20 17:17

編集2021/08/21 19:17
jimbe

総合スコア13209

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Jhon_McClane

2021/08/21 13:49

cancel(true) の実行は確認できましたが,Thread.interrupted() がどこで呼ばれたのか,logを見る限りでは分からなかったです.(e.printStackTrace();を追記して) 出来れば,close()せずに,Thread.interrupted() で socket.receive() に割り込みを入れたいのですが, (設計が上手く出来ていなく,別のエラーが生じてしまうため) 何処に何を記述すれば良いのかいまいちピンと来ていないとこです。。。
jimbe

2021/08/21 13:59

> Thread.interrupted() で socket.receive() に割り込み それは java のライブラリ(もしくはもっと下位)の仕様ですので、割り込みが効かないなら諦めるしかありません。 ソケットにタイムアウトを設定してポーリングする感じにすれば close しないことになりますが… intterupt は結局意味無しですけど。 別のエラーの発生する設計とは、どのようなものでしょうか。
Jhon_McClane

2021/08/21 14:52

それは java のライブラリ(もしくはもっと下位)の仕様ですので、割り込みが効かないなら諦めるしかありません。 >>承知しました.とりあえず,完璧ではないですが,ある程度動くところまで行っているので諦めます. 別のエラーの発生する設計とは、どのようなものでしょうか。 >>socketをclose()するタイミングがよく分からなく,一度生成したsocketオブジェクトをいろんなクラスで併用した設計になっています。(Applicationクラスに記憶させて無理矢理やってます.) そのため,まずは,定期的にpingを送信するクラスでsocketが閉じていると怒られました。 そもそも,socketのスコープ,寿命がどれくらいが適しているのかが分からなく,「何度もオブジェクトを作るよりは1つでまとめちゃえ」といった発想で設計したので,close()は何処にも書いてません.
jimbe

2021/08/21 15:49

> 一度生成したsocketオブジェクトをいろんなクラスで併用した設計 ping 送信でしたら、ご提示のコードは受信ですから関係無いのではないでしょうか。 それにメインアクティビティが閉じるということはアプリが終わる方向で、それなら ping で生存確認するのも終わるのではと思うのですが…。 アプリのあちこちでばらばらに Socket を用いて処理を行っているのであれば、それらを新規に通信監理クラスに纏めて Socket を管理させたほうが良さそうです。
Jhon_McClane

2021/08/22 16:31 編集

>ping 送信でしたら、ご提示のコードは受信ですから関係無いのではないでしょうか。 正しい設計方法を知らないのですが,今は,1つのsocketで送受信しているため,受信socketをclose()することで,送信socketにも影響が出てエラーが出たのだと思います. >新規に通信監理クラスに纏めて Socket を管理 設計がぐちゃぐちゃしているので,そのように変更できたらしたいと思います. >本質からそれてしまうのですが,通信するためにsocketを利用する場合は, 送信用のsocket,受信用のsocketの最低2つは作成した方がよろしいのでしょうか。あと送信socketのclose()するタイミングが分かりません.イベントによって通信する場合があるので開けっぱなしでやっています.
jimbe

2021/08/21 17:08

つまりメインアクティビティが pause になってもバックグラウンドが生きていて、利用している通信機能も生きていないといけないのですね。 であれば今度は何故 ReceiveTask を AsyncTask にされたのかが疑問になります。 AsyncTask は「結果を画面に表示する必要のある処理の為の一時的な別スレッド」用で、アクティビティのライフサイクルに合わない処理に使うには無意味な機能(onProgressUpdate/onPostExecute等)が載っています。 長時間の(そしてアクティビティとは別のサイクルを持つ)裏作業が必要であれば、Service の実装を検討されては如何でしょうか。
jimbe

2021/08/21 17:20 編集

UDP で相互通信している場合はもちろんソケットを閉じてはいけないですが、相互通信するなら TCP のほうが良いのではないでしょうか。 UDP 通り「送りっぱなし」の送信であれば、送信直後に close しても大丈夫です。(送信に使った Socket を取っておくのは、送信用ポートをリザーブしておく程度の意味しかないように思います。)
jimbe

2021/08/21 17:32

ネットで探していて一つ方法を思い出しました。 「receive しているポートに停止用のダミーパケットを送り付ける」という方法があります。
Jhon_McClane

2021/08/22 09:06

logでダミーパケットの送信は確認できたのですが,「isCancelled:レシーバの処理を中断します」が呼ばれないです... 現状呼ばれない原因が何処にあるのか,見当がつかないため,ソースコードが載せられなく申し訳ないです.レシーバ処理の開始後に,「退出」ボタンを押すことで,レシーバを中断する処理をそのまま書いたのですがね。。。んーんなんでだ?
jimbe

2021/08/22 09:17

cancel(false) を呼んでからダミーパケットを送信しているのですよね?
Jhon_McClane

2021/08/22 12:50

原因が分かりました.onClick内の宛先ポートを「s.getLocalPort()」としていたことでした. 無事解決したのですが,レシーバを中断すると,JSONObject jsonObject = new JSONObject(receivedResult); に警告が出ました.これは無視してかまわないでしょうか.
jimbe

2021/08/22 12:55

ポート等テキトウでした、すいません。 receive した結果を JSON で解析しているのでしょうか。 jsonObject を作る前に receiveResult の大きさをチェックしてダミーだったら何もしないようにする等するのは如何でしょう。
Jhon_McClane

2021/08/22 15:51

なるほど,ありがとうございます.無事解決することが出来ました!^^ 今回の原因をまとめると, 受信を扱ったループ処理などの場合,receiveTask.cancel(false);を呼んだ時点で socket.receive();が呼ばれているため,isCancelled()に行かないと言うことですね。 そこで,ダミーパケットを送信することで,while文が次の周回に入り,isCancellsed()がチェックされる.つまりisCancelled()は,socket.receive();よりも前に記述する必要がある.
jimbe

2021/08/22 16:55 編集

AsyncTask.cancel は( isCancelled が true を返すような)内部の"停止フラグ"を true にし、さらにパラメータに true を指定した場合は Thread.interrupted() を呼び出しますので、もしAsyncTask が interrupted で割り込みが出来るような(Thread.sleep 等で)停止をしていた場合にはそれが終了して戻ってくる為 isCancelled を呼び出したりのキャンセル動作が出来ますが、Socket.receive は interrupted では割り込まれません(戻ってこない)。つまり AsyncTask.cancel の呼び出しだけでは(パラメータの true/false に関係無く) 止まらないというわけです。 何か受信するまで戻ってこない…なら「じゃあ受信させよう」というわけでダミーパケットを送り付けて、(強制的に)戻ってこさせるのですね。 一度戻って来さえすれば、isCancelled を呼び出したりのキャンセル動作は、ループのどこでも良いです。何なら Socket.receive が戻ったら即 isCancelled をチェックして true ならループを break; し、false なら JSON で receive が受信したデータを処理する等の正常処理をしてループに戻る(=再び Socket.receive をする)…でもいけると思います。
Jhon_McClane

2021/08/22 16:19 編集

Thread.interrupted() の効果についてようやく理解できました. まだ,疑問が残るのですが, onCancelled()内で記述したrunning = falseの必要性がピンときてないです. isCancelled()内で,break;しているため,loopを抜けられるのではと思ったのですが. https://stackoverflow.com/questions/6039158/android-cancel-async-task 上記サイトに 「cancel()を呼び出し,doInBackground()がまだ実行を開始していない場合,onCancelled()が呼び出される。」と記載されていました. この時点で,loop抜けているのでは?と思い,running=falseを消してみると,止まっていました. while文の条件式に意味はあるのでしょうか
jimbe

2021/08/22 16:48

AsyncTask の動作に関しましては実際コードレベルで確実な知識は持っていないので動作「イメージ」となってしまいますが。 まず onCancelled は、 doInBackgraund が終わった後に onPostExecute が呼ばれるか onCancelled が呼ばれるか…というレベルのメソッドです。 どちらが呼ばれるかの判断はその AsyncTask オブジェクトに対し cancel() が呼ばれているかどうかで、呼ばれていれば onCancelled、呼ばれていなければ onPostExecute です。 ご提示のリンクの記述が正しければ、実際には doInBackgraund が呼ばれていない状態、つまりスレッドが実際の動作を開始していない段階で cancel を呼び出しても、onCancelled が呼ばれる…ということでしょう。 ただそれが実際にどの状況なのか、例えば AsyncTask オブジェクトが Executor によってスレッドに乗せられてから doInBackgroud が呼び出されるまでの間なのか、等は分かりません。 running フラグによるループの制御ですが、このコードでは AsyncTask のキャンセル機能があるため、意味が無くなってますね。 ちょっとこの辺り、java の Swing と記憶が混じっていて Android だったか Swing だったかがハッキリしないのですが(?~?)、以前のキャンセル機能は、キャンセルするとスレッドが強制停止させられて、通信中だろうが何かの書き込み中だろうが全て途中で破棄されてしまい後からリカバリ出来なくなってしまう状態だったため、そのキャンセル機能に頼らずにプログラム的にちゃんと後始末をしてから停止するよう、自前でフラグを用意してやっていました。 running フラグはその名残りに見えます。 ですので、 while(running) { if (isCancelled()){ break; } は実際 while(!isCancelled()) { でも良いかもしれません。
Jhon_McClane

2021/08/22 17:24 編集

なるほど,いろいろ背景があるのですね。勉強になります. ・UDP で相互通信している場合はもちろんソケットを閉じてはいけないですが、相互通信するなら TCP のほうが良いのではないでしょうか。 >見落としていました.最終的に出来たら,音声を複数人に,向きによって音声を飛ばす相手を制御したりしたいので,UDPでやります.ただ,今のところ実現できるか分からないですがが(笑) ・Service の実装を検討されては如何でしょうか。 >見落としていました.処理的には,Serviceの方が望ましいと思います. 実装前に,Serviceの使い方などを見て,ルールが年々厳しくなっているとのことだったので, いろいろ面倒かなと思い、AsyncTaskで実装した次第です. また,このサイトhttps://android.keicode.com/basics/services-communicate-broadcast-receiver.phpを見て,Serviceを使う場合、ブロードキャストレシーバの知識も必要で,覚えることが大変そうだと思ったことも理由です. 今となっては,非同期の処理時間が長いのでServiceでやれば良かったのでは?と思ってます. AsyncTaskで長時間処理をしていると,なにかまずいことが起きるでしょうか?
jimbe

2021/08/22 18:14

まぁ、UDPかTCPか、ServiceかAsyncTaskかは、最も情報を持つ作者さんが一番考えて居られると見るべきですね(^_^ AsyncTask で長時間の処理をして何か問題があったという記事は、teratail のご質問とか何かの調査の途中で見かけたとかは今のところありません。 今思い付くとすれば、AsyncTask が使用するフレッドプール?のスレッドを1つ専用し続けてしまうだろうことぐらいです。
Jhon_McClane

2021/08/23 05:01

・スレッドを1つ専用し続けてしまう >確かに,受信を担うクラスとして,サーバからの受信,android端末からの受信を担うクラスをそれぞれ作成しましたが,どちらか一方しか機能していなく,結局一緒にしました.
jimbe

2021/08/23 07:24

> どちらか一方しか機能していなく そう言われてしまいますとまた気になってしまいますが、あまりに本来のご質問から離れそうですので、お聞きしないことにしますね <_ _;>
Jhon_McClane

2021/08/23 12:21

jimbe様 いつも助けていただき本当にありがとうございます. 今回の質疑応答はこれにて閉じたいと思います. 厚かましいお願いですが,また,機会があればよろしくお願いいたします. ありがとうございました。┏○ペコッ
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問