Tacknのつぶやき このページをアンテナに追加 RSSフィード

2013年12月22日

Android Studio 0.4.0 でAIDLを使ってサービスからUIを非同期更新

バックグラウンド(無限ループ)で重い処理が動き続ける状態で

それにあわせてUIを更新したいと思いました。

  • Activity内でThreadを使うとUIが固まってしまう
  • AsyncTaskは便利だけれどTaskが使い捨てなのが利用を悩む
  • Messengerは非同期では無さそう。

勉強不足なのは重々承知なので誤解は多々あると思いますがご容赦下さい。

AIDLを用いたコールバックでUIを非同期に更新にチャレンジ

環境

OSWindows8Pro 64bit
JavaOracle Java SE 1.7.0u45
IDEAndroid Studio 0.4.0
Android SDKr22.3
Gradle1.9

ファイルリスト

以下のファイルを生成・編集します。

  • IMyService.aidl
  • IMyCallback.aidl
  • MyService.java
  • fragment_main.xml
  • MainActivity.java
  • AndroidManifest.xml

Android Studio 0.4.0 でウィザードが使えるところは使っています。

f:id:Tackn1977:20131222153034p:image

新規プロジェクトの作成

f:id:Tackn1977:20131222153035p:image

ウィザードをすすめます

f:id:Tackn1977:20131222153036p:image

デモなのでシンプルなのを選びます

f:id:Tackn1977:20131222153037p:image

fragment_main.xmlを今回は使います。

f:id:Tackn1977:20131222153038p:image

gradle でプロジェクトがつくられていきます

f:id:Tackn1977:20131222153039p:image

プロジェクトが起動した直後

f:id:Tackn1977:20131222153040p:image

AIDLを格納するフォルダをjavaと同階層につくります

f:id:Tackn1977:20131222153041j:image

javaと階層を揃えるたpackageをつくります

f:id:Tackn1977:20131222153042p:image

新規作成でFileを選択

f:id:Tackn1977:20131222153044p:image

拡張子まで含めて空ファイルを作成

f:id:Tackn1977:20131222153045p:image

ドロイド君アイコンの空ファイルが出来ます

これでAIDLの記述準備が出来ました。

※コードに色は付きますが入力サポートは受けられません

f:id:Tackn1977:20131222153050p:image

ServiceはAndroidComponentにあります

f:id:Tackn1977:20131222153051p:image

ウィザードでServiceを選択します

f:id:Tackn1977:20131222153052p:image

AndroidManifest.xmlへの登録も完了します

※今回は修正が必要です

f:id:Tackn1977:20131222153054p:image

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

service側のスレッドで実行している10秒毎の呼び出しで、

Activity側のUIが非同期で更新されていきます。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/Tackn1977/20131222/1387695859