Android插件化原理

一、前言

這篇文章來說一下Android插件化的原理和大概的一個運行流程,最後將一個demo呈現出來。java

二、分析

插件說到底就是一個apk文件,咱們要作的事情是從宿主中加載該apk文件的類對象(好比啓動Activity)和使用該apk文件的資源等操做。咱們知道系統是不會安裝apk插件的,因此宿主是不知道咱們的插件的任何信息。咱們以前分析了Activity的啓動過程,其實就是在ActivityThread的performLaunchActivity方法中建立了Activity對象,如今再看一下其中的邏輯:android

java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
複製代碼

activity的建立是使用反射的方式進行的,而ClassLoader是由r.packageInfo提供的,這裏的r.packageInfo是一個LoadedApk對象,LoadedApk對象存儲了Apk文件的相關信息,而該對象的賦值是在H類的handleMessage中:git

case LAUNCH_ACTIVITY: {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

    r.packageInfo = getPackageInfoNoCheck(
            r.activityInfo.applicationInfo, r.compatInfo);
    handleLaunchActivity(r, null);
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
複製代碼

能夠看到r.packageInfo(LoadedApk對象)是經過getPackageInfoNoCheck()方法獲取的,咱們進入到getPackageInfoNoCheck()方法中:github

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
        CompatibilityInfo compatInfo) {
    return getPackageInfo(ai, compatInfo, null, false, true, false);
}
複製代碼

它又調用了getPackageInfo方法:數組

private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
        ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
        boolean registerPackage) {
    final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
    synchronized (mResourcesManager) {
        WeakReference<LoadedApk> ref;
        if (differentUser) {
            // Caching not supported across users
            ref = null;
        } else if (includeCode) {
            ref = mPackages.get(aInfo.packageName);
        } else {
            ref = mResourcePackages.get(aInfo.packageName);
        }

        LoadedApk packageInfo = ref != null ? ref.get() : null;
        if (packageInfo == null || (packageInfo.mResources != null
                && !packageInfo.mResources.getAssets().isUpToDate())) {
            if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
                    : "Loading resource-only package ") + aInfo.packageName
                    + " (in " + (mBoundApplication != null
                            ? mBoundApplication.processName : null)
                    + ")");
            packageInfo =
                new LoadedApk(this, aInfo, compatInfo, baseLoader,
                        securityViolation, includeCode &&
                        (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

            if (mSystemThread && "android".equals(aInfo.packageName)) {
                packageInfo.installSystemApplicationInfo(aInfo,
                        getSystemContext().mPackageInfo.getClassLoader());
            }

            if (differentUser) {
                // Caching not supported across users
            } else if (includeCode) {
                mPackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
            } else {
                mResourcePackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
            }
        }
        return packageInfo;
    }
}
複製代碼

它會從mPackages的這樣的一個ArrayMap中去獲取,若是獲取到則返回,沒有獲取到則new了一個LoadedApk對象,而後將其存入到mPackages中。 因此實現插件化咱們能夠這樣作(方法一): 因爲插件的加載須要一個ClassLoader,而這個ClassLoader是自定義的,而且從上面的源碼能夠看到系統的ClassLoader是從LoadedApk對象中獲取的,因此咱們須要一個自定義的LoadedApk對象,從上面的源碼可知,LoadedApk對象是從mPackages這樣的一個ArrayMap中獲取的,因此咱們能夠將咱們自定義的LoadedApk對象存入到這個mPackages這個map中,key爲插件的packagename,這樣在要獲取LoadedApk對象時就直接從map中返回了,而後獲得的是咱們自定義的LoadedApk對象,而後從該對象中獲得自定義的ClassLoader對象,從而使用該ClassLoader能夠加載外部插件了。 這種方法是可行的,可是過程比較複雜。由於在上面的過程當中須要hook住比較多的系統類和方法,而且在構建一個LoadedApk對象時,還須要一個ApplicationInfo對象,該對象須要解析插件的Manifest文件獲取,因此咱們還須要手動去解析Manifest文件,這就增長了該方法的複雜程度。bash

**那是否還有其餘方法來實現插件化呢(方法二)?**從Dex的加載過程這篇文章中能夠看到,類的加載最終是調用了DexPathList的findClass方法,而在DexPathList中維護了一份Element類型的dexElement數組,這個數組存放的就是dex文件的信息,因此咱們能夠將插件的dex文件信息也存放到該數組中,而後在加載插件類的時候,天然就可以從插件的dex文件中進行加載插件的類。 這種方法相比於上述的第一種方法,顯然是比較簡潔而且便捷。可是也是存在着問題的。對於四大組件中的Activity、Service、ContentProvider是須要在Manifest註冊的,不然會報錯誤異常。從Activity的啓動過程這篇文章能夠看出,咱們能夠hook住startActivity這個方法,使用一個佔坑的Activity在Manifest中註冊,從而解決問題。原理是這樣的: 首先在啓動Activity的時候,咱們將要啓動的Activity替換爲佔坑的Activity,用這個佔坑的Activity去進行系統的合法性驗證,當驗證經過的時候,在生成Activity對象時,再次hook住Activity的建立方法,將真正要啓動的Activity替換回來,讓系統建立一個真正須要啓動的Activity對象。app

