Service Intent must be explicit錯誤
解決方案:
使用隱式方式繫結: **1.在service中新增action:**<service android:name="your service"
android:exported="true">
<intent-filter>
<action android:name="your action" />
</intent-filter>
</service>
2.在繫結時intent設定action與package:
Intent intent = new Intent();
intent.setAction("your action");
intent.setPackage("service app's package name");//設定service所在app的包名
activity.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
然後就可以順利繫結aidl中的service了。
注:onServiceDisconnected是繫結後意外斷連後的回撥,一般進行重新繫結的操作。
private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { } @Override public void onServiceDisconnected(ComponentName name) { //一般進行重新繫結的操作 } };
原因:
在Android 5.0之後google出於安全的角度禁止了顯式宣告Intent來繫結aidl中的Service.否則就會拋個異常出來.
在Android 4.4的ContextImpl原始碼中,能看到如果啟動service的intent的component和package都為空並且版本大於KITKAT的時候只是報出一個警報,告訴開發者隱式宣告intent去啟動Service是不安全的.再往下看,丫的異常都寫好了只是註釋掉了,看來google早就想這麼幹了.
private void validateServiceIntent(Intent service) {
if (service.getComponent() == null && service.getPackage() == null) {
if (true || getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.KITKAT) {
Log.w(TAG, "Implicit intents with startService are not safe: " + service
+ " " + Debug.getCallers(2, 3));
//IllegalArgumentException ex = new IllegalArgumentException(
// "Service Intent must be explicit: " + service);
//Log.e(TAG, "This will become an error", ex);
//throw ex;
}
}
}
果然在Android 5.0的原始碼中上面註釋的程式碼已經不註釋了,當targetSdkVersion版本大於LOLLIPOP直接異常丟擲來,要求Service intent必須顯式宣告.所以如果開發的應用指定targetSdkVersion版本是小於LOLLIPOP的話還是按以前的方式給報個警報,這也就造成了如果沒有做了完善的Android 5.0相容就貿然把targetSdkVersion升到LOLLIPOP的話很有可能就會碰到這個問題.並且這個問題是很嚴重的,想象一下,你的app自升級的Service是隱式啟動的,碰到這個問題後app就不能自升級了,這批使用者有可能就一直停留在當前版本.會產生很致命的問題.
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));
}
}
}
從原始碼中的邏輯來看的話,判斷一個intent是不是顯式宣告的點就是component和package,只要這兩個有一個生效就不算是隱式宣告的,接下來繼續分析一下Intent的原始碼,可以看到下面三種構造方式,設定action來宣告Intent是沒有構建component的,所以顯式宣告需要用到第一和第二種構造(還有帶packagename或component的拷貝構造),或者後面設定package屬性.
public Intent(Context packageContext, Class<?> cls) {
mComponent = new ComponentName(packageContext, cls);
}
public Intent(String action) {
setAction(action);
}
public Intent(String action, Uri uri,
Context packageContext, Class<?> cls) {
setAction(action);
mData = uri;
mComponent = new ComponentName(packageContext, cls);
}
public Intent setPackage(String packageName) {
if (packageName != null && mSelector != null) {
throw new IllegalArgumentException(
"Can't set package name when selector is already set");
}
mPackage = packageName;
return this;
}