Edited at

別Package別Process別APKのServiceにAIDLからBindするサンプル (Android L以降対応版)

More than 1 year has passed since last update.


Android 5.0 Lollipopでの変更点

AIDLを使ってAndroid Serviceにbind/unbindする方法は初期のAndroidから使えますが,Android 5.0 Lollipopから必要条件が追加されていて,以前からあるサンプルコードではIllegalArgumentException発生で正常に動作しなくなっています.

↓ Android L以上でIllegalArgumentException発生させる部分のCode


ContextImple.validateServiceIntent()

private void validateServiceIntent(Intent service) {

if (service.getComponent() == null && service.getPackage() == null) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
IllegalArgumentException ex = new IllegalArgumentException(
"Service Intent must be explicit: " + service);
throw ex;
} else {
Log.w(TAG, "Implicit intents with startService are not safe: " + service
+ " " + Debug.getCallers(2, 3));
}
}
}

http://androidxref.com/5.0.0_r2/xref/frameworks/base/core/java/android/app/ContextImpl.java#validateServiceIntent

のように,service.getComponent()かservice.getPackage()のどちらか一方が非NULLでなければExplicit Intentとは判定されなくなっているようです.

これを踏まえて,AIDLからServiceにBindするサンプルを書いてみます.


Server側

AIDLから接続される側のAPK.


Dir構成 (Gradle Build世代)

AidlServer/

- app/
- src/
- main/
- aidl/
- test/aidl/server/IAidlServerService.aidl
- java/
- test/aidl/server/AidlServerService.java
- res/
- (省略)
- AndroidManifest.xml

GitHubにSample CodeをUpしました.

https://github.com/fezrestia/sample-bind-aidl-service/tree/master/AidlServer


Android Manifest

公開するServiceの定義.Package + Intent FilterでExplicit Intentにする.


AndroidManifest.xml

<manifest

package="test.aidl.server"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
>
<service
android:name=".AidlServerService"
android:exported="true"
>
<intent-filter>
<action android:name="test.aidl.server.ACTION_BIND" />
</intent-filter>
</service>
</application>
</manifest>


Sample Service

『HELLO WORLD !』という文字列を返すだけのAPIをもつサンプル.


AidlServerService.java

package test.aidl.server;

// import省略

public class AidlServerService extends Service {

private IAidlServerService.Stub mStub = new IAidlServerService.Stub() {
@Override
public String getMessage() throws RemoteException {
return "HELLO WORLD !";
}
};

@Override
public IBinder onBind(Intent intent) {
return mStub;
}

// その他@Override省略
}



AIDL

同じものをClient側にも持たせる.Build時の名前解決にも必要.


IAidlServerService.aidl

package test.aidl.server;

interface IAidlServerService {
String getMessage();
}


ちなみに,.aidlのファイルはBuild時にParseされ,Stubという子クラスを持ったCodeが自動生成されてBuildに使われます.


Client側

AIDLに接続する側のAPK.


Dir構成 (Gradle Build世代)

AidlClient/

- app/
- src/
- main/
- aidl/
- test/aidl/server/IAidlServerService.aidl (Server側と同じpackage/file)
- java/
- test/aidl/client/AidlClientActivity.java
- res/
- (省略)
- AndroidManifest.xml

GitHubにSample CodeをUpしました.

https://github.com/fezrestia/sample-bind-aidl-service/tree/master/AidlClient


Android Manifest

Client側のManifestはServiceにbindするActivityのみ.Activityである必要は無いです.


AndroidManifest.xml

<manifest

package="test.aidl.client"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
>
<activity
android:name=".AidlClientActivity"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>


Sample Activity

onResume/onPauseでbind/unbindService,ButtonのonClick()でAPI Callする設定.


AidlClientActivity.java

package test.aidl.client;

// AIDLのimport
import test.aidl.server.IAidlServerService;

// その他import省略

public class AidlClientActivity extends Activity {

// onCreateなどでButton View取得 + setOnClickListener()などしておく.

@Override
public void onResume() {
super.onResume();

// Bind.
//
// NOTICE:
// Android 5.0 Lolipop以降でExplicit Intent判定に必須のため,setPackage()でpackage名を明示.
//
Intent service = new Intent("test.aidl.server.ACTION_BIND");
service.setPackage("test.aidl.server"); // Android 5.0 Lolipop以降で必須の設定
bindService(service, mAidlConnection, BIND_AUTO_CREATE);
}

@Override
public void onPause() {
// Unbind.
if (mService != null) {
unbindService(mAidlConnection);
}

super.onPause();
}

private IAidlServerService mService = null;
private AidlConnection mAidlConnection = new AidlConnection();

// Service接続状態のCallback.
private class AidlConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IAidlServerService.Stub.asInterface(service);
}

@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
}

// Button ClickでAPI Callするサンプル.
private class TriggerOnClickListener implements View.OnClickListener {
@Override
public void onClick(View view) {
try {
ret = mService.getMessage();
} catch (RemoteException e) {
e.printStackTrace();
}

Log.e("TraceLog", "#### Received : " + ret);
}
}
}



AIDL

Server側のAIDLをそのままコピーしてClientのProjectにもたせておく.


IAidlServerService.aidl

package test.aidl.server;

interface IAidlServerService {
String getMessage();
}


こちらもServer側と同じくCodeの自動生成 + Build時名前解決に使われる.


まとめ

AndroidはOS Updateでpublic APIの暗黙仕様がコロコロ変わって過去資産がいきなり使えなくなったりして震えますね.

Code一式をGitHubとかに上げたら更新します.

→追記しました.

///---