關於做者java
郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。android
文章目錄git
更多Android開源框架源碼分析文章請參見Android open framwork analysis。程序員
從2012年開始,插件化技術獲得了很大的發展,究其緣由,主要是由於隨着業務的增加,主工程變得愈來愈難以維護,並且隨着公司業務的擴展,原來的主應用也逐漸分化了多個子應用,研發團隊也由 一個變成多個,可是子應用仍然須要主應用的流量入口優點,種種業務場景的需求,極大地促進了插件化技術的發展。github
就目前而言,主流的插件化框架有如下幾種:緩存
從上圖對比能夠看出,有着不錯的表現的重點是360的DroidPlugin框架和滴滴的VirtualAPK框架,這兩家公司的業務類型不一樣,致使了這兩套框架的側重點也有所不一樣,具體說來:微信
也就是說若是咱們須要去加載一個內部業務模塊,而且這個業務模塊很難從主工程中徹底解耦,那麼咱們會優先選擇VirtualAPK這種方案。架構
A powerful and lightweight plugin framework for Androidapp
官方網站:https://github.com/didi/VirtualAPK框架
源碼版本:0.9.1
按照國際慣例,在分析VirtualAPK的源碼實現以前,先吹一波它的優勢😎。以下所示:
完善的功能
優秀的兼容性
極低的入侵性
👉 注:吹了這麼多,其實這套框架仍是有瑕疵的,具體的問題,在分析源碼的時候咱們會講。
要理解一套框架,首先須要從總體去把握它,理解它的構造和層次劃分,而後逐個去分析,VirtualAPK的總體架構圖以下圖所示:
總體的源碼結構也並不複雜,以下圖所示:
在使用VirtualAPK以前,咱們須要多VirtualAPK進行初始化,以下所示:
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
PluginManager.getInstance(base).init();
}
複製代碼
咱們來分析了一下VirtualAPK初始化的過程當中作了哪些事情,以下所示:
public class PluginManager {
private PluginManager(Context context) {
Context app = context.getApplicationContext();
// 獲取Context
if (app == null) {
this.mContext = context;
} else {
this.mContext = ((Application)app).getBaseContext();
}
//初始化
prepare();
}
private void prepare() {
Systems.sHostContext = getHostContext();
//1. hook對象Instrumentation。
this.hookInstrumentationAndHandler();
//2. 根據不一樣的Android版本分別從ActivityManagerNative中Hook對象IActivityManager。
if (Build.VERSION.SDK_INT >= 26) {
this.hookAMSForO();
} else {
this.hookSystemServices();
}
}
}
}
複製代碼
能夠發現VirtualAPK在初始化的時候主要hook了兩個對象,以下所示:
首先是Instrumentation對象。爲何要hook這個對象呢?🤔這是由於Instrumentation對象在啓動Activity會有一套校驗過程,其中一條就是檢查Activity有沒有在Manifest文件中進行註冊,以下所示:
public class Instrumentation {
public static void checkStartActivityResult(int res, Object intent) {
if (res >= ActivityManager.START_SUCCESS) {
return;
}
switch (res) {
case ActivityManager.START_INTENT_NOT_RESOLVED:
case ActivityManager.START_CLASS_NOT_FOUND:
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+ "; have you declared this activity in your AndroidManifest.xml?");
throw new ActivityNotFoundException(
"No Activity found to handle " + intent);
case ActivityManager.START_PERMISSION_DENIED:
throw new SecurityException("Not allowed to start activity "
+ intent);
case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
throw new AndroidRuntimeException(
"FORWARD_RESULT_FLAG used while also requesting a result");
case ActivityManager.START_NOT_ACTIVITY:
throw new IllegalArgumentException(
"PendingIntent is not an activity");
case ActivityManager.START_NOT_VOICE_COMPATIBLE:
throw new SecurityException(
"Starting under voice control not allowed for: " + intent);
case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY:
// Fail silently for this case so we don't break current apps.
// TODO(b/22929608): Instead of failing silently or throwing an exception,
// we should properly position the activity in the stack (i.e. behind all current
// user activity/task) and not change the positioning of stacks.
Log.e(TAG,
"Not allowed to start background user activity that shouldn't be displayed"
+ " for all users. Failing silently...");
break;
default:
throw new AndroidRuntimeException("Unknown error code "
+ res + " when starting " + intent);
}
}
}
複製代碼
這些異常信息多半咱們都很熟悉,其中have you declared this activity in your AndroidManifest.xml,就是沒有註冊Activity所報的異常,hook對象Instrumentation,而後替換掉裏面相應 的方法,來達到繞過檢查的目的,咱們來看看hook的流程,以下所示:
public class PluginManager {
private void hookInstrumentationAndHandler() {
try {
Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);
if (baseInstrumentation.getClass().getName().contains("lbe")) {
// reject executing in paralell space, for example, lbe.
System.exit(0);
}
//自定義的VAInstrumentation重寫了newActivity()等邏輯。Instrumentation對象也被保存了下載
final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);
//獲取ctivityThread實例
Object activityThread = ReflectUtil.getActivityThread(this.mContext);
//用自定義的VAInstrumentation重對象替換掉ActivityThread裏的Instrumentation對象
ReflectUtil.setInstrumentation(activityThread, instrumentation);
ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
this.mInstrumentation = instrumentation;
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
在文章03Android組件框架:Android視圖容器Activity中,咱們提到 過Instrumentation對象用來監控應用與系統的交互行爲,Activity的建立也是在Instrumentation對象裏完成的,之因此要hook這個對象就是爲了修改Activity建立邏輯。
這裏用自定義的VAInstrumentation重對象替換掉ActivityThread裏的Instrumentation對象,這樣當系統啓動Activity調用Instrumentation的newActivity()方法的時候就會調用自定義的VAInstrumentation 裏面的newActivity()方法。
public class PluginManager {
// Android API 26及其以上
private void hookAMSForO() {
try {
Singleton<IActivityManager> defaultSingleton = (Singleton<IActivityManager>) ReflectUtil.getField(ActivityManager.class, null, "IActivityManagerSingleton");
IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get());
ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy);
} catch (Exception e) {
e.printStackTrace();
}
}
// Android API 26如下
private void hookSystemServices() {
try {
Singleton<IActivityManager> defaultSingleton = (Singleton<IActivityManager>) ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault");
IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get());
//從ActivityManagerNative中Hook對象IActivityManager
ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy);
if (defaultSingleton.get() == activityManagerProxy) {
this.mActivityManager = activityManagerProxy;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
除了Instrumentation對象,它還根據不一樣的Android版本分別從ActivityManagerNative中Hook對象IActivityManager,那麼這個IActivityManager對象是什麼呢?🤔
咱們以前在文章02Android組件框架:Android組件管理者ActivityManager中 曾經聊到過它是ActivityManagerService的代理對象,經過它能夠和ActivityManagerService進行IPC通訊,並請求它完成一些組件管理上的工做。咱們平時調用的startActivity()、startService()、bindService()等組件調用的方法 最終都是調用ActivityManagerService裏的方法來完成的。
以上即是VIrtualAPK的初始化流程,咱們接着來看VIrtualAPK是如何去加載一個APK文件的。👇
VirtualAPK對於加載的APK文件沒有額外的約束,只須要添加VirtualAPK的插件進行編譯,以下所示:
apply plugin: 'com.didi.virtualapk.plugin'
virtualApk {
packageId = 0x6f // The package id of Resources.
targetHost='source/host/app' // The path of application module in host project.
applyHostMapping = true // [Optional] Default value is true.
}
複製代碼
而後就能夠調用PluginManager直接加載編譯完成的APK,被加載的APK在PluginManager裏是一個LoadedPlugin對象,VirtualAPK經過這些LoadedPlugin對象來管理APK,這些APK也能夠 想在手機裏直接安裝的App同樣運行。
String pluginPath = Environment.getExternalStorageDirectory().getAbsolutePath().concat("/Test.apk");
File plugin = new File(pluginPath);
PluginManager.getInstance(base).loadPlugin(plugin);
複製代碼
APK的加載流程以下圖所示:
咱們能夠看到上面調用loadPlugin()方法去加載一個APK,咱們來看一下它的實現。
public class PluginManager {
public void loadPlugin(File apk) throws Exception {
if (null == apk) {
throw new IllegalArgumentException("error : apk is null.");
}
if (!apk.exists()) {
throw new FileNotFoundException(apk.getAbsolutePath());
}
// 1. 加載APK文件
LoadedPlugin plugin = LoadedPlugin.create(this, this.mContext, apk);
if (null != plugin) {
this.mPlugins.put(plugin.getPackageName(), plugin);
// try to invoke plugin's application
// 2. 嘗試調用APK
plugin.invokeApplication();
} else {
throw new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath());
}
}
}
複製代碼
這裏調用了LoadedPlugin的create()方法去構建一個LoadedPlugin對象,因此的初始化操做都是在LoadedPlugin的構造方法裏完成的,以下所示:
public final class LoadedPlugin {
LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws PackageParser.PackageParserException {
this.mPluginManager = pluginManager;
this.mHostContext = context;
this.mLocation = apk.getAbsolutePath();
// 1. 調用PackageParser解析APK,獲取PackageParser.Package對象。
this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;
// 2. 構建PackageInfo對象。
this.mPackageInfo = new PackageInfo();
this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;
this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();
this.mPackageInfo.signatures = this.mPackage.mSignatures;
this.mPackageInfo.packageName = this.mPackage.packageName;
if (pluginManager.getLoadedPlugin(mPackageInfo.packageName) != null) {
throw new RuntimeException("plugin has already been loaded : " + mPackageInfo.packageName);
}
this.mPackageInfo.versionCode = this.mPackage.mVersionCode;
this.mPackageInfo.versionName = this.mPackage.mVersionName;
this.mPackageInfo.permissions = new PermissionInfo[0];
// 3. 構建PluginPackageManager對象。
this.mPackageManager = new PluginPackageManager();
this.mPluginContext = new PluginContext(this);
this.mNativeLibDir = context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE);
// 4. 構建Resouces對象。
this.mResources = createResources(context, apk);
// 5. 構建ClassLoader對象。
this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
// 6. 拷貝so庫。
tryToCopyNativeLib(apk);
// 7. 緩存Instrumentation對象。
Map<ComponentName, InstrumentationInfo> instrumentations = new HashMap<ComponentName, InstrumentationInfo>();
for (PackageParser.Instrumentation instrumentation : this.mPackage.instrumentation) {
instrumentations.put(instrumentation.getComponentName(), instrumentation.info);
}
this.mInstrumentationInfos = Collections.unmodifiableMap(instrumentations);
this.mPackageInfo.instrumentation = instrumentations.values().toArray(new InstrumentationInfo[instrumentations.size()]);
// 8. 緩存APK裏的Activity信息。
Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity activity : this.mPackage.activities) {
activityInfos.put(activity.getComponentName(), activity.info);
}
this.mActivityInfos = Collections.unmodifiableMap(activityInfos);
this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]);
// 9. 緩存APK裏的Service信息。
Map<ComponentName, ServiceInfo> serviceInfos = new HashMap<ComponentName, ServiceInfo>();
for (PackageParser.Service service : this.mPackage.services) {
serviceInfos.put(service.getComponentName(), service.info);
}
this.mServiceInfos = Collections.unmodifiableMap(serviceInfos);
this.mPackageInfo.services = serviceInfos.values().toArray(new ServiceInfo[serviceInfos.size()]);
// 10. 緩存APK裏的Content Provider信息。
Map<String, ProviderInfo> providers = new HashMap<String, ProviderInfo>();
Map<ComponentName, ProviderInfo> providerInfos = new HashMap<ComponentName, ProviderInfo>();
for (PackageParser.Provider provider : this.mPackage.providers) {
providers.put(provider.info.authority, provider.info);
providerInfos.put(provider.getComponentName(), provider.info);
}
this.mProviders = Collections.unmodifiableMap(providers);
this.mProviderInfos = Collections.unmodifiableMap(providerInfos);
this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]);
// 11. 將靜態的廣播轉爲動態的。
Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity receiver : this.mPackage.receivers) {
receivers.put(receiver.getComponentName(), receiver.info);
try {
BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());
for (PackageParser.ActivityIntentInfo aii : receiver.intents) {
this.mHostContext.registerReceiver(br, aii);
}
} catch (Exception e) {
e.printStackTrace();
}
}
this.mReceiverInfos = Collections.unmodifiableMap(receivers);
this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]);
}
}
複製代碼
整個LoadedPlugin對象構建的過程就是去解析APK裏的組件信息,並緩存起來,具體說來:
咱們接着來看沒有在宿主App的Manifest裏註冊的四大組件時如何被啓動起來的。👇
前面咱們說過在初始化VirtualAPK的過程當中使用自定義的VAInstrumentation在ActivityThread中替換掉了原生的Instrumentation對象,來達到hook到Activity啓動流程的目的,繞開Instrumentation 啓動Activity的校驗流程。
那麼VirtualAPK是如何繞過系統校驗的呢?🤔
Virtual是採用佔坑的方式來繞過校驗的,它在庫裏的Manifest文件裏定義了佔坑的文件,以下所示:
<application>
<!-- Stub Activities -->
<activity android:name=".A$1" android:launchMode="standard"/>
<activity android:name=".A$2" android:launchMode="standard" android:theme="@android:style/Theme.Translucent" />
<!-- Stub Activities -->
<activity android:name=".B$1" android:launchMode="singleTop"/>
<activity android:name=".B$2" android:launchMode="singleTop"/>
<activity android:name=".B$3" android:launchMode="singleTop"/>
<activity android:name=".B$4" android:launchMode="singleTop"/>
<activity android:name=".B$5" android:launchMode="singleTop"/>
<activity android:name=".B$6" android:launchMode="singleTop"/>
<activity android:name=".B$7" android:launchMode="singleTop"/>
<activity android:name=".B$8" android:launchMode="singleTop"/>
<!-- Stub Activities -->
<activity android:name=".C$1" android:launchMode="singleTask"/>
<activity android:name=".C$2" android:launchMode="singleTask"/>
<activity android:name=".C$3" android:launchMode="singleTask"/>
<activity android:name=".C$4" android:launchMode="singleTask"/>
<activity android:name=".C$5" android:launchMode="singleTask"/>
<activity android:name=".C$6" android:launchMode="singleTask"/>
<activity android:name=".C$7" android:launchMode="singleTask"/>
<activity android:name=".C$8" android:launchMode="singleTask"/>
<!-- Stub Activities -->
<activity android:name=".D$1" android:launchMode="singleInstance"/>
<activity android:name=".D$2" android:launchMode="singleInstance"/>
<activity android:name=".D$3" android:launchMode="singleInstance"/>
<activity android:name=".D$4" android:launchMode="singleInstance"/>
<activity android:name=".D$5" android:launchMode="singleInstance"/>
<activity android:name=".D$6" android:launchMode="singleInstance"/>
<activity android:name=".D$7" android:launchMode="singleInstance"/>
<activity android:name=".D$8" android:launchMode="singleInstance"/>
</application>
複製代碼
A、B、C、D分別表明standard、singleTop、singleTask和singleInstance四種啓動模式。
VirtualAPK製造一些假的Activity替身在Manifest文件提早進行註冊佔坑,在啓動真正的Activity時候,再將Activity填到坑裏,以完成啓動Activity。咱們來看看具體的是實現流程:
以上四個方法都是啓動Activity的過程當中必經的四個方法。
public class VAInstrumentation extends Instrumentation implements Handler.Callback {
public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
//1. 將隱式Intent轉換爲顯式Intent,Virtual是經過intent.setClassName(this, "com.guoxiaoxing.plugin.MainActivity");這種
//方式來啓動Activity的,這裏將包名封裝成真正的ComponentName對象。
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
// null component is an implicitly intent
if (intent.getComponent() != null) {
Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
intent.getComponent().getClassName()));
//2. 用註冊過的StubActivity替換掉真實的Activity以便繞過校驗。
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
//3. 生成了佔坑的StubActivity的Intent。調用realExecStartActivity()方法繼續執行Activity的啓動,藉此繞過校驗。
ActivityResult result = realExecStartActivity(who, contextThread, token, target,
intent, requestCode, options);
return result;
}
private ActivityResult realExecStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
ActivityResult result = null;
try {
Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class,
int.class, Bundle.class};
// 用佔坑的StubActivity的Intent去啓動Activity,藉此繞過校驗。
result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase,
"execStartActivity", parameterTypes,
who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
if (e.getCause() instanceof ActivityNotFoundException) {
throw (ActivityNotFoundException) e.getCause();
}
e.printStackTrace();
}
return result;
}
}
複製代碼
該方法主要作了三件事情,以下所示:
其中重點就在於註冊過的StubActivity替換掉真實的Activity以便繞過檢測,咱們來看看它的實現,以下所示:
public class ComponentsHandler {
public void markIntentIfNeeded(Intent intent) {
if (intent.getComponent() == null) {
return;
}
// 包名
String targetPackageName = intent.getComponent().getPackageName();
// 類名
String targetClassName = intent.getComponent().getClassName();
// 搜索對應啓動模式的佔坑StubActivity
if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {
// 作一個插件的標記
intent.putExtra(Constants.KEY_IS_PLUGIN, true);
// 存包名
intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
// 存類名,之因此存儲這些信息是爲了之後獲取真正的Activity的Intent信息去啓動真正的Activity。
intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
// 查找StubActivity
dispatchStubActivity(intent);
}
}
private void dispatchStubActivity(Intent intent) {
ComponentName component = intent.getComponent();
String targetClassName = intent.getComponent().getClassName();
// 獲取intent對應的LoadedPlugin對象
LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent);
// 根據ComponentName信息獲取對應的ActivityInfo
ActivityInfo info = loadedPlugin.getActivityInfo(component);
if (info == null) {
throw new RuntimeException("can not find " + component);
}
//啓動模式
int launchMode = info.launchMode;
Resources.Theme themeObj = loadedPlugin.getResources().newTheme();
// 替換掉對應的主題
themeObj.applyStyle(info.theme, true);
// 獲取對應的StubActivity
String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);
Log.i(TAG, String.format("dispatchStubActivity,[%s -> %s]", targetClassName, stubActivity));
// 設置StubActivity的全路徑名
intent.setClassName(mContext, stubActivity);
}
}
複製代碼
咱們來看看具體是如何查詢StubActivity的,以下所示:
class StubActivityInfo {
// 標準模式Activity的最大個數
public static final int MAX_COUNT_STANDARD = 1;
// 棧頂複用模式Activity的最大個數
public static final int MAX_COUNT_SINGLETOP = 8;
// 棧內複用模式Activity的最大個數
public static final int MAX_COUNT_SINGLETASK = 8;
// 單例模式Activity的最大個數
public static final int MAX_COUNT_SINGLEINSTANCE = 8;
//那些佔坑的Activity的全路徑名
public static final String corePackage = "com.didi.virtualapk.core";
public static final String STUB_ACTIVITY_STANDARD = "%s.A$%d";
public static final String STUB_ACTIVITY_SINGLETOP = "%s.B$%d";
public static final String STUB_ACTIVITY_SINGLETASK = "%s.C$%d";
public static final String STUB_ACTIVITY_SINGLEINSTANCE = "%s.D$%d";
public final int usedStandardStubActivity = 1;
public int usedSingleTopStubActivity = 0;
public int usedSingleTaskStubActivity = 0;
public int usedSingleInstanceStubActivity = 0;
private HashMap<String, String> mCachedStubActivity = new HashMap<>();
public String getStubActivity(String className, int launchMode, Theme theme) {
// 1. 先從緩存中查找StuActivity。
String stubActivity= mCachedStubActivity.get(className);
if (stubActivity != null) {
return stubActivity;
}
TypedArray array = theme.obtainStyledAttributes(new int[]{
android.R.attr.windowIsTranslucent,
android.R.attr.windowBackground
});
boolean windowIsTranslucent = array.getBoolean(0, false);
array.recycle();
if (Constants.DEBUG) {
Log.d("StubActivityInfo", "getStubActivity, is transparent theme ? " + windowIsTranslucent);
}
// 標準啓動模式:com.didi.virtualapk.core.A$1
stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);
switch (launchMode) {
case ActivityInfo.LAUNCH_MULTIPLE: {
// 標準啓動模式:com.didi.virtualapk.core.$1,每次自增1
stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);
if (windowIsTranslucent) {
stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, 2);
}
break;
}
case ActivityInfo.LAUNCH_SINGLE_TOP: {
// 棧頂複用模式:com.didi.virtualapk.core.$,每次自增1,範圍從1 - 8.
usedSingleTopStubActivity = usedSingleTopStubActivity % MAX_COUNT_SINGLETOP + 1;
stubActivity = String.format(STUB_ACTIVITY_SINGLETOP, corePackage, usedSingleTopStubActivity);
break;
}
case ActivityInfo.LAUNCH_SINGLE_TASK: {
// 棧內模式:com.didi.virtualapk.core.C$,每次自增1,範圍從1 - 8.
usedSingleTaskStubActivity = usedSingleTaskStubActivity % MAX_COUNT_SINGLETASK + 1;
stubActivity = String.format(STUB_ACTIVITY_SINGLETASK, corePackage, usedSingleTaskStubActivity);
break;
}
case ActivityInfo.LAUNCH_SINGLE_INSTANCE: {
// 單例模式模式:com.didi.virtualapk.core.D$,每次自增1,範圍從1 - 8.
usedSingleInstanceStubActivity = usedSingleInstanceStubActivity % MAX_COUNT_SINGLEINSTANCE + 1;
stubActivity = String.format(STUB_ACTIVITY_SINGLEINSTANCE, corePackage, usedSingleInstanceStubActivity);
break;
}
default:break;
}
// 將查找到的Activity放到緩存中
mCachedStubActivity.put(className, stubActivity);
return stubActivity;
}
}
複製代碼
能夠看着這裏邊完成了佔坑StubActivity的查找,以下所示:
既然這裏爲了染過檢驗把要啓動的Activity變成了佔坑的StubActivity。那麼真正啓動Activity的時候就要再次變回來,咱們接着分析。
public class VAInstrumentation extends Instrumentation implements Handler.Callback {
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try {
cl.loadClass(className);
} catch (ClassNotFoundException e) {
// 1. 根據Intent查找對應的LoadedPlugin。
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
// 2. 從Intent中提取真正的targetClassName。
String targetClassName = PluginUtil.getTargetActivity(intent);
Log.i(TAG, String.format("newActivity[%s : %s]", className, targetClassName));
if (targetClassName != null) {
// 3. 這個mBase是咱們上面保存的原生的Instrumentation對象,調用它的newActivity()方法去完成Activity的構建,這
// 至關因而一個動態代理模式。getClassLoader()是本身構建的一個DexClassLoader類,專門用來加載APK裏面的類。
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
try {
// for 4.1+
ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());
} catch (Exception ignored) {
// ignored.
}
return activity;
}
}
return mBase.newActivity(cl, className, intent);
}
}
複製代碼
VAInstrumentation覆寫了Instrumentation的newActivity方法,這個方法主要作了三件事情:
經過上面的分析,總體的思路就很是清晰了,以下所示:
提早在Manifest文件裏註冊多個佔坑的StubActivity,校驗階段,將Intent裏的className替換成StubActivity,並保存原有的Activity信息,藉此經過校驗。啓動階段,再 從Intent中取出真正的Activity信息,調用Instrumentation的newActivity()方法繼續執行Activity的啓動。
總體的思路仍是挺機制的👍,固然佔坑思想很早就有Android的同窗提出來了,這也是實現插件化的思路的一種。介紹完了Activity的啓動流程,咱們接着來看Service的啓動流程。👇
Service的啓動採用動態代理AMS,攔截Service的相關操做請求,而後轉給ActivityManagerProxy處理,咱們來看一看。
public class ActivityManagerProxy implements InvocationHandler {
private Object startService(Object proxy, Method method, Object[] args) throws Throwable {
// 獲取IApplicationThread對象。
IApplicationThread appThread = (IApplicationThread) args[0];
// 獲取跳轉Intent。
Intent target = (Intent) args[1];
// 檢查Service的信息
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
if (null == resolveInfo || null == resolveInfo.serviceInfo) {
// is host service
return method.invoke(this.mActivityManager, args);
}
return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);
}
private ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
return mPluginManager.getHostContext().startService(wrapperIntent);
}
private Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
// fill in service with ComponentName
target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();
// 判斷交給LocalService處理仍是交給RemoteService處理,LocalService和RemoteService分別對應是否
// 在新進程中啓動Service。
boolean local = PluginUtil.isLocalService(serviceInfo);
Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class;
Intent intent = new Intent();
intent.setClass(mPluginManager.getHostContext(), delegate);
intent.putExtra(RemoteService.EXTRA_TARGET, target);
// 保存一下這個command,分別對應不一樣的操做。
intent.putExtra(RemoteService.EXTRA_COMMAND, command);
intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);
if (extras != null) {
intent.putExtras(extras);
}
return intent;
}
}
複製代碼
因此本質上講,不論是啓動、綁定仍是關閉Intent,最終都是調用LocalService或者RemoteService裏的onStartCommand()方法進行操做請求的分發。
LocalService和RemoteService都已經在VirtualAPK的Manifest文件裏進行了註冊,以下所示:
<application>
<!-- Local Service running in main process -->
<service android:name="com.didi.virtualapk.delegate.LocalService" />
<!-- Daemon Service running in child process -->
<service android:name="com.didi.virtualapk.delegate.RemoteService" android:process=":daemon">
<intent-filter>
<action android:name="${applicationId}.intent.ACTION_DAEMON_SERVICE" />
</intent-filter>
</service>
</application>
複製代碼
咱們接着來看看它們倆的具體實現。
public class LocalService extends Service {
private static final String TAG = "LocalService";
// 插件APK裏的目標Service
public static final String EXTRA_TARGET = "target";
public static final String EXTRA_COMMAND = "command";
public static final String EXTRA_PLUGIN_LOCATION = "plugin_location";
public static final int EXTRA_COMMAND_START_SERVICE = 1;
public static final int EXTRA_COMMAND_STOP_SERVICE = 2;
public static final int EXTRA_COMMAND_BIND_SERVICE = 3;
public static final int EXTRA_COMMAND_UNBIND_SERVICE = 4;
private PluginManager mPluginManager;
@Override
public IBinder onBind(Intent intent) {
return new Binder();
}
@Override
public void onCreate() {
super.onCreate();
// 獲取PluginManager單例
mPluginManager = PluginManager.getInstance(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (null == intent || !intent.hasExtra(EXTRA_TARGET) || !intent.hasExtra(EXTRA_COMMAND)) {
return START_STICKY;
}
Intent target = intent.getParcelableExtra(EXTRA_TARGET);
// 獲取command信息
int command = intent.getIntExtra(EXTRA_COMMAND, 0);
if (null == target || command <= 0) {
return START_STICKY;
}
// 獲取組件信息
ComponentName component = target.getComponent();
// 根據組件信息獲取對應的LoadedPlugin
LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);
// ClassNotFoundException when unmarshalling in Android 5.1
target.setExtrasClassLoader(plugin.getClassLoader());
switch (command) {
// 啓動Service
case EXTRA_COMMAND_START_SERVICE: {
// 獲取ActivityThread對象。
ActivityThread mainThread = (ActivityThread)ReflectUtil.getActivityThread(getBaseContext());
// 獲取IApplicationThread對象。
IApplicationThread appThread = mainThread.getApplicationThread();
Service service;
if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
// 嘗試從ComponentsHandler裏獲取Service、
service = this.mPluginManager.getComponentsHandler().getService(component);
} else {
try {
// 調用DexClassLoader加載Service類。
service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();
Application app = plugin.getApplication();
IBinder token = appThread.asBinder();
Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class);
IActivityManager am = mPluginManager.getActivityManager();
// 調用attch()方法,綁定應用上下文。
attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);
// 回調Service的onCreate()方法。
service.onCreate();
// 插入Service
this.mPluginManager.getComponentsHandler().rememberService(component, service);
} catch (Throwable t) {
return START_STICKY;
}
}
// 回調Service的onStartCommand()方法。
service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());
break;
}
// 綁定服務
case EXTRA_COMMAND_BIND_SERVICE: {
ActivityThread mainThread = (ActivityThread)ReflectUtil.getActivityThread(getBaseContext());
IApplicationThread appThread = mainThread.getApplicationThread();
Service service = null;
if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
// 嘗試從ComponentsHandler裏獲取Service、
service = this.mPluginManager.getComponentsHandler().getService(component);
} else {
try {
// 調用DexClassLoader加載Service類。
service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();
Application app = plugin.getApplication();
IBinder token = appThread.asBinder();
Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class);
IActivityManager am = mPluginManager.getActivityManager();
// 調用attch()方法,綁定應用上下文。
attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);
// 回調Service的onCreate()方法。
service.onCreate();
// 插入Service
this.mPluginManager.getComponentsHandler().rememberService(component, service);
} catch (Throwable t) {
t.printStackTrace();
}
}
try {
// 回調Service的onBind()方法。
IBinder binder = service.onBind(target);
IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), "sc");
IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection);
if (Build.VERSION.SDK_INT >= 26) {
ReflectUtil.invokeNoException(IServiceConnection.class, iServiceConnection, "connected",
new Class[]{ComponentName.class, IBinder.class, boolean.class},
new Object[]{component, binder, false});
} else {
iServiceConnection.connected(component, binder);
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
// 中止服務
case EXTRA_COMMAND_STOP_SERVICE: {
// 從ComponentsHandler移除Service的記錄
Service service = this.mPluginManager.getComponentsHandler().forgetService(component);
if (null != service) {
try {
// 回調Service的onDestroy()方法
service.onDestroy();
} catch (Exception e) {
Log.e(TAG, "Unable to stop service " + service + ": " + e.toString());
}
} else {
Log.i(TAG, component + " not found");
}
break;
}
case EXTRA_COMMAND_UNBIND_SERVICE: {
// 從ComponentsHandler移除Service的記錄
Service service = this.mPluginManager.getComponentsHandler().forgetService(component);
if (null != service) {
try {
// 回調Service的onUnbind()方法
service.onUnbind(target);
// 回調Service的onDestroy()方法
service.onDestroy();
} catch (Exception e) {
Log.e(TAG, "Unable to unbind service " + service + ": " + e.toString());
}
} else {
Log.i(TAG, component + " not found");
}
break;
}
}
return START_STICKY;
}
}
複製代碼
你能夠發現整個類的實現,就至關於重寫了Service啓動的一部分流程,主要包括上下文綁定和一些生命週期方法的回調,這一塊的具體內容能夠參照咱們以前寫的文章05Android組件框架:Android後臺服務Service 除此以外,它還使用了ComponentsHandler來管理Service,畢竟咱們只在Manifest中註冊了一個LocalService,ComponentsHandler主要用來插入和刪除Service以及管理ServiceConnection。這樣 就即使只註冊了一個LocalService,也能夠啓動插件APK裏的多個Service了。
咱們接着來看看RemoteService,👇
public class RemoteService extends LocalService {
@Override
public IBinder onBind(Intent intent) {
// onBind()方法返回空,說明不能被綁定。
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
return super.onStartCommand(intent, flags, startId);
}
Intent target = intent.getParcelableExtra(EXTRA_TARGET);
if (target != null) {
String pluginLocation = intent.getStringExtra(EXTRA_PLUGIN_LOCATION);
ComponentName component = target.getComponent();
LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(component);
if (plugin == null && pluginLocation != null) {
try {
// 比LocalService多了一個從文件加載APK插件的操做
PluginManager.getInstance(this).loadPlugin(new File(pluginLocation));
} catch (Exception e) {
e.printStackTrace();
}
}
}
return super.onStartCommand(intent, flags, startId);
}
}
複製代碼
RemoteService繼承與於LocalService,它們倆的區別就在於onBind()和onStartCommand()方法的實現上,以下所示:
Broadcast Receiver就比較簡單了,直接將靜態廣播轉爲動態廣播,免去註冊的過程。
// 將靜態的廣播轉爲動態的。
Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity receiver : this.mPackage.receivers) {
receivers.put(receiver.getComponentName(), receiver.info);
try {
BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());
for (PackageParser.ActivityIntentInfo aii : receiver.intents) {
this.mHostContext.registerReceiver(br, aii);
}
} catch (Exception e) {
e.printStackTrace();
}
}
複製代碼
VirtualAPK經過動態代理IContentProvider,攔截ContentProvider的相關操做請求,而後轉給PluginContentResolver來處理。IContentProvider對象的hook其實是在PluginManager裏 完成的,以下所示:
public class PluginManager {
private void hookIContentProviderAsNeeded() {
// 拿到佔坑的Content Provider,而後主動去調用它的call()方法,call()方法
// 會調用RemoteContentProvider的getContentProvider構建一個RemoteContentProvider。
Uri uri = Uri.parse(PluginContentResolver.getUri(mContext));
mContext.getContentResolver().call(uri, "wakeup", null, null);
try {
Field authority = null;
Field mProvider = null;
// 獲取ActivityThread對象
ActivityThread activityThread = (ActivityThread) ReflectUtil.getActivityThread(mContext);
// 獲取ContentProvider Map
Map mProviderMap = (Map) ReflectUtil.getField(activityThread.getClass(), activityThread, "mProviderMap");
Iterator iter = mProviderMap.entrySet().iterator();
// 變量查詢相應的ContentProvider
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
String auth;
if (key instanceof String) {
auth = (String) key;
} else {
if (authority == null) {
authority = key.getClass().getDeclaredField("authority");
authority.setAccessible(true);
}
auth = (String) authority.get(key);
}
if (auth.equals(PluginContentResolver.getAuthority(mContext))) {
if (mProvider == null) {
mProvider = val.getClass().getDeclaredField("mProvider");
mProvider.setAccessible(true);
}
IContentProvider rawProvider = (IContentProvider) mProvider.get(val);
// 獲取對應的IContentProvider
IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
mIContentProvider = proxy;
Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
ContentProvider也在Manifest文件裏進行了佔坑註冊,以下所示:
<application>
<provider android:name="com.didi.virtualapk.delegate.RemoteContentProvider" android:authorities="${applicationId}.VirtualAPK.Provider" android:process=":daemon" />
</application>
複製代碼
獲取到IContentProvider對象後,就能夠對它進行動態代理,攔截它裏面的操做,例如:query、insert、update、delete等操,在這些操做裏吧用戶調用的URI緩存佔坑Provider的URI,再 把原來的URI做爲參數拼接在佔坑Provider後面便可。這個替換和拼接的過程是由PluginContentResolver的wrapperUri()方法來完成的,以下所示:
public class PluginContentResolver extends ContentResolver {
@Keep
public static Uri wrapperUri(LoadedPlugin loadedPlugin, Uri pluginUri) {
String pkg = loadedPlugin.getPackageName();
String pluginUriString = Uri.encode(pluginUri.toString());
StringBuilder builder = new StringBuilder(PluginContentResolver.getUri(loadedPlugin.getHostContext()));
//先加入佔坑Provider的URI
builder.append("/?plugin=" + loadedPlugin.getLocation());
// 再將目標URI和packageName拼接上去
builder.append("&pkg=" + pkg);
builder.append("&uri=" + pluginUriString);
Uri wrapperUri = Uri.parse(builder.toString());
return wrapperUri;
}
}
複製代碼
能夠發現上面佔坑的Provider是RemoteContentProvider,它繼承ContentProvider,至關因而它的代理類,以下所示:
public class RemoteContentProvider extends ContentProvider {
private ContentProvider getContentProvider(final Uri uri) {
final PluginManager pluginManager = PluginManager.getInstance(getContext());
Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));
final String auth = pluginUri.getAuthority();
// 1. 嘗試從緩存中獲取ContentProvider。
ContentProvider cachedProvider = sCachedProviders.get(auth);
if (cachedProvider != null) {
return cachedProvider;
}
synchronized (sCachedProviders) {
// 2. 獲取LoadedPlugin對象。
LoadedPlugin plugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
if (plugin == null) {
try {
pluginManager.loadPlugin(new File(uri.getQueryParameter(KEY_PLUGIN)));
} catch (Exception e) {
e.printStackTrace();
}
}
// 3. 從LoadedPlugin對象裏獲取Provider相關信息ProviderInfo。
final ProviderInfo providerInfo = pluginManager.resolveContentProvider(auth, 0);
if (providerInfo != null) {
RunUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
LoadedPlugin loadedPlugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
// 4. 利用反射建立ContentProvider對象。
ContentProvider contentProvider = (ContentProvider) Class.forName(providerInfo.name).newInstance();
contentProvider.attachInfo(loadedPlugin.getPluginContext(), providerInfo);
// 5. 將ContentProvider對象存入緩存中。
sCachedProviders.put(auth, contentProvider);
} catch (Exception e) {
e.printStackTrace();
}
}
}, true);
return sCachedProviders.get(auth);
}
}
return null;
}
}
複製代碼
整個構建ContentProvider對象的流程以下所示:
咱們再接着來看看RemoteContentProvider裏面的增刪改查操做,以下所示:
public class RemoteContentProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// 1. 經過傳入的URI生成一個新的Provider。
ContentProvider provider = getContentProvider(uri);
// 2. 拿到目標URI。
Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));
if (provider != null) {
// 3. 執行最終的查詢操做。
return provider.query(pluginUri, projection, selection, selectionArgs, sortOrder);
}
return null;
}
}
複製代碼
query()方法的邏輯也十分簡單,以下所示:
好了,四大組件的啓動流程都分析完了,咱們再來總結一下:
以上即是整個VrtualAPK框架的原理分析。