三、實現

根據上述的分析,咱們選擇第二種方法進行demo實現,下面詳細介紹實現的步驟:ide

3.1 加載插件apk

首先須要建立一個自定義的ClassLoader去加載外部插件,而加載外部插件的ClassLoader類型必須是DexClassLoader,具體實現以下:優化

//插件的外部路徑
String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "plugin_demo.apk";
//插件優化後的路徑
String cachePath = getCacheDir().getAbsolutePath();
//自定義的ClassLoader
DexClassLoader pluginLoader = new DexClassLoader(apkPath, cachePath, cachePath, getClassLoader());
複製代碼

3.2 合併宿主和插件的Element[]數組

拿到宿主的ClassLoader,經過ClassLoader獲取pathList,經過pathList獲取Element[]數組;拿到插件的ClassLoader,插件和宿主作一樣的操做獲取Element[]數組,而後將這兩個數組合並,最後將合併的Element[]數組設置給宿主的pathList。ui

public static void mergePathFileElement(ClassLoader pluginLoader) {
  //拿到宿主的ClassLoader
  ClassLoader originPathLoader = MainApplication.getContext().getClassLoader();
  //獲取宿主的pathList
  Object originPathList = getPathList(originPathLoader);
  //獲取插件的pathList
  Object pluginPathList = getPathList(pluginLoader);
  //獲取宿主的Element[]數組
  Object originElements = getElement(originPathList);
  //獲取插件的Element[]數組
  Object pluginElements = getElement(pluginPathList);
  //將宿主和插件的Element[]數組進行合併
  Object combineElements = combineElements(originElements, pluginElements);
  //將合併的Elements設置給宿主的pathList
  setDexElements(originPathList, combineElements);
}
複製代碼

上面的getPathList和getElement都是經過反射獲取的,咱們主要看一下數組合並的邏輯:

private static Object combineElements(Object originElements, Object pluginElements) {
  Class<?> arrayType = originElements.getClass().getComponentType();
  int originLength = Array.getLength(originElements);
  int pluginLength = Array.getLength(pluginElements);
  int lengths = originLength + pluginLength;
  Object newArray = Array.newInstance(arrayType, lengths);
  for (int i = 0; i < lengths; i++) {
      if (i < originLength) {
          Array.set(newArray, i, Array.get(originElements, i));
      } else {
          Array.set(newArray, i, Array.get(pluginElements, i - originLength));
      }
  }
  return newArray;
}
複製代碼

合併的邏輯也很簡單,首先經過反射獲取數組的類型並使用Array.newInstance()新建一個數組,而後將原有的兩個數組設置到新建的那個數組中。

3.3 代理系統啓動Activity的方法

首先咱們從Activity的啓動過程這篇文章能夠看出,Activity的啓動最終會走到Instrumentation類的execStartActivity方法,在該方法中調用了ActivityManager.getService().startActivity方法,咱們看一下這裏的邏輯:

int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);

public static IActivityManager getService() {
      return IActivityManagerSingleton.get();
}
複製代碼

ActivityManager.getService()方法返回了一個IActivityManager對象,而該對象是經過IActivityManagerSingleton.get()產生的,咱們再看一下IActivityManagerSingleton.get()方法:

private static final Singleton<IActivityManager> IActivityManagerSingleton =
      new Singleton<IActivityManager>() {
      @Override
      protected IActivityManager create() {
        final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
        final IActivityManager am = IActivityManager.Stub.asInterface(b);
        return am;
      }
};

public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}
複製代碼

IActivityManagerSingleton是一個Singleton對象,get()方法返回了的是mInstance對象,而該對象是經過create()方法生成的,從create()方法中能夠看到生成了一個IActivityManager類型的對象,該對象是一個遠程代理類。 所以咱們要hook住startActivity方法,那麼咱們要生成一個IActivityManager對象的一個代理類,所以咱們須要獲取現有的IActivityManager對象,也就是說要獲取ActivityManager.getService()方法返回的對象。

Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
Field singletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
singletonField.setAccessible(true);
Object singletonValue = singletonField.get(null);

//singletonValue是一個 android.util.Singleton對象;取出這個單例裏面的字段
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstance = singletonClass.getDeclaredField("mInstance");
mInstance.setAccessible(true);
//取出了AMS代理對象,這裏的AMS代理對象就是singletonValue對象的值
Object iActivityManager = mInstance.get(singletonValue);

Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()
        , new Class<?>[]{iActivityManagerInterface}, new IActivityManagerHandler(iActivityManager));
//將singletonValue對象的值(即上面的iActivityManager對象)設置爲(AMS代理對象的)代理對象的值
mInstance.set(singletonValue, proxy);
複製代碼

利用反射的原理,獲取了ActivityManager.getService()方法返回的對象,而後利用該對象建立了一個代理對象,最後將這個代理對象設置給ActivityManager.getService()方法返回的對象。

3.4 iActivityManager對象的代理對象

public class IActivityManagerHandler implements InvocationHandler {
    private Object iActivityManagerHandler;

