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

栏目: IOS · Android · 发布时间: 5年前

内容简介:Activity启动过程重点是应用进程跟AMS进行通信,处理完成后AMS再交给应用进程继续处理。需要Hook的点就是在AMS调用之前跟MAS调用完成之后。在Activity启动时,通过Instrumentation的checkStartActivityResult去检查启动的Activity的结果,如果插件的Activity未在清单文件中注册,则会抛出ActivityNotFoundException。需要解决的就是如何通过验证?需要解决的是将需要加载的插件Activity创建出来
  • 将特定功能打包为插件,当用户需要使用某个特定功能时,才进行下载并开启
  • 发版更灵活,可随时发版
  • 组织架构更灵活,每个团队负责自身的插件开发
  • 开发中调试速度更快,直接将插件推入手机运行

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方法

参考资料:


以上所述就是小编给大家介绍的《Android进阶(九)Activity插件化和VirtualApk分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Convergence Culture

Convergence Culture

Henry Jenkins / NYU Press / 2006-08-01 / USD 30.00

"Convergence Culture" maps a new territory: where old and new media intersect, where grassroots and corporate media collide, where the power of the media producer, and the power of the consumer intera......一起来看看 《Convergence Culture》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器