Android进阶(九)Activity插件化和VirtualApk分析

1,633 阅读7分钟

一、插件化特点

1、优点

  • 将特定功能打包为插件,当用户需要使用某个特定功能时,才进行下载并开启
  • 发版更灵活,可随时发版
  • 组织架构更灵活,每个团队负责自身的插件开发
  • 开发中调试速度更快,直接将插件推入手机运行

2、局限性

  • 稳定性不够,通过hook方式,存在兼容问题
  • 插件化开发如果改动过大可能就需要发版

二、Activity启动Hook点分析

Activity启动过程重点是应用进程跟AMS进行通信,处理完成后AMS再交给应用进程继续处理。需要Hook的点就是在AMS调用之前跟MAS调用完成之后。

1、execStartActivity

#Instrumentation
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, String resultWho,
        Intent intent, int requestCode, Bundle options, UserHandle user) {
    .....
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        //调用AMS继续启动Activity
        int result = ActivityManager.getService()
            .startActivityAsUser(whoThread, who.getBasePackageName(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token, resultWho,
                    requestCode, 0, null, options, user.getIdentifier());
        //检查启动Activity的结果
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}

在Activity启动时,通过Instrumentation的checkStartActivityResult去检查启动的Activity的结果,如果插件的Activity未在清单文件中注册,则会抛出ActivityNotFoundException。需要解决的就是如何通过验证?

2、ActivityThread

#ActivityThread
private class H extends Handler {
...
   public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                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);
                    //调用了performLaunchActivity方法
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                ...
              }
...
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {       
    ...
    //创建要启动Activity的上下文环境
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        //用类加载器来创建Activity的实例
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);//1
      ...
    } catch (Exception e) {
      ...
    }
  ...
    return activity;
}

需要解决的是将需要加载的插件Activity创建出来

三、VirtualApk原理分析

VirtualApk

1、初始化

在Application进行初始化操作

PluginManager.getInstance(base).init();

在初始化操作时Hook了Instrumentation、ActivityThread的mH类的Callback、IActivityManager、DataBindingUtil

#PluginManager
protected PluginManager(Context context) {
    ......
    hookCurrentProcess();
}

protected void hookCurrentProcess() {
    hookInstrumentationAndHandler();
    hookSystemServices();
    hookDataBindingUtil();
}

(1)hookInstrumentationAndHandler

#PluginManager
protected void hookInstrumentationAndHandler() {
    try {
        ActivityThread activityThread = ActivityThread.currentActivityThread();
        Instrumentation baseInstrumentation = activityThread.getInstrumentation();

        final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation);
        
        Reflector.with(activityThread).field("mInstrumentation").set(instrumentation);
        Handler mainHandler = Reflector.with(activityThread).method("getHandler").call();
        Reflector.with(mainHandler).field("mCallback").set(instrumentation);
        this.mInstrumentation = instrumentation;
        Log.d(TAG, "hookInstrumentationAndHandler succeed : " + mInstrumentation);
    } catch (Exception e) {
        Log.w(TAG, e);
    }
}

public class VAInstrumentation extends Instrumentation implements Handler.Callback {......}
  • 创建VAInstrumentation,是Instrumentation的子类,实现了Handler.Callback方法
  • 通过反射将VAInstrumentation设置给ActivityThread, hook住了Instrumentation
  • 通过反射设置了Handler.Callback,拦截了ActivityThread的H的Callback

(2)hookSystemServices

