Android 5.0之後隱式聲明Intent 啟動Service引發的問題

來源:互聯網
上載者:User

標籤: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引發的問題

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.