1. 程式人生 > >Android外掛化的相容性(上):Android O的適配

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

      首先宣告,《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

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

Android外掛相容性Android P的

     Android系統的每次版本升級,都會對原有程式碼進行重構,這就為外掛化帶來了麻煩。      Android P對外掛化的影響,主要體現在兩方面,一是它重構了H類中Activity相關的邏輯,另一個是它重構了Instrumentation。      3.1 H類的變身      3.1

Android插件的兼容性Android O

cto load 註意 android系統 自己 攔截 str oca 接口 首先聲明,《Android插件化開發指南》這本書所介紹的Android底層是基於Android6.0(API level 23)的,而本書介紹的各種插件化解決方案,以及配套的70多個例

Android 外掛分析3- Activity啟動流程

在真正分析外掛化技術前,我們必須瞭解一些必要的關於Android四大元件的相關知識。 以Activity為例,我們需要了解Activity啟動過程,才能有效的進行Hook實現外掛化。 以Android 8.1為例 我們啟動一個Activity通常會使用startActi

Android外掛探索資源載入

前情提要 PathClassLoader和DexClassLoader的區別 DexClassLoader的原始碼如下: public class DexClassLoader extends BaseDexClassLoader {

Android外掛原理Activity外掛

相關文章 前言 四大元件的外掛化是外掛化技術的核心知識點,而Activity外掛化更是重中之重,Activity外掛化主要有三種實現方式,分別是反射實現、介面實現和Hook技術實現。反射實現會對效能有所影響,主流的外掛化框架沒有采用此方式,關於介面實

Android外掛探索免安裝執行Activity

在上一篇中,我們介紹了兩種免安裝啟動Activity的方法。但是那兩種方法都有缺陷,必須在AndroidManifest.xml中註冊。那麼今天,我們來探索其它幾種不需要在清單檔案中註冊的啟動方式。 靜態代理啟動activity 通過前幾篇的探索我們

包建強的培訓課程10Android外掛從入門到精通

Android外掛化和熱修復 一.簡介 本課程結合講師多年來對Android外掛化技術的潛心研究,以及在千萬級使用者的App上長期實踐經驗,整理而成。本課程從四大元件的外掛化技術講起,中途會詳細剖析Android系統中與外掛化技術有關的底層概念,最後詳細介紹業界流行很廣的外

Android插件的兼容性Android P的

有一個 tca pro 內部 bject load anon stat activity Android系統的每次版本升級,都會對原有代碼進行重構,這就為插件化帶來了麻煩。 Android P對插件化的影響,主要體現在兩方面,一是它重構了H類中Acti

Kubernetes 新概念 “Initializers”解析能讓你為叢集編寫外掛的新模型_Kubernetes中文社群

Kubernetes v1.7 新增了 Initializers,它可以用來方便地擴充套件准入控制,今天的文章來自 Google Kubernetes 現役工程師 Ahmet Alp Balkan,讓他帶領我們詳解 Initializer。上期將帶大家做一個簡單的瞭解,明白 Initializ

Android開發知識Android事件處理機制事件分發、傳遞、攔截、處理機制的原理分析

  在我們剛開始學習安卓的時候,總會一開始就接觸到Button,也就是對按鈕進行一個事件監聽的事件,當我們點選螢幕上的按鈕時就可以觸發一個點選事件。那麼,從我們點選螢幕到按鈕觸發事件這個過程,是什麼樣子的呢?本文我們就來談一下關於事件攔截處理機制的基本知識。

Android Plugin插樁式實現外掛開發-實現原理及Activity外掛實現

1. 前言在現在一些大型的Android應用中都採用了外掛化的開發方式,比如美團,支付寶及我們常用的微信等採用了插修的化的開發方式來進行開發的,既然國內一流的網際網路公司都採用這樣的方式來開發那它一定能帶給開發部署大型應用帶來很大的便捷,那麼外掛化的優勢在哪裡呢?1.1 外掛

多媒體開發11Android平臺裁剪m4a

Android手機上設定鈴聲的操作比較靈活,你聽到一首喜歡的歌曲,馬上就可以對這首歌曲進行裁剪,裁剪到片段後,再通過系統的介面設定為鈴聲(電話鈴聲、鬧鐘鈴聲等)。前提是,播放這首歌的APP,需要提供裁剪歌曲的功能。 那麼,怎麼樣實現擷取音訊檔案的功能呢? 基於之前的介紹,你可能很自然就想到使用FFmpeg命令

Pro Android學習筆記 ActionBar1Home圖標區

ces tom 新的 方便 find rac vertica lba manifest ?? Pro Android學習筆記(四八):ActionBar(1):Home圖標區 2013年03月10日 ? 綜合 ? 共 3256字 ? 字號 小 中 大 ? 評論關閉

Android項目實戰實現第一次進入軟件的引導頁

spl cli rate gets -i let ride open rtm 原文:Android項目實戰(三):實現第一次進入軟件的引導頁最近做的APP接近尾聲了,就是些優化工作了, 我們都知道現在的APP都會有引導頁,就是安裝之後第一次打開才顯示的引導頁面(介紹這個軟

Android項目實戰JazzyGridView和JazzyListView的使用

@+ java類 gif HR 使用 out tar 項目 適配器 原文:Android項目實戰(六):JazzyGridView和JazzyListView的使用GridView和ListView控件劃動的動畫效果 ---------------------------

Android項目實戰ViewPager切換動畫3.0版本以上有效果

技術 code info utf-8 play draw pos support addview 原文:Android項目實戰(四):ViewPager切換動畫(3.0版本以上有效果)學習內容來自“慕課網” 一般APP進去之後都會有幾張圖片來導航,

Android項目實戰Dialog主題Activity實現自定義對話框效果

utf 定義 nim 亮點 close .com 去除 span 代碼 原文:Android項目實戰(七):Dialog主題Activity實現自定義對話框效果想必大家都用過Dialog主題的Activity吧,用它來顯示自定義對話框效果絕對是一個非常不錯的選擇。 即把a

Android項目實戰CustomShapeImageView 自定義形狀的ImageView

重點 clas home 項目開發 logs clip com html days 原文:Android項目實戰(九):CustomShapeImageView 自定義形狀的ImageView一個兩年前出來的第三方類庫,具有不限於圓形ImageView的多種形狀ImageV

Android項目實戰十三淺談EventBus

app mage tar 一句話 creat 簡單 銷毀 second gradle 原文:Android項目實戰(十三):淺談EventBus概述: EventBus是一款針對Android優化的發布/訂閱事件總線。 主要功能是替代Intent,Handler,Bro