protected void hookSystemServices() {
    try {
        Singleton<IActivityManager> defaultSingleton;
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            defaultSingleton = Reflector.on(ActivityManager.class).field("IActivityManagerSingleton").get();
        } else {
            defaultSingleton = Reflector.on(ActivityManagerNative.class).field("gDefault").get();
        }
        IActivityManager origin = defaultSingleton.get();
        IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class },
            createActivityManagerProxy(origin));

        // Hook IActivityManager from ActivityManagerNative
        Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy);

        if (defaultSingleton.get() == activityManagerProxy) {
            this.mActivityManager = activityManagerProxy;
            Log.d(TAG, "hookSystemServices succeed : " + mActivityManager);
        }
    } catch (Exception e) {
        Log.w(TAG, e);
    }
}
public class ActivityManagerProxy implements InvocationHandler {......}
  • 创建了IActivityManager的动态代理对象ActivityManagerProxy
  • 通过反射来替换掉AMS的代理对象IActivityManager,来接管Activity启动等操作

2、插件加载

(1)loadPlugin
一般会将某个功能插件生成jar或者apk文件,然后交给主工程通过PluginManager的loadPlugin进行加载

#PluginManager
public void loadPlugin(File apk) throws Exception {
    ......
    //将插件文件转换为一个LoadedPlugin对象
    LoadedPlugin plugin = createLoadedPlugin(apk);
    
    if (null == plugin) {
        throw new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath());
    }
    //将插件LoadedPlugin存入
    this.mPlugins.put(plugin.getPackageName(), plugin);
    ......
}

(2)构建LoadedPlugin对象

#LoadedPlugin
public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
    this.mPluginManager = pluginManager;
    this.mHostContext = context;
    this.mLocation = apk.getAbsolutePath();
    this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
    this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;
    //创建PackageInfo对象
    this.mPackageInfo = new PackageInfo();
    this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;
    this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();
    ......
    this.mPackageManager = createPluginPackageManager();
    this.mPluginContext = createPluginContext(null);
    this.mNativeLibDir = getDir(context, Constants.NATIVE_DIR);
    this.mPackage.applicationInfo.nativeLibraryDir = this.mNativeLibDir.getAbsolutePath();
    //创建Resource
    this.mResources = createResources(context, getPackageName(), apk);
    //创建ClassLoader
    this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
    //拷贝so
    tryToCopyNativeLib(apk);

    // 缓存instrumentations
    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()]);

    // 缓存activities
    Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();
    for (PackageParser.Activity activity : this.mPackage.activities) {
        activity.info.metaData = activity.metaData;
        activityInfos.put(activity.getComponentName(), activity.info);
    }
    this.mActivityInfos = Collections.unmodifiableMap(activityInfos);
    this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]);

    // 缓存services
    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()]);

    // 缓存providers
    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()]);

    // Register broadcast receivers dynamically
    Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
    for (PackageParser.Activity receiver : this.mPackage.receivers) {
        receivers.put(receiver.getComponentName(), receiver.info);

        BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());
        for (PackageParser.ActivityIntentInfo aii : receiver.intents) {
            this.mHostContext.registerReceiver(br, aii);
        }
    }
    this.mReceiverInfos = Collections.unmodifiableMap(receivers);
    this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]);

    // try to invoke plugin's application
    invokeApplication();
}

创建PackageInfo、Resouces、ClassLoader对象,存储Instrumentation、Activity、Service、Content Provider等信息
(3)创建ClassLoader对象

