本文已授权微信公众号《非著名程序员》原创首发,转载请务必注明出处。

关于VirtualApk

VirtualApk github : https://github.com/didi/VirtualAPK

VirtualAPK wiki : https://github.com/didi/VirtualAPK/wiki

工程介绍

工程结构
  1. CoreLibrary是VirtualApk(以下简称VA)的核心库,因为其中hook了一些被@hide注解的framework类,这些类无法被调用,所以这些无法被调用的类都放在了使用provided(Gradle3.0之后改为了compileOnly)依赖的库工程AndroidStub中,且路径与源码路径一致。
  2. AndroidStub : 无法被直接调用的系统类,仅在编译时获得引用。
  3. app :宿主程序
  4. PluginDemo : 示例插件程序
  5. VirtralApk-gradle-plugin : 自定义的Gradle插件

整个工程结构还是很清晰的,其中CoreLibrary是我们重点关注的对象

初始化 : PluginManager.getInstance(base).init()

VA的使用很简洁,首先在Application中初始化,在需要的地方load下插件APK,之后就可以像同一个工程一样去调用插件中的四大组件。看起来非常简洁,但是简洁不意味着简单。首先看下Application中的初始化。

public class VAApplication extends Application {    @Override    protected void attachBaseContext(Context base) {        super.attachBaseContext(base);        long start = System.currentTimeMillis();        PluginManager.getInstance(base).init();        Log.d("ryg", "use time:" + (System.currentTimeMillis() - start));    }}

从类名及方法名中可以看出PluginManager为全局插件管理单例对象,跟进。

    private PluginManager(Context context) {        Context app = context.getApplicationContext();        if (app == null) {            this.mContext = context;        } else {            this.mContext = ((Application)app).getBaseContext();        }        prepare();    }    private void prepare() {        Systems.sHostContext = getHostContext();        this.hookInstrumentationAndHandler();        if (Build.VERSION.SDK_INT >= 26) {            this.hookAMSForO();        } else {            this.hookSystemServices();        }    }    public void init() {        mComponentsHandler = new ComponentsHandler(this);        RunUtil.getThreadPool().execute(new Runnable() {            @Override            public void run() {                doInWorkThread();            }        });    }    private void doInWorkThread() {    }

在prepare()方法中先hook了Instrumentation和Handler,之后针对8.0之前和之后分别hook了AMS和SystemServices。做过测试的同学肯定对Instrumentation这个类很熟悉,它能实现一些比较niubility的功能,这里不再延伸。先看下怎么hook的这几个类吧。

