標籤:android 源碼 android 5.0 lollipop 隱式service
一.概述
Android系統升級到5.0之後做了不少的變化(5.0變化),開發人員一定要注意這些變化,要不然就有的折騰了.這次最大的變化應該是把Dalvik虛擬機器改成了ART(Android Runtime),後續會專門講解這一塊.其他的都是一些零碎的問題,例如前段時間發了一篇Android 5.0之後修改了HashMap的實現(傳送門).這篇主要講一下遇到跟Service相關的問題.
二.詳情
Service身為Android四大組件之一,它的使用頻率還是比較高的,並且現在主要都是運用在比較關鍵的部位,例如升級推送等.在Android 5.0之後google出於安全的角度禁止了隱式聲明Intent來啟動Service.也禁止使用Intent filter.否則就會拋個異常出來.
官方的解釋如下.
那麼google到底是怎麼限制的呢?限制的判定條件是什麼呢?這些都可以從Android 4.4和Android 5.0的源碼中找到區別.
在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; }
三.案例分析 經過這麼分析之後單看這個問題是很好做相容的,但是結合實際的一些複雜情況就不一定了.之前就碰到過一個比較複雜的相容. 情境複現: 將項目裡的targetSdkVersion升級到22之後,針對這篇Service的問題做了相容,so easy啊,但是萬萬沒想到項目裡用的某盟的第三方登陸分享的SDK版本太老了(一年前),開發沒有去升級SDK跟check相容性.並且經過四輪測試愣是沒有測到這個點,結果線上上發現了這個Bug,泥煤啊Android 5.0裝置用微博登陸就閃退,泥煤啊.幸好這個項目做了線上bug熱修複,這個時候就體現出這個功能的好處了,之前又部落格簡單講解了一下實現原理(傳送門). 接下來思路就是升級最新的某盟SDK然後打個補丁包給線上升級過去.然而升級完最新的SDK之後發現並沒有什麼卵用,看過熱修複那篇部落格的童鞋應該可以想到,那種打dex補丁的形式是不能添加新資源的,而最新的SDK新增了不少資源進來,所以這個思路是行不通的. 既然升級最新的SDK不行,那能不能修改老的SDK自己給它做Android 5.0相容呢?理論上來說是可行的.說幹就幹,先記錄一下SDK崩潰的記錄.
再把SDK jar反編譯一下,裡面很多被混淆過,幸好崩潰的位置這個類的邏輯還有可讀性都還比較完整.直接定位到崩潰的方法.果然是隱式綁定的Service,上面分析源碼的時候從Intent構造源碼可以看到這種聲明方式既沒有構造有效component,也沒有提供出packageName,崩了也無話可說.
既然定位到了位置,那麼就單獨把這個類抽離出來,新開一個Android工程,建立一個library的module,裡面只有接下來要修改的這一個檔案,由於這個類裡面會引用SDK jar包裡面的資源,所以也要把jar包引入到module裡面.
包名一定要和之前一模一樣.因為我們修改過之後要替換掉SDK中原有的.class檔案.
build一下每問題,OK.因為這裡啟動的Service是新浪微部落格戶端裡面的一個Service,串連上之後會通過AIDL進行跨進程通訊.所以我們沒辦法在構造Intent的時候就顯式聲明.既然沒有辦法構建有效component,那麼給它設定一個包名也可以生效的.既然是新浪微博那麼包名應該是com.sina.weibo.
private boolean a(Activity paramActivity) { Context localContext = paramActivity.getApplicationContext(); Intent mIntent = new Intent("com.sina.weibo.remotessoservice"); mIntent.setPackage("com.sina.weibo"); return localContext.bindService(mIntent, this.serviceConnection, Context.BIND_AUTO_CREATE); }
reBuild一下,然後去主專案build檔案夾裡面的中間資源裡面找到module打成的jar包,直接把編譯好的.class解壓出來.
解壓某盟的SDK,進入到正確的路徑下直接替換剛才編譯出來的.class檔案.
回到SDK的跟目錄下,運行 jar cvf sdk.jar * 命令進行重打包,將重新打好的SDK替換到項目裡面.驗證bug.BinGo!搞定!這樣就可以打個dex補丁包給線上的版本更新過去修複5.0相容的bug了.
四.總結 這裡結合這個案例簡單分析了一下Android 5.0之後對啟動綁定Service做的變化.碰到這種問題也是因為做相容的時候沒有覆蓋所有的地方,這些坑都是跑不掉的.
轉載請註明出處:http://blog.csdn.net/l2show/article/details/47421961
Android 5.0之後隱式聲明Intent 啟動Service引發的問題