Android 5.0 Lollipopでの変更点
AIDLを使ってAndroid Serviceにbind/unbindする方法は初期のAndroidから使えますが,Android 5.0 Lollipopから必要条件が追加されていて,以前からあるサンプルコードではIllegalArgumentException発生で正常に動作しなくなっています.
↓ Android L以上でIllegalArgumentException発生させる部分のCode
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));
}
}
}
のように,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にする.
<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をもつサンプル.
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時の名前解決にも必要.
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である必要は無いです.
<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する設定.
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にもたせておく.
package test.aidl.server;
interface IAidlServerService {
String getMessage();
}
こちらもServer側と同じくCodeの自動生成 + Build時名前解決に使われる.
まとめ
AndroidはOS Updateでpublic APIの暗黙仕様がコロコロ変わって過去資産がいきなり使えなくなったりして震えますね.
Code一式をGitHubとかに上げたら更新します.
→追記しました.
///---