#LoadedPlugin
protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception {
    File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR);
    String dexOutputPath = dexOutputDir.getAbsolutePath();
    //创建DexClassLoader用来加载插件
    DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);

    if (Constants.COMBINE_CLASSLOADER) {
        DexUtil.insertDex(loader, parent, libsDir);
    }

    return loader;
}
#DexUtil
public static void insertDex(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception {
    Object baseDexElements = getDexElements(getPathList(baseClassLoader));
    Object newDexElements = getDexElements(getPathList(dexClassLoader));
    //将宿主自身的dex文件和插件的dex文件合并
    Object allDexElements = combineArray(baseDexElements, newDexElements);
    Object pathList = getPathList(baseClassLoader);
    //通过反射将合并后的dex文件赋值给dexElements
    Reflector.with(pathList).field("dexElements").set(allDexElements);    
    insertNativeLibrary(dexClassLoader, baseClassLoader, nativeLibsDir);
}
  • 创建DexClassLoader对象
  • 将宿主和插件Dex文件合并,并通过反射赋值给dexElements
  • 然后插件中的Activity等文件就可以被加载了

3、定义占位Activity

<activity android:exported="false" android:name="com.didi.virtualapk.delegate.StubActivity" android:launchMode="standard"/>
<!-- Stub Activities -->
<activity android:exported="false" android:name=".A$1" android:launchMode="standard"/>
<activity android:exported="false" android:name=".A$2" android:launchMode="standard"
    android:theme="@android:style/Theme.Translucent" />

......
<!-- Local Service running in main process -->
<service android:exported="false" android:name="com.didi.virtualapk.delegate.LocalService" />

<!-- Daemon Service running in child process -->
<service android:exported="false" android:name="com.didi.virtualapk.delegate.RemoteService" android:process=":daemon">
    <intent-filter>
        <action android:name="${applicationId}.intent.ACTION_DAEMON_SERVICE" />
    </intent-filter>
</service>

<provider
    android:exported="false"
    android:name="com.didi.virtualapk.delegate.RemoteContentProvider"
    android:authorities="${applicationId}.VirtualAPK.Provider"
    android:process=":daemon" />

在清单文件中定了各种启动模式的占位Activity、Service、ContentProvider

4、将插件Activity替换为占位的Activity

(1)启动Activity时会走到VAInstrumentation的execStartActivity方法

#VAInstrumentation
@Override
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) {
    //替换为占坑的Activity
    injectIntent(intent);
    //继续走Instrumentation的execStartActivity方法
    return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode);
}

protected void injectIntent(Intent intent) {
    //通过intent去匹配PluginManager中Activity的坑位
    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()));
        // resolve intent with Stub Activity if needed
        this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
    }
}

(2)将插件Activity的相关信息进行存储

public void markIntentIfNeeded(Intent intent) {
    if (intent.getComponent() == null) {
        return;
    }

    String targetPackageName = intent.getComponent().getPackageName();
    String targetClassName = intent.getComponent().getClassName();
    // search map and return specific launchmode stub activity
    if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {
        intent.putExtra(Constants.KEY_IS_PLUGIN, true);
        //将目标插件包名和类路径先存起来,方便后期替换回来
        intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
        intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
        dispatchStubActivity(intent);
    }
}

(3)将插件Activity替换为占位的Activity进行启动

private void dispatchStubActivity(Intent intent) {
    ComponentName component = intent.getComponent();
    String targetClassName = intent.getComponent().getClassName();
    LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent);
    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);
    //通过launchMode等信息找到合适的占位Activity
    String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);
    Log.i(TAG, String.format("dispatchStubActivity,[%s -> %s]", targetClassName, stubActivity));
    intent.setClassName(mContext, stubActivity);
}

接下来拿着占位的Activity继续跟AMS进行通信

5、替换回目标插件的Activity

(1)VAInstrumentation接收到ApplicationThread发送的消息

#VAInstrumentation
@Override
public boolean handleMessage(Message msg) {
    if (msg.what == LAUNCH_ACTIVITY) {
        // ActivityClientRecord r
        Object r = msg.obj;
        try {
            Reflector reflector = Reflector.with(r);
            Intent intent = reflector.field("intent").get();
            intent.setExtrasClassLoader(mPluginManager.getHostContext().getClassLoader());
            //获取ActivityInfo
            ActivityInfo activityInfo = reflector.field("activityInfo").get();       
            if (PluginUtil.isIntentFromPlugin(intent)) {
                int theme = PluginUtil.getTheme(mPluginManager.getHostContext(), intent);
                if (theme != 0) {
                    Log.i(TAG, "resolve theme, current theme:" + activityInfo.theme + "  after :0x" + Integer.toHexString(theme));
                    //更换thme
                    activityInfo.theme = theme;
                }
            }
        } catch (Exception e) {
            Log.w(TAG, e);
        }
    }

    return false;
}