hook Instrumentation and H

    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);            }            final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);            Object activityThread = ReflectUtil.getActivityThread(this.mContext);            ReflectUtil.setInstrumentation(activityThread, instrumentation);            ReflectUtil.setHandlerCallback(this.mContext, instrumentation);            this.mInstrumentation = instrumentation;        } catch (Exception e) {            e.printStackTrace();        }    }    public static Instrumentation getInstrumentation(Context base) {        if (getActivityThread(base) != null) {            try {                sInstrumentation = (Instrumentation) ReflectUtil.invoke(                        sActivityThread.getClass(), sActivityThread, "getInstrumentation");            } catch (Exception e) {                e.printStackTrace();            }        }        return sInstrumentation;    }    public static Object getActivityThread(Context base) {        if (sActivityThread == null) {            try {                Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");                Object activityThread = null;                try {                    activityThread = ReflectUtil.getField(activityThreadClazz, null, "sCurrentActivityThread");                } catch (Exception e) {                    // ignored                }                if (activityThread == null) {                    activityThread = ((ThreadLocal<?>) ReflectUtil.getField(activityThreadClazz, null, "sThreadLocal")).get();                }                sActivityThread = activityThread;            } catch (Exception e) {                e.printStackTrace();            }        }        return sActivityThread;    }

1.通过反射获取到ActivityThread中的sCurrentActivityThread对象,其实就是获取ActivityThread实例

2.通过反射ActivityThread#getInstrumentation方法获取系统的getInstrumentation对象

3.实例化继承自Instrumentation并且实现了Handler.Callback接口的VAInstrumentation对象

    public static void setInstrumentation(Object activityThread, Instrumentation instrumentation) {        try {            ReflectUtil.setField(activityThread.getClass(), activityThread, "mInstrumentation", instrumentation);        } catch (Exception e) {            e.printStackTrace();        }    }

4.通过反射将VAInstrumentation对象设置给ActivityThread的mInstrumentation属性

    public static void setHandlerCallback(Context base, Handler.Callback callback) {        try {            Object activityThread = getActivityThread(base);            Handler mainHandler = (Handler) ReflectUtil.invoke(activityThread.getClass(), activityThread, "getHandler", (Object[])null);            ReflectUtil.setField(Handler.class, mainHandler, "mCallback", callback);        } catch (Exception e) {            e.printStackTrace();        }    }

5.通过反射ActivityThread#getHandler方法获取ActivityThread中的mH对象,mH对象是H的一个实例,H继承自Handler。系统对四大组件包括生命周期等的调用,本质上都是通过这个叫做mH的Handler对象完成的。

6.因为VAInstrumentation实现了Handler.Callback接口,所以可以通过反射将VAInstrumentation对象设置给mH对象。

以上“六脉神剑”操作完成之后,系统再调用ActivityThread中的mInstrumentation或者mH,首先会进入到VAInstrumentation对应的方法中,这样就完成了对Instrumentation及mH的hook。

ps:这里有个小细节

public class VAInstrumentation extends Instrumentation implements Handler.Callback {    @Override    public boolean handleMessage(Message msg) {        if (msg.what == LAUNCH_ACTIVITY) {            ...        }        return false;    }}

VAInstrumentation#handleMessage直接return false。在Handler#dispatchMessage中

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

VAInstrumentation相当于其中的mCallback对象,VAInstrumentation#handleMessage方法return false之后,不影响系统继续处理。目前实际来看,VAInstrumentation#handleMessage只是处理了插件包中Activity的theme信息。

对于插件化加载Activity的处理上面已经足够了,进一步的操作会在Activity章节分析。

hook IActivityManager(AMS)

    private void hookSystemServices() {        try {            Singleton defaultSingleton = (Singleton) ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault");            IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get());            // Hook IActivityManager from ActivityManagerNative            ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy);            if (defaultSingleton.get() == activityManagerProxy) {                this.mActivityManager = activityManagerProxy;            }        } catch (Exception e) {            e.printStackTrace();        }    }
  1. 通过反射获取ActivityManagerNative的gDefault属性(8.0之后是IActivityManagerSingleton属性,以下gDefault等同IActivityManagerSingleton),gDefault.get()返回的是AMS对象。这个过程可以参考Android Launcher启动应用程序流程源码解析中的“AMS中Binder机制简析”小节,直接搜索即可。

  2. ActivityManagerProxy#newInstance使用动态代理的方式返回了代理AMS的ActivityManagerProxy对象,为了处理Service的声明周期问题(比如启动一个已经alive的service,不再次执行onCreate方法,只再次执行onStartCommend方法),在代理对象中拦截了startService、stopService、bindService、unbindService等操作,不在指定范围内的操作继续执行AMS原有操作。

  3. 通过反射设置gDefault对象的mInstance属性为代理AMS的ActivityManagerProxy对象

ps:这里有点小疑惑:hookAMSForO中没有像hookSystemServices中给mActivityManager属性赋值,那么在LocalService中反射调用service#attach的时候AMS为null,目测会有问题(AMS为null,service启动不会出错,但是不能stop,详见Service源码),已提issue。

一个月后的今天,作者确认是个bug,下个版本即将修复。

加载插件 : PluginManager.getInstance(base).loadPlugin(apk)

    public void loadPlugin(File apk) throws Exception {        ...        LoadedPlugin plugin = LoadedPlugin.create(this, this.mContext, apk);        if (null != plugin) {            this.mPlugins.put(plugin.getPackageName(), plugin);            // try to invoke plugin's application            plugin.invokeApplication();        }    }

加载插件大概分为三步:
1. 解析插件APK所有信息并封装为LoadedPlugin对象
2. 把加载过的插件apk放进缓存Map,key为插件包名,value为封装完成的LoadedPlugin对象。可见VA是通过包名来区分不同的插件,并且重复加载相同包名的插件会报错throw new RuntimeException("plugin has already been loaded : " + mPackageInfo.packageName)
3. 反射调用插件包的Application

下面跟进上述步骤一和步骤三的细节

解析插件APK并封装为LoadedPlugin对象

public final class LoadedPlugin {    public static LoadedPlugin create(PluginManager pluginManager, Context host, File apk) throws Exception {        return new LoadedPlugin(pluginManager, host, apk);    }    LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws PackageParser.PackageParserException {        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;        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];        this.mPackageManager = new PluginPackageManager();        this.mPluginContext = new PluginContext(this);        this.mNativeLibDir = context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE);        this.mResources = createResources(context, apk);        this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());        tryToCopyNativeLib(apk);        // Cache instrumentations        Map instrumentations = new HashMap();        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()]);        // Cache activities        Map activityInfos = new HashMap();        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()]);        // Cache services        Map serviceInfos = new HashMap();        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()]);        // Cache providers        Map providers = new HashMap();        Map providerInfos = new HashMap();        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 receivers = new HashMap();        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()]);    }}

代码有点小长,但看起来非常整洁。一步步的看。

  1. 调用了PackageParserCompat.parsePackage解析插件apk里面的各种信息,parsePackage中调用了系统的PackageParser去解析各种信息,并做了不同版本的兼容处理。我猜这段是APK安装过程中的模板解析代码,不过我没看过这个,暂时就假设这里能获取所有需要的信息吧,so sad…
  2. 区分是否是Constants.COMBINE_RESOURCES模式,构造不同Resources。后续单独分析
  3. 创建每个插件apk的DexClassLoader,区分是否是Constants.COMBINE_CLASSLOADER模式,true(默认),则合并插件APK的dex文件到宿主APK的dex文件中。false,则不合并。并且无论合并还是不合并,LoadedPlugin中都保存了插件APK对应的DexClassLoader。
  4. 获取所有四大组件信息
  5. 动态注册所有静态Receiver

那么多代码主是解析并保存插件APK的所有信息,之后对插件apk的所有操作,通过对应的LoadedPlugin对象操作即可。

两种方式构造Resources

Constants.COMBINE_RESOURCES == true模式(默认)

在这种模式下,会将插件APK的资源和宿主APK的资源合并。这里还要再区分下Android L之前和之后。

由于资源做过分区,则在Android L后直接将插件包的apk地址addAssetPath之后就可以,但是在Android L之前,addAssetPath只是把补丁包加入到资源路径列表里,但是资源的解析其实是在很早的时候就已经执行完了。所以在 Android L 之前是需要想办法构造一个新的AssetManager里mResources才行。VirtualAPK 资源篇

class ResourcesManager {    public static synchronized Resources createResources(Context hostContext, String apk) {        Resources hostResources = hostContext.getResources();        Resources newResources = null;        // 宿主AssetManager        AssetManager assetManager;        try {            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {                // L之前通过反射重新构造AssetManager                assetManager = AssetManager.class.newInstance();                ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", hostContext.getApplicationInfo().sourceDir);            } else {                // L及以后,直接获取AssetManager即可                assetManager = hostResources.getAssets();            }            // 加载插件apk资源            ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", apk);            List pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();            // 加载所有已经load过的插件apk资源            for (LoadedPlugin plugin : pluginList) {                ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", plugin.getLocation());            }            ...            // 更新所有Resources对象都指向newResources            for (LoadedPlugin plugin : pluginList) {                plugin.updateResources(newResources);            }        } catch (Exception e) {            e.printStackTrace();        }        return newResources;    }

Constants.COMBINE_RESOURCES == false模式

    @WorkerThread    private static Resources createResources(Context context, File apk) {        if (Constants.COMBINE_RESOURCES) {            Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());            ResourcesManager.hookResources(context, resources);            return resources;        } else {            Resources hostResources = context.getResources();            AssetManager assetManager = createAssetManager(context, apk);            return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());        }    }    private static AssetManager createAssetManager(Context context, File apk) {        try {            AssetManager am = AssetManager.class.newInstance();            ReflectUtil.invoke(AssetManager.class, am, "addAssetPath", apk.getAbsolutePath());            return am;        } catch (Exception e) {            e.printStackTrace();            return null;        }    }

这种使用插件APK的Resources对象,插件APK的资源由插件Resources对象查找,宿主APK资源由宿主Resources对象查找。这种多Resources方式也是之前分析过的换肤框架AndSkin动态加载外部资源的方式。

两种对dex的处理

Constants.COMBINE_CLASSLOADER = false

    private static ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) {        File dexOutputDir = context.getDir(Constants.OPTIMIZE_DIR, Context.MODE_PRIVATE);        String dexOutputPath = dexOutputDir.getAbsolutePath();        DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);        if (Constants.COMBINE_CLASSLOADER) {            DexUtil.insertDex(loader);        }        return loader;    }

对插件dex不做任何处理,需要load插件类的时候,直接调用这里对应的DexClassLoader即可。

Constants.COMBINE_CLASSLOADER = true(默认)

DexUtil.insertDex(loader)在生成DexClassLoader的基础上,再将插件APK的dex文件添加到到宿主APK dex文件的末尾,大概过程如下

    public static void insertDex(DexClassLoader dexClassLoader) throws Exception {        Object baseDexElements = getDexElements(getPathList(getPathClassLoader()));        Object newDexElements = getDexElements(getPathList(dexClassLoader));        Object allDexElements = combineArray(baseDexElements, newDexElements);        Object pathList = getPathList(getPathClassLoader());        ReflectUtil.setField(pathList.getClass(), pathList, "dexElements", allDexElements);        insertNativeLibrary(dexClassLoader);    }

貌似这里合并dex与否,似乎都不影响loadClass的过程,并没有看到合并dex文件真正起作用的地方。照常理,合并dex之后可以采用单ClassLoader去load插件类,笔者尝试将VAInstrumentation#newActivity中的

Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent)

修改为

Activity activity = mBase.newActivity(cl, targetClassName, intent)

发现确实像预期中一样:合并dex之后,可以用宿主的ClassLoader去加载插件中的类。这里姑且斗胆揣摩作者设置Constants.COMBINE_CLASSLOADER模式是为了切换日后方便切换单ClassLoder和多ClassLoader架构。

有一种热修复方案是采用合并dex,只需要将patch.dex插在在宿主dex前面即可。JVM在patch.dex中加载到目标类(修复过bug的类)之后,不会再去下一个dex中查找,这样就完成了热修复的过程。

反射调用插件包的Application

    public void invokeApplication() {        if (mApplication != null) {            return;        }        // make sure application's callback is run on ui thread.        RunUtil.runOnUiThread(new Runnable() {            @Override            public void run() {                mApplication = makeApplication(false, mPluginManager.getInstrumentation());            }        }, true);    }    private Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {        if (null != this.mApplication) {            return this.mApplication;        }        String appClass = this.mPackage.applicationInfo.className;        if (forceDefaultAppClass || null == appClass) {            appClass = "android.app.Application";        }        try {            this.mApplication = instrumentation.newApplication(this.mClassLoader, appClass, this.getPluginContext());            instrumentation.callApplicationOnCreate(this.mApplication);            return this.mApplication;        } catch (Exception e) {            e.printStackTrace();            return null;        }    }

这个过程比较简单,在newApplication方法中会反射获取Application实例并调用Application#attach,callApplicationOnCreate方法名已经够清楚了…这个过程是模仿系统LoadedApk#makeApplication()方法创建插件Application并调用Application#attach和Application#onCreate。

Activity

前面叙述了一大堆初始化和loadPlugin的逻辑,在启动插件Activity的时候,关键信息有如下几个:

  1. hook Instrumentation
  2. hook H
  3. 两种结构的resource
  4. 两种模式的ClassLoader(对于插件,实际只用了插件的ClassLoader)

VA使用占坑的方式启动插件Activity。为了适配不同的LaunchMode、Theme,VA在CoreLibrary的AndroidManifest.xml中注册了很多占坑Activity。因为VA启动插件Activity表面上没做骚操作(其实骚操作都在hook的类中),所以这里必须要对Activity的启动流程有一些了解,强烈推荐阅读:

Android Launcher启动应用程序流程源码解析

Android Activity启动流程源码解析

由于插件Activity没有在宿主Activity中注册(可以提前注册,不过没必要),所以需要躲过AMS对插件Activity是否存在的验证,VA的做法是在验证前,将插件Activity的名字替换为占坑Activity类名,在真正加载及实例化Activity的时候再讲占坑类名替换为插件Activity,如此,变完成了“偷梁换柱”的过程。

无论调用StartActivity()还是startActivityForResult(),最终调用的都是startActivityForResult()。在startActivityForResult中调用了Instrumentation#execStartActivity。VA hook了这个API。

    public ActivityResult execStartActivity(            Context who, IBinder contextThread, IBinder token, Activity target,            Intent intent, int requestCode, Bundle options) {        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);        }        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};            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;    }

从这里开始,脑子一直要有两条线:1、加载的是宿主Activity,2、加载的是插件Activity。在VAInstrumentation#execStartActivity中依据插件Activity的LaunchMode、Theme找到合适的占坑类,之后反射调用系统的execStartActivity方法。对于启动插件Activity的Intent还会put几个插件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);        }    }

上文虽然替换了插件Activity为占坑类,但load的时候还是要处理插件Activity,而不是占坑类。所以这里只是相当于保存了一下插件Activity信息。

ActivityThread在收到mH(已被hook)的LAUNCH_ACTIVITY消息后,才会真正装载Activity继而调用attach、onCreate。

public final class ActivityThread {    ...    final H mH = new H();    ...        private class H extends Handler {        public static final int LAUNCH_ACTIVITY         = 100;        ...        public void handleMessage(Message msg) {            switch (msg.what) {                case LAUNCH_ACTIVITY: {                    handleLaunchActivity(r, null);                } break;                ...            }        }    }}    private final void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {          ......          // 加载activity,然后调用onCreate,onStart方法        Activity a = performLaunchActivity(r, customIntent);          if (a != null) {            // 调用onResume方法            handleResumeActivity(r.token, false, r.isForward,                    !r.activity.mFinished && !r.startsNotResumed);            ...           }        ...     }     private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {            activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);            if (r.isPersistable()) {                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);            } else {                mInstrumentation.callActivityOnCreate(activity, r.state);            }            ...                }

VA在VAInstrumentation中hook了newActivity、callActivityOnCreate、LAUNCH_ACTIVITY消息

LAUNCH_ACTIVITY消息

LAUNCH_ACTIVITY是第一步,其中判断是否为插件Activity,如果是,则替换Theme。

    public boolean handleMessage(Message msg) {        if (msg.what == LAUNCH_ACTIVITY) {            // ActivityClientRecord r            Object r = msg.obj;            try {                Intent intent = (Intent) ReflectUtil.getField(r.getClass(), r, "intent");                intent.setExtrasClassLoader(VAInstrumentation.class.getClassLoader());                ActivityInfo activityInfo = (ActivityInfo) ReflectUtil.getField(r.getClass(), r, "activityInfo");                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));                        activityInfo.theme = theme;                    }                }            } catch (Exception e) {                e.printStackTrace();            }        }        return false;    }

newActivity

VA对插件Activity的骚操作就在这里

    @Override    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {        try {            cl.loadClass(className);        } catch (ClassNotFoundException e) {            LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);            String targetClassName = PluginUtil.getTargetActivity(intent);            Log.i(TAG, String.format("newActivity[%s : %s]", className, targetClassName));            if (targetClassName != null) {                Log.i(TAG, String.format("newActivity[%s : %s]", className, targetClassName));                Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);                // 单ClassLoder架构//                Activity activity = mBase.newActivity(cl, 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);    }

先尝试加载类,如果是宿主Activity,那么肯定能找到,直接交给系统处理就好,如果是插件Activity,这里的className为占坑类名,那么会发生ClassNotFoundException。也就意味着,如果发生ClassNotFoundException,那么直接对插件Activity进行处理就好。因为之前在Ientent中put了Constants.KEY_IS_PLUGIN,这里其实还可以从Intent中取value判断。在catch代码中先从Intent中依据之前put进去的key取出插件Activity类名,再替换占坑类名为为插件Activity类名,如此、便完成了一出精彩的“偷梁换柱”。

Service

在初始化中通过动态代理的方式hook了AMS并设置为ActivityManagerProxy对象,之后对AMS任何方法的调用都会先进入到ActivityManagerProxy#invoke方法。在invoke方法中可以拦截指定操作。

VA启动插件Service的套路

通过动态代理hook了AMS,并拦截启动/停止Service的API。首先判断是否为宿主Service,如果是,那么不做任何操作;如果不是,hook API实现自己的操作。具体体现为启动插件Service之前,依据是否是多进程模式,启动代理LocalService/RemoteService,在代理Service中实现对目标Service声明周期的调用。下面是一些具体细节的跟进。

framework对startService的简单执行流程如下

ContextImpl#startService->ContextImpl#startServiceCommon->AMS#startService/AMS#bindService

这个hook的入口比较靠前,只hook了几个启动/停止Service相关的API。

启动代理Service

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        if ("startService".equals(method.getName())) {            try {                return startService(proxy, method, args);            } catch (Throwable e) {                Log.e(TAG, "Start service error", e);            }        } else if ("bindService".equals(method.getName())) {            try {                return bindService(proxy, method, args);            } catch (Throwable e) {                Log.e(TAG, "Stop Service error", e);            }        }         ...        return method.invoke(this.mActivityManager, args);    }

这里以sample中bindService为例。在ActivityManagerProxy(以下简称AMP)中,hook了bindService API。

    private Object bindService(Object proxy, Method method, Object[] args) throws Throwable {        Intent target = (Intent) args[2];        ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);        if (null == resolveInfo || null == resolveInfo.serviceInfo) {            // is host service            return method.invoke(this.mActivityManager, args);        }        Bundle bundle = new Bundle();        PluginUtil.putBinder(bundle, "sc", (IBinder) args[4]);        startDelegateServiceForTarget(target, resolveInfo.serviceInfo, bundle, RemoteService.EXTRA_COMMAND_BIND_SERVICE);        mPluginManager.getComponentsHandler().remberIServiceConnection((IBinder) args[4], target);        return 1;    }

首先查询是否为宿主Service,对于宿主Service什么都不用做,直接调用并return即可。args[4]为ServiceConnection对象,先put进Bundle。之后便是启动代理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();        // start delegate service to run plugin service inside        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);        intent.putExtra(RemoteService.EXTRA_COMMAND, command);        intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);        if (extras != null) {            intent.putExtras(extras);        }        return intent;    }

startDelegateServiceForTarget中首先替换插件Service为代理Service,之后启动代理Service。在AMP#wrapperTargetIntent方法中,首先依据进程名是否相同决定启动LocalService/RemoteService,之后重新创建了一个Intent,为了后面启动插件Service,然后把之前的Intent当做一个value传递给了重新创建的Intent,最后启动代理Service。VA对Service的“偷梁换柱”就发生在这里,之后对插件Service的启动包括生命周期的管理都放在了代理Service中。跟进。

sample中bindService的BookManagerService另开了一个进程,所以对应启动的是RemoteService。因为RemoteService继承LocalService,所以startService启动RemoteService,对应的生命周期为

RemoteService#onCreate -> LocalService#onCreate -> RemoteService#onCreate#onStartCommand -> LocalService#onStartCommand

新开了一个名为“package:daemon”进程,在RemoteService#onCreate#onStartCommand只是做了一个检测插件APK是否被load的操作,主要内容还是在LocalService中。

public class LocalService extends Service {    @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);        int command = intent.getIntExtra(EXTRA_COMMAND, 0);        if (null == target || command <= 0) {            return START_STICKY;        }        ComponentName component = target.getComponent();        LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);        // ClassNotFoundException when unmarshalling in Android 5.1        target.setExtrasClassLoader(plugin.getClassLoader());        switch (command) {            case EXTRA_COMMAND_BIND_SERVICE: {                ActivityThread mainThread = (ActivityThread)ReflectUtil.getActivityThread(getBaseContext());                IApplicationThread appThread = mainThread.getApplicationThread();                Service service = null;                if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {                    service = this.mPluginManager.getComponentsHandler().getService(component);                } else {                    try {                        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();                        attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);                        service.onCreate();                        this.mPluginManager.getComponentsHandler().rememberService(component, service);                    } catch (Throwable t) {                        t.printStackTrace();                    }                }                try {                    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;                ...            }        return START_STICKY;    } }

首先从Intent中取出之前set进去的一些对象,名为target的Intent对象才是启动插件Service的Intent。之后依据command,去做相应的操作,这里command为bindService对应的EXTRA_COMMAND_BIND_SERVICE。

插件Service的加载及生命周期的处理

在EXTRA_COMMAND_BIND_SERVICE分支中,首先判断插件Service是否存活,这个判断的逻辑比较简单,ComponentsHandler有个缓存的Map,启动插件Service的时候往这个Map添加,停止插件Service的时候移除。判断下这个Map对象指定key是否能查询到value即可。这里分两种情况:

  1. 插件Service已经存活,获取Service对象即可。
  2. 插件Service没有存活,需要手动load serviceClass,然后调用newInstance获取插件Service实例,最后调用Service#attach(反射)和Service#onCreate。

现在无论之前插件Service是否存活,现在都完成了Service调用attach onCreate这么一个操作。之后手动调用Service#onBind,最后调用ServiceConnection#onServiceConnected即可。通过代理Service的一通操作,就实现了插件Service的加载及生命周期管理。打完收工~

BroadCast

将所有静态广播修改为动态注册,由此带来的一些限制详见wiki。

ContentProvider

// TODO 工作中貌似就没用过ContentProvider,不想看了….

更多相关文章

  1. android 调用.net 的webservice .
  2. Android(安卓)- Looper / Handler / Message
  3. 让gradle常驻内存,加快项目构建速度
  4. Andorid读写全局配置文件的方法
  5. Android(安卓)自定义View——自定义一个文本选择框
  6. Android之内存溢出(Out Of Memory)的总结
  7. Android中WebViewClient与WebChromClient两个类的区别
  8. android拍照显示缩略图
  9. Android(安卓)FrameWork——PackageManager框架

随机推荐

  1. Android(安卓)Interface Definition Lang
  2. Android(安卓)之 EditText属性用法介绍
  3. android:screenOrientation 强制屏幕方向
  4. Android(安卓)JUnit单元测试
  5. Android数据库操作的两种方式
  6. android 中限制EditText 最大输入字符数
  7. Google Maps API Key申请方法及地址
  8. 2010.11.27———android 展示网络上的图
  9. Android中的存储路径之外部存储
  10. android studio 3.6.0 绑定视图新特性