Android Studio 0.4.0 でAIDLを使ってサービスからUIを非同期更新
バックグラウンド(無限ループ)で重い処理が動き続ける状態で
それにあわせてUIを更新したいと思いました。
- Activity内でThreadを使うとUIが固まってしまう
- AsyncTaskは便利だけれどTaskが使い捨てなのが利用を悩む
- Messengerは非同期では無さそう。
勉強不足なのは重々承知なので誤解は多々あると思いますがご容赦下さい。
AIDLを用いたコールバックでUIを非同期に更新にチャレンジ
環境
OS | Windows8Pro 64bit |
Java | Oracle Java SE 1.7.0u45 |
IDE | Android Studio 0.4.0 |
Android SDK | r22.3 |
Gradle | 1.9 |
ファイルリスト
以下のファイルを生成・編集します。- IMyService.aidl
- IMyCallback.aidl
- MyService.java
- fragment_main.xml
- MainActivity.java
- AndroidManifest.xml
Android Studio 0.4.0 でウィザードが使えるところは使っています。
fragment_main.xmlを今回は使います。
AIDLを格納するフォルダをjavaと同階層につくります
javaと階層を揃えるたpackageをつくります
ドロイド君アイコンの空ファイルが出来ます
これでAIDLの記述準備が出来ました。
※コードに色は付きますが入力サポートは受けられません
AndroidManifest.xmlへの登録も完了します
※今回は修正が必要です
AIDLを含めてコンパイルをすると入力サポートが受けられます
IMyService.aidl
package jp.tackn.aidldemo; import jp.tackn.aidldemo.IMyCallback; /** * バックグラウンド処理を行うサービス * Created by Tackn on 13/12/22. */ interface IMyService { /** * コールバック登録。 * @param callback 登録するコールバック。 */ oneway void registerCallback(IMyCallback callback); /** * コールバック解除。 * @param callback 解除するコールバック。 */ oneway void unregisterCallback(IMyCallback callback); /** * 非同期の処理スタート * @param num 何号線か指定 */ void AsyncStart(); }
IMyCallback.aidl
package jp.tackn.aidldemo; /** * UI更新用のコールバック * Created by Tackn on 13/12/22. */ oneway interface IMyCallback { /** * バックグラウンド処理 * @param date UI更新に使うコールバックされる値 */ void doInBackground(String date); }
MyService.java
package jp.tackn.aidldemo; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; import java.util.Date; /** * Serviceから非同期コールバックでUIを更新デモ */ public class MyService extends Service { /** 識別用文字列 */ private static final String TAG = "AidlDemo"; /** AIDLのサービス実装 */ private IMyService.Stub mStub = new IMyService.Stub() { /** コールバックリスト */ private RemoteCallbackList<IMyCallback> mCallbacks = new RemoteCallbackList<IMyCallback>(); /** * コールバック登録 * @param callback 登録するコールバック。 * @throws android.os.RemoteException 接続エラー */ @Override public void registerCallback(IMyCallback callback) throws RemoteException { mCallbacks.register(callback); } /** * コールバック解除 * @param callback 解除するコールバック。 * @throws RemoteException 接続エラー */ @Override public void unregisterCallback(IMyCallback callback) throws RemoteException { mCallbacks.unregister(callback); } /** * 非同期処理開始 */ @Override public void AsyncStart() throws RemoteException { // 無限ループ定義 new Thread(new Runnable() { @Override public void run() { while (true){ //時間のかかる処理 try { Thread.sleep(10000L); } catch (InterruptedException e) { e.printStackTrace(); } //コールバックへ updateUI(new Date().toString()); } } }).start(); } /** * コールバックでUIの更新処理を行う * @param date 更新用文字列 */ private void updateUI(String date){ int n = mCallbacks.beginBroadcast(); for (int i = 0; i < n; i++) { try { mCallbacks.getBroadcastItem(i).doInBackground(date); } catch (RemoteException e) { Log.d(TAG, e.getMessage(), e); } } mCallbacks.finishBroadcast(); } }; /** * コンストラクタ */ public MyService() { super(); } /** * サービスがバインドされたときのコールバック * @param intent 情報コンテナ * @return 接続に利用されるサービス */ @Override public IBinder onBind(Intent intent) { return mStub; } }
fragment_main.xml
findViewById をするためにidを追加しただけです<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context="jp.tackn.aidldemo.MainActivity$PlaceholderFragment"> <TextView android:id="@+id/textView" android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>
MainActivity.java
メインの実装なのでちょっと長めですpackage jp.tackn.aidldemo; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.util.Log; import android.widget.TextView; /** * サービスからのコールバックでUI更新デモ */ public class MainActivity extends ActionBarActivity { /** 識別用文字列 */ private static final String TAG = "AidlDemo"; /** 更新対象のUI */ private TextView textView; /** UI更新用 */ private Handler mHandler; /** AIDLで記述されたサービスの参照 */ private IMyService mService; /** AIDLで記述されたコールバックの中身 */ private IMyCallback mCallback = new IMyCallback.Stub() { /** * バックグラウンド処理 * @param date 更新文字列 */ @Override public void doInBackground(final String date) throws RemoteException { mHandler.post(new Runnable() { public void run() { Log.d(TAG,"doInBackGroud:"+date); textView.setText( date + "\n" + textView.getText() ); } }); } }; /** * サービスとのコネクション */ private ServiceConnection mServiceConnection = new ServiceConnection() { /** * サービスが接続された時の処理 * @param name サービスのクラス名 * @param service サービスとのバインダ */ @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = IMyService.Stub.asInterface(service); try { Log.d(TAG, name.getClassName()); //コールバックの登録 mService.registerCallback(mCallback); //サービス側のメソッドを実行 mService.AsyncStart(); } catch (RemoteException e) { e.printStackTrace(); } } /** * サービスが切断された時の処理 * @param name サービスのクラス名 */ @Override public void onServiceDisconnected(ComponentName name) { mService = null; } }; /** * アクティビティ生成時の処理 * サービスの接続処理 * @param savedInstanceState 保存された状態 */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_main); mHandler = new Handler(); textView = (TextView)findViewById(R.id.textView); bindService(new Intent(IMyService.class.getName()), mServiceConnection, BIND_AUTO_CREATE); } /** * アクティビティ破棄時の処理 * サービスの切断処理 */ @Override protected void onDestroy(){ unbindService(mServiceConnection); super.onDestroy(); } }
AndroidManifest.xml
serviceのintent-filterでAIDLへのパスを与えます<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="jp.tackn.aidldemo" > <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="jp.tackn.aidldemo.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name="jp.tackn.aidldemo.MyService" android:process=":service" > <intent-filter> <action android:name="jp.tackn.aidldemo.IMyService" /> </intent-filter> </service> </application> </manifest>
実行![f:id:Tackn1977:20131222155851p:image f:id:Tackn1977:20131222155851p:image](https://cdn-ak.f.st-hatena.com/images/fotolife/T/Tackn1977/20131222/20131222155851.png)
service側のスレッドで実行している10秒毎の呼び出しで、
Activity側のUIが非同期で更新されていきます。