(2)通过VAInstrumentation的newActivity创建一个Activity对象

#VAInstrumentation
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    try {
        cl.loadClass(className);
        Log.i(TAG, String.format("newActivity[%s]", className));
        
    } catch (ClassNotFoundException e) {
        //占位的Activity不存在,进入catch处理
        ComponentName component = PluginUtil.getComponent(intent);
        
        if (component == null) {
            return newActivity(mBase.newActivity(cl, className, intent));
        }
        //获取目标插件的Activity
        String targetClassName = component.getClassName();    
        LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);

        if (plugin == null) {
            // Not found then goto stub activity.
            boolean debuggable = false;
            try {
                Context context = this.mPluginManager.getHostContext();
                debuggable = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
            } catch (Throwable ex) {
    
            }

            if (debuggable) {
                throw new ActivityNotFoundException("error intent: " + intent.toURI());
            }
            
            Log.i(TAG, "Not found. starting the stub activity: " + StubActivity.class);
            return newActivity(mBase.newActivity(cl, StubActivity.class.getName(), intent));
        }
        //通过Instrumentation的newActivity实现目标Activity的创建
        Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
        activity.setIntent(intent);

        // for 4.1+
        Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources());

        return newActivity(activity);
    }

    return newActivity(mBase.newActivity(cl, className, intent));
}

获取目标Activity的ComponentName

#PluginUtil
public static ComponentName getComponent(Intent intent) {
    if (intent == null) {
        return null;
    }
    if (isIntentFromPlugin(intent)) {
        return new ComponentName(intent.getStringExtra(Constants.KEY_TARGET_PACKAGE),
            intent.getStringExtra(Constants.KEY_TARGET_ACTIVITY));
    }
    
    return intent.getComponent();
}

获取之前存储到占位Activity的相关参数信息,并返回ComponentName,继续执行Activity启动操作

6、callActivityOnCreate

@Override
public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {
    injectActivity(activity);
    mBase.callActivityOnCreate(activity, icicle, persistentState);
}

protected void injectActivity(Activity activity) {
    final Intent intent = activity.getIntent();
    if (PluginUtil.isIntentFromPlugin(intent)) {
        Context base = activity.getBaseContext();
        try {
            LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
            Reflector.with(base).field("mResources").set(plugin.getResources());
            Reflector reflector = Reflector.with(activity);
            reflector.field("mBase").set(plugin.createPluginContext(activity.getBaseContext()));
            reflector.field("mApplication").set(plugin.getApplication());

            // set screenOrientation
            ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));
            if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
                activity.setRequestedOrientation(activityInfo.screenOrientation);
            }

            // for native activity
            ComponentName component = PluginUtil.getComponent(intent);
            Intent wrapperIntent = new Intent(intent);
            wrapperIntent.setClassName(component.getPackageName(), component.getClassName());
            activity.setIntent(wrapperIntent);
            
        } catch (Exception e) {
            Log.w(TAG, e);
        }
    }
}

设置了修改了mResources、mBase(Context)、mApplication对象,最终执行了Activity的onCreate方法

7、Activity插件化总结

(1)初始化时Hook住Instrumentation、ActivityThread.mH的Callback回调 (2)在宿主工程的清单文件中定义占位Activity (3)加载插件时,将插件dex文件和宿主dex文件合并,反射赋值给PathList的dexElements,以便被ClassLoader加载 (4)启动目标Activity过程中,VAInstrumentation将目标Activity替换为占位Activity,并将目标Activity信息作为参数存储。从而通过对Activity的校验,继而继续与AMS进行通信。 (5)AMS处理完成后,传递到ApplicationThread中,通过VAInstrumentation拦截到该消息,将占位Activity替换为目标Activity,并将目标Activity进行创建,继而进行后续操作。 (6)设置mResources、mBase(Context)、mApplication对象,最终调用到了Activity的onCreate方法

参考资料: