Android外掛程式化的相容性(上):Android O的適配

來源:互聯網
上載者:User

標籤:void   nta   最簡   tcl   class   啟動   lse   base   object   

      首先聲明,《Android外掛程式化開發指南》這本書所介紹的Android底層是基於Android6.0(API level 23)的,而本書介紹的各種外掛程式化解決方案,以及配套的70多個例子,在Android7.0(API level 24)手機上測試都是能正常工作的。

     如果讀者您的手機是Android 26、27,甚至28(也就是Android P),那麼會有30個外掛程式化的例子不能正常工作,這是因為Android系統底層的源碼改動導致的。

     本篇文章,專門介紹Android O的改動對外掛程式化產生的影響,以及相應的外掛程式化解決方案。

 

(一)從ActivityManagerNative的重構談起

     首先是ActivityManagerNative這個類的gDefault欄位,這個欄位在API 25以及之前的版本,定義如下:

public abstract class ActivityManagerNative extends Binder implements IActivityManager {    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {        protected IActivityManager create() {            IBinder b = ServiceManager.getService("activity");            if (false) {                Log.v("ActivityManager", "default service binder = " + b);            }            IActivityManager am = asInterface(b);            if (false) {                Log.v("ActivityManager", "default service = " + am);            }            return am;        }    };}

 

     所以,我們可以通過反射擷取ActivityManagerNative的gDefault欄位,執行它的create方法,得到IActivityManager介面類型的對象。

     看到這個介面類型,我們眼前一亮,可以通過Proxy.newProxyInstance方法,hook掉這個IActivityManager對象,攔截它的startActivity方法,把要啟動的、沒有在Manifest中聲明的Activity,替換成占坑StubActivity,代碼如下所示:

    public static void hookAMN() throws ClassNotFoundException,            NoSuchMethodException, InvocationTargetException,            IllegalAccessException, NoSuchFieldException {        //擷取AMN的gDefault單例gDefault,gDefault是final靜態        Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault");        // gDefault是一個 android.util.Singleton<T>對象; 我們取出這個單例裡面的mInstance欄位        Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", gDefault, "mInstance");        // 建立一個這個對象的代理對象MockClass1, 然後替換這個欄位, 讓我們的代理對象幫忙幹活        Class<?> classB2Interface = Class.forName("android.app.IActivityManager");        Object proxy = Proxy.newProxyInstance(                Thread.currentThread().getContextClassLoader(),                new Class<?>[] { classB2Interface },                new MockClass1(mInstance));        //把gDefault的mInstance欄位,修改為proxy        Class class1 = gDefault.getClass();        RefInvoke.setFieldObject("android.util.Singleton", gDefault, "mInstance", proxy);    }

 

     我們在書中的第5章詳細講解過上述這些代碼。但不幸的是,這些代碼在Android O(API level 26)以上的系統版本中就不能運行了,在運行到這句話的時候,gDefault的值為空白:

Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault");

 

     這是因為Google在Android O中,把ActivityManagerNative中的這個gDefault欄位刪除了,轉移到了ActivityManager類中,但此時,這個欄位改名為IActivityManagerSingleton,所以在Android P中,要把這句話改為:

Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManager", "IActivityManagerSingleton");

    

     但這又不相容於Android O以下的版本了,所以寫一個if-else條件陳述式,根據Android系統的版本,來做不同的處理,如下所示:

        Object gDefault = null;        if (android.os.Build.VERSION.SDK_INT <= 25) {            //擷取AMN的gDefault單例gDefault,gDefault是靜態            gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault");        } else {            //擷取ActivityManager的單例IActivityManagerSingleton,他其實就是之前的gDefault            gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManager", "IActivityManagerSingleton");        }

 

(二)Element和DexFile的興衰史

     接下來我們把目光轉移到外掛程式類的載入。我們在書中介紹了3種載入方式:

     1. 為每一個外掛程式建立一個ClassLoader,用外掛程式ClassLoader去載入外掛程式中的類。

     2. 把所有外掛程式中的dex,都合并到宿主App的dex數組中。

     3. 把宿主App所使用的ClassLoader,替換成我們自己建立的ClassLoader,在這個新的ClassLoader中,有一個容器變數,承載所有外掛程式的ClassLoader,用來載入外掛程式中的類。

     這其中,第2種方式的實現是最簡單的,也就是合并所有外掛程式的dex到一個數組中,具體代碼實現如下所示:

public final class BaseDexClassLoaderHookHelper {    public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)            throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {        // 擷取 BaseDexClassLoader : pathList        Object pathListObj = RefInvoke.getFieldObject(DexClassLoader.class.getSuperclass(), cl, "pathList");        // 擷取 PathList: Element[] dexElements        Object[] dexElements = (Object[]) RefInvoke.getFieldObject(pathListObj, "dexElements");        // Element 類型        Class<?> elementClass = dexElements.getClass().getComponentType();        // 建立一個數組, 用來替換原始的數組        Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);        // 構造外掛程式Element(File file, boolean isDirectory, File zip, DexFile dexFile) 這個建構函式        Class[] p1 = {File.class, boolean.class, File.class, DexFile.class};        Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};        Object o = RefInvoke.createObject(elementClass, p1, v1);        Object[] toAddElementArray = new Object[] { o };        // 把原始的elements複製進去        System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);        // 外掛程式的那個element複製進去        System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);        // 替換        RefInvoke.setFieldObject(pathListObj, "dexElements", newElements);    }}

 

     這個思路沒問題。注意其中的這麼幾句話:

Class[] p1 = {File.class, boolean.class, File.class, DexFile.class};Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};Object o = RefInvoke.createObject(elementClass, p1, v1);Object[] toAddElementArray = new Object[] { o };

 

     這幾句話中,通過反射執行了Element的帶有4個參數的建構函式,但不幸的是,在Android O以及之後的版本,這個帶有4個參數的建構函式就被廢棄了。

     此外,在這個建構函式中使用到的DexFile這個類,也被廢棄了,對此Google給出的解釋是,只有Android系統可以使用DexFile,App層面不能使用它。

     於是,我們不得不另闢蹊徑,通過執行DexPathList類的makeDexElements方法,來產生外掛程式中的dex:

List<File> legalFiles = new ArrayList<>();legalFiles.add(apkFile);List<IOException> suppressedExceptions = new ArrayList<IOException>();Class[] p1 = {List.class, File.class, List.class, ClassLoader.class};Object[] v1 = {legalFiles, optDexFile, suppressedExceptions, cl};Object[] toAddElementArray = (Object[])RefInvoke.invokeStaticMethod("dalvik.system.DexPathList", "makeDexElements", p1, v1);

 

     這段代碼,在Android O之前的版本也是適用的。所以,我們找到了比DexFile更好用的makeDexElements方法,進行Hook。

Android外掛程式化的相容性(上):Android O的適配

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.