    public IActivityManagerHandler(Object iActivityManagerHandler) {
        this.iActivityManagerHandler = iActivityManagerHandler;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("startActivity")) {
            Log.d("ABC", "startActivity 被攔截了");

            Intent rawIntent = null;
            int intentIndex = 0;
            // 找到參數裏面的第一個Intent對象
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    rawIntent = (Intent) args[i];
                    intentIndex = i;
                    break;
                }
            }
            String packageName = MainApplication.getContext().getPackageName();
            //建立一個新的Intent
            Intent newIntent = new Intent();
            if (rawIntent != null) {
                //把啓動的Activity替換爲佔坑Activity
                ComponentName componentName = new ComponentName(packageName, PitActivity.class.getName());
                newIntent.setComponent(componentName);
                //將咱們真正要啓動的Activity保存起來
                newIntent.putExtra(ActivityHookHelper.RAW_INTENT, rawIntent);
                //替換掉intent,已達到欺騙系統的做用
                args[intentIndex] = newIntent;
                Log.d("ABC", "startActivity hook 成功");
            }
            return method.invoke(iActivityManagerHandler, args);

        }
        return method.invoke(iActivityManagerHandler, args);
    }
}
複製代碼

建立一個iActivityManager對象的代理對象,並攔截startActivity方法,在其內部將要啓動的Activity替換成佔坑的Activity。

3.5 攔截建立Activity的方法

在系統檢查完畢後,在建立Activity對象的時候,須要再次攔截建立Activity的方法,並建立真正要啓動的Activity對象。 系統檢查完畢後,會回調ActivityThread裏的scheduleLaunchActivity方法,這個方法發送了一個消息到ActivityThread的內部類H裏,而H類是一個Handler,以下所示:

case LAUNCH_ACTIVITY: {
  Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
  final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

  r.packageInfo = getPackageInfoNoCheck(
          r.activityInfo.applicationInfo, r.compatInfo);
  handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
  Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
複製代碼

而Handler是經過dispatchMessage(Message msg)方法來分發消息的,咱們先來看一下其中邏輯:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
複製代碼

能夠看到若是Handler設置了mCallback對象的話,則會走mCallback對象的handleMessage方法,並且若是mCallback對象的handleMessage方法返回爲true則不會走到下面的handleMessage方法。咱們再看一下H類,能夠看到它並無設置mCallback對象,所以咱們能夠利用反射給它設置一個mCallback對象,並在handleMessage方法中處理LAUNCH_ACTIVITY(msg.what = 100)的狀況:

public static void hookHandler() {
  try {
      Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
      //ActivityThread有一個靜態方法返回了本身,這裏能夠獲取activityThread對象
      Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
      currentActivityThread.setAccessible(true);
      Object activityThread = currentActivityThread.invoke(null);

      Field mH = activityThreadClass.getDeclaredField("mH");
      mH.setAccessible(true);
      Handler mHValue = (Handler) mH.get(activityThread); //獲取activityThread對象中mH變量的值,其中mH的類型是Handler類型

      Field callback = Handler.class.getDeclaredField("mCallback");
      callback.setAccessible(true);
      callback.set(mHValue, new ActivityCallbackHandler(mHValue)); //將一個自定義的Callback設置給Handler
  } catch (Exception e) {
      e.printStackTrace();
  }
}
複製代碼

上面就是利用反射給ActivityThread對象的內部類H設置了一個mCallback對象,其mCallback對象以下:

public class ActivityCallbackHandler implements Handler.Callback {
    private Handler handler;
    private int launchActivity = -1;

    public ActivityCallbackHandler(Handler handler) {
        this.handler = handler;
        try {
            Class<?> innerClass = Class.forName("android.app.ActivityThread$H");
            Field field = innerClass.getDeclaredField("LAUNCH_ACTIVITY");
            field.setAccessible(true);
            launchActivity = (int) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == launchActivity) {
            handleLaunchActivity(msg);
        }
        handler.handleMessage(msg);
        return true;
    }

    private void handleLaunchActivity(Message msg) {
        Object activityClientRecord = msg.obj;
        try {
            Field intent = activityClientRecord.getClass().getDeclaredField("intent");
            intent.setAccessible(true);
            Intent intentValue = (Intent) intent.get(activityClientRecord);
            Intent rawIntent = intentValue.getParcelableExtra(ActivityHookHelper.RAW_INTENT);
            if (rawIntent != null) {
                intentValue.setComponent(rawIntent.getComponent());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
複製代碼

在msg.what = LAUNCH_ACTIVITY時,msg.obj是一個ActivityClientRecord類型,其中有一個字段是intent,該intent存儲的信息就是咱們要啓動Activity時的一些信息,因此在該intent中拿到真正須要啓動的Intent(以前經過putExtra存起來了),而後改變當前的intent的component,將其設置爲真正要啓動的intent的component,已達到建立真正啓動類的結果。

四、總結

通過了上述的分步驟實現,已經能夠將一個插件的Activity啓動起來了。具體的demo能夠在github上下載到: 宿主:github.com/hwldzh/plug… 插件:github.com/hwldzh/plug…

相關文章
相關標籤/搜索