0%

深入理解VirtualApk插件化

什么是插件化?

插件化包括宿主和插件2部分。把需要实现的模块或功能独立的提取出来,比如相册,每个模块相当于一个独立的apk,这个apk就是一个插件。有一个加载相册插件apk的App叫做宿主,宿主是一个普通的App,但包含了加载插件apk的功能,使得插件apk正常运行。插件化可以减少宿主App的规模,可以把插件apk放到服务器上,当需要使用到相应的功能时再去加载相应的插件apk。宿主和插件都有各自的包名和版本号,包名可以区别宿主和各个插件,版本号可用于宿主和插件的升级。

简单介绍下VirtualAPK

VirtualAPK对插件没有额外的约束,原生的apk即可作为插件。插件工程编译生成apk后,即可通过宿主App加载,每个插件apk被加载后,都会在宿主中创建一个单独的LoadedPlugin对象。如下图所示,通过这些LoadedPlugin对象,VirtualAPK就可以管理插件并赋予插件新的意义,使其可以像手机中安装过的App一样运行。

一、从代码入口分析

代码分析基于0.9.8版本

compile 'com.didi.virtualapk:core:0.9.8'

VirtualApk初始化插件引擎需要在Application的attachBaseContext进行

public class VAApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        PluginManager.getInstance(base).init();
    }

}

getInstalce方法用单例设计模式创建PluginManager对象,使用了synchronized关键字同步锁

public static PluginManager getInstance(Context base) {
    if (sInstance == null) {
        synchronized (PluginManager.class) {
            if (sInstance == null) {
                sInstance = createInstance(base);
            }
        }
    }

    return sInstance;
}

创建PluginManager对象后,接着会调用hookCurrentProcess方法

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

到这里可以知道,hook了Instrumentation、Handler、SystemServices、DataBindingUtil。下面逐个分析下这四个hook流程,这里分析流程的目的是要了解hook是怎么回事,hook了是要干嘛呢。

二、hookInstrumentationAndHandler

先看看hookInstrumentationAndHandler的代码

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);
    }
}

第一行ActivityThread activityThread = ActivityThread.currentActivityThread();这里会不会让你想起什么来,ActivityThread是一个@hide类,为什么可以直接使用@hide类呢?可以按点击去试试看,会跳到AndroidStub模块下的ActivityThread。AndroidStub定义了许多路径一样的类但是里面都是实现抛出RuntimeException.为了尽量避免使用反射浪费性能,使用了AndroidStub模块来欺骗编译器。欺骗编译器需要查看Android framework层源码,定义和原码中一摸一样的方法,实现抛出RuntimeException。CoreLibrary使用provided依赖AndroidStub,provided依赖是不打包依赖包,而是运行时提供,所以成功欺骗了编辑器,用来提高了性能。

public final class ActivityThread {

    public static ActivityThread currentActivityThread() {
        throw new RuntimeException("Stub!");
    }
    ...
}

CoreLibrary使用provided依赖AndroidStub

final String projectAndroidStub = ':AndroidStub'
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    provided project(projectAndroidStub)
}

现在回到hook instrumentation上,使用了Reflector反射器直接把framework层的ActivityThread类下mInstrumentation变量变成了VAInstrumentation,使得VAInstrumentation起到了代理作用。

final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation);
Reflector.with(activityThread).field("mInstrumentation").set(instrumentation);

代理的目的就是先让VAInstrumentation处理自己的逻辑,处理完后再给framework层的Instrumentation处理,以实现达到欺骗系统的作用,校验的是宿主占坑Activity,启动插件中的Activity。

public class VAInstrumentation extends Instrumentation implements Handler.Callback {
...
protected Instrumentation mBase;

@Override
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) {
    injectIntent(intent);
    return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options);
}

protected void injectIntent(Intent intent) {
    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);
    }
}
...
}

现在来看看hook Handler,

Handler mainHandler = Reflector.with(activityThread).method("getHandler").call();
Reflector.with(mainHandler).field("mCallback").set(instrumentation);

看看ActivityThread源码下getHandler()是什么?原来是H类。

public final class ActivityThread extends ClientTransactionHandler {
    ...
    final H mH = new H();
    final Handler getHandler() {
        return mH;
    }
    ...
}

那为什么可以直接把H类的mCallback直接替换成功VAInstrumentation实现的Handler.Callback不会引起其他问题,导致无法执行H类的handleMessage呢?看看Handler源码就知道了。new H()的时候mCallback为null,使用代理VAInstrumentation后mCallback.handleMessage(msg)会一直返回false,会继续执行handleMessage方法。起到了代理H类的效果,先执行VAInstrumentation的handleMessage,再执行H类的handleMessage。

public class Handler {
    ...
    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
        ...
        mCallback = callback;
        mAsynchronous = async;
    }

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

三、hookSystemServices

hook SystemServices的时候,先从ActivityManager.class或ActivityManagerNative.class中反射获取Singleton对象,再使用ActivityManagerProxy动态代理动态代理获取一个 IActivityManager.

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);
    }
}

ActivityManager.class下的IActivityManagerSingleton

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

Reflector.with(defaultSingleton).field(“mInstance”).set(activityManagerProxy);这里的mInstance其实是Singleton类的mInstance变量。

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

    protected abstract T create();

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

三、hook DataBindingUtil

分析完上面2个原理,到这里应该比较清晰hook是什么回事了,就是在执行framework层代码之前,先执行Proxy代理的代码,来实现一些意想不到的效果。hook DataBindingUtil可以自己看看代码分析下。

protected void hookDataBindingUtil() {
    Reflector.QuietReflector reflector = Reflector.QuietReflector.on("android.databinding.DataBindingUtil").field("sMapper");
    Object old = reflector.get();
    if (old != null) {
        try {
            Callback callback = Reflector.on("android.databinding.DataBinderMapperProxy").constructor().newInstance();
            reflector.set(callback);
            addCallback(callback);
            Log.d(TAG, "hookDataBindingUtil succeed : " + callback);
        } catch (Reflector.ReflectedException e) {
            Log.w(TAG, e);
        }
    }
}

四、支持插件中的Activity

这里需要了解Activity的启动流程,如果你还没有了解可以点击这里。前面我们了解了hook Instrumentation,看看VAInstrumentation到底做了什么。启动Activity时会执行execStartActivity,而在执行execStartActivity之前做了injectIntent,是为了绕过系统校验是否在宿主的AndroidManifest.xml中注册过插件中的Activity。也就是要达到插件中的Activity不用在宿主中注册就可以启动。

@Override
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) {
    injectIntent(intent);
    return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options);
}

protected void injectIntent(Intent intent) {
    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);
    }
}

transformIntentToExplicitAsNeeded作用是把隐式启动的Activity转化为显式启动。下图可以知道把Intent { act=com.didi.virtualapk.plugin.BookManagerActivity }转化为显式Intent { act=com.didi.virtualapk.plugin.BookManagerActivity cmp=com.didi.virtualapk.demo/.aidl.BookManagerActivity },ComponentName由null变成包含BookManagerActivity数据的ComponentName。

markIntentIfNeeded其实就是记录了下插件的信息包括isPlugin、插件package、要启动的插件Activity类,记录的目的是绕过系统校验后,再把这些信息取出来,启动真正要启动的插件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);
    }
}

这里要看看dispatchStubActivity把targetActivity替换成stubActivity的过程。

图中可以看到stubActivity是com.didi.virtualapk.core.A$1,这是CoreLibrary/src/main/AndroidManifest.xml下提前注册占坑Activity。包含了四种启动模式,不同的启动模式取不同的占坑Activity,达到支持插件Activity的四种启动模式。

<!-- 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" />

<!-- Stub Activities -->
<activity android:exported="false" android:name=".B$1" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$2" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$3" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$4" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$5" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$6" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$7" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$8" android:launchMode="singleTop"/>

<!-- Stub Activities -->
<activity android:exported="false" android:name=".C$1" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$2" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$3" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$4" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$5" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$6" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$7" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$8" android:launchMode="singleTask"/>

<!-- Stub Activities -->
<activity android:exported="false" android:name=".D$1" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$2" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$3" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$4" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$5" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$6" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$7" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$8" android:launchMode="singleInstance"/>

如果想了解如何取对应设计模式的占坑,可以查看StubActivityInfo类,如取值stubActivity为com.didi.virtualapk.core.A$1

public static final String STUB_ACTIVITY_STANDARD = "%s.A$%d";
stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);

injectIntent绕过校验后,会执行newActivity,在classloader加载占坑类com.didi.virtualapk.core.A$1时,由于只是占坑,不存在这个类,会走ClassNotFoundException异常逻辑。

@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) {
        ComponentName component = PluginUtil.getComponent(intent);
        
        if (component == null) {
            return newActivity(mBase.newActivity(cl, className, intent));
        }

        String targetClassName = component.getClassName();
        Log.i(TAG, String.format("newActivity[%s : %s/%s]", className, component.getPackageName(), targetClassName));

        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));
        }
        
        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));
}

通过this.mPluginManager.getLoadedPlugin(component)获取已经加载的插件,并重新设置了恢复了要启动的插件Activity。就这样callActivityOnCreate的时候也是调用要启动的插件Activity。

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

五、支持插件中的Service

前面已经了解了hookSystemServices的过程,是使用了动态代理生成代理类。

IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class },
    createActivityManagerProxy(origin));

再看看createActivityManagerProxy做了什么

protected ActivityManagerProxy createActivityManagerProxy(IActivityManager origin) throws Exception {
    return new ActivityManagerProxy(this, origin);
}

既然用了动态代理,那就看看ActivityManagerProxy的invoke方法

@Override
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 ("stopService".equals(method.getName())) {
        try {
            return stopService(proxy, method, args);
        } catch (Throwable e) {
            Log.e(TAG, "Stop Service error", e);
        }
    }
    ...
}

在执行Service生命周期等关键方法时都做了相应的代理处理,看看startService

protected Object startService(Object proxy, Method method, Object[] args) throws Throwable {
    IApplicationThread appThread = (IApplicationThread) args[0];
    Intent target = (Intent) args[1];
    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);
}

resolveService判断是不是宿主工程的Service,宿主Service走原来的逻辑,插件Service就走startDelegateServiceForTarget

public ResolveInfo resolveService(Intent intent, int flags) {
    for (LoadedPlugin plugin : this.mPlugins.values()) {
        ResolveInfo resolveInfo = plugin.resolveService(intent, flags);
        if (null != resolveInfo) {
            return resolveInfo;
        }
    }

    return null;
}

startDelegateServiceForTarget里面执行wrapperTargetIntent,

protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
    Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
    return mPluginManager.getHostContext().startService(wrapperIntent);
}

关键点local ? LocalService.class : RemoteService.class,明确了需要哪个Service做代理类。

protected 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;
}

RemoteService继承了LocalService,RemoteService作用是loadPlugin,其他工作交给LocalService。这样做也合理,如果是插件的交给RemoteService来loadPlugin,省下的逻辑都是相同的交给LocalService处理就可以了。

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 {
                PluginManager.getInstance(this).loadPlugin(new File(pluginLocation));
            } catch (Exception e) {
                Log.w(TAG, e);
            }
        }
    }

    return super.onStartCommand(intent, flags, startId);
}

LocalService按照service的启动流程,loadClass先加载service,反射调用attach,在调用onCreate方法,rememberService记录attach后的service,再调用service.onStartCommand执行命令。如果是启动了的直接调用onStartCommand。

@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);
    
    if (plugin == null) {
        Log.w(TAG, "Error target: " + target.toURI());
        return START_STICKY;
    }
    // ClassNotFoundException when unmarshalling in Android 5.1
    target.setExtrasClassLoader(plugin.getClassLoader());
    switch (command) {
        case EXTRA_COMMAND_START_SERVICE: {
            ActivityThread mainThread = ActivityThread.currentActivityThread();
            IApplicationThread appThread = mainThread.getApplicationThread();
            Service service;

            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) {
                    return START_STICKY;
                }
            }

            service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());
            break;
        }
        ...
    }

    return START_STICKY;
}

TODO:分析动态代理思想

六、支持插件中的BroadcastReceiver

思路是动态注册广播,将静态注册的广播转变为动态注册,将插件中静态注册的receiver使用mHostContext重新注册一遍。具体代码可以在LoadedPlugin构造方法查看。

public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
    ...

    // 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();
}

再看看查询所有的receivers,先对比ComponentName是否相同,component为空时再用intent等去匹配。

public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
    ComponentName component = intent.getComponent();
    List<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>();
    ContentResolver resolver = this.mPluginContext.getContentResolver();

    for (PackageParser.Activity receiver : this.mPackage.receivers) {
        if (receiver.getComponentName().equals(component)) {
            ResolveInfo resolveInfo = new ResolveInfo();
            resolveInfo.activityInfo = receiver.info;
            resolveInfos.add(resolveInfo);
        } else if (component == null) {
            // only match implicit intent
            for (PackageParser.ActivityIntentInfo intentInfo : receiver.intents) {
                if (intentInfo.match(resolver, intent, true, TAG) >= 0) {
                    ResolveInfo resolveInfo = new ResolveInfo();
                    resolveInfo.activityInfo = receiver.info;
                    resolveInfos.add(resolveInfo);
                    break;
                }
            }
        }
    }

    return resolveInfos;
}

七、支持插件中的ContentProvider

先看一下插件化是如何使用ContentProvider的,获取插件LoadedPlugin后,通过PluginContentResolver的wrapperUri转化Uri为后续支持读取的Uri。

// test ContentProvider
Uri bookUri = Uri.parse("content://com.didi.virtualapk.demo.book.provider/book");
LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(pkg);
bookUri = PluginContentResolver.wrapperUri(plugin, bookUri);

Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);
if (bookCursor != null) {
    while (bookCursor.moveToNext()) {
        int bookId = bookCursor.getInt(0);
        String bookName = bookCursor.getString(1);
        Log.d("ryg", "query book:" + bookId + ", " + bookName);
    }
    bookCursor.close();
}

是用wrapperUri转化下Uri后就可以正常读取ContentProvider的数据了。这里使用到了RemoteContentProvider,这个转化其实就是后面会使用到RemoteContentProvider。

public static Uri wrapperUri(LoadedPlugin loadedPlugin, Uri pluginUri) {
    String pkg = loadedPlugin.getPackageName();
    String pluginUriString = Uri.encode(pluginUri.toString());
    StringBuilder builder = new StringBuilder(RemoteContentProvider.getUri(loadedPlugin.getHostContext()));
    builder.append("/?plugin=" + loadedPlugin.getLocation());
    builder.append("&pkg=" + pkg);
    builder.append("&uri=" + pluginUriString);
    Uri wrapperUri = Uri.parse(builder.toString());
    return wrapperUri;
}

wrapperUri会拼接成

content://com.libill.virtualapk.VirtualAPK.Provider/?plugin=/storage/emulated/0/Test.apk&pkg=com.didi.virtualapk.demo&uri=content%3A%2F%2Fcom.didi.virtualapk.demo.book.provider%2Fbook

我们先看看pluginManager.loadPlugin(apk)时做了一些解释数据,并缓存起来。

// Cache 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()]);

当使用getContentResolver的query时,会通过IPC通信,调用acquireProvider获取IContentProvider的binder对象。而PluginContentResolver对acquireProvider等多个方法做了代理。

@Override
protected IContentProvider acquireProvider(Context context, String auth) {
    if (mPluginManager.resolveContentProvider(auth, 0) != null) {
        return mPluginManager.getIContentProvider();
    }
    return super.acquireProvider(context, auth);
}

在getIContentProvider方法,做了hook IContentProvider动作。

public synchronized IContentProvider getIContentProvider() {
    if (mIContentProvider == null) {
        hookIContentProviderAsNeeded();
    }

    return mIContentProvider;
}

使用了反射器对mProviderMap反射操作,获取mProviderMap来能获取到占坑的 Provider。对authority、mProvider做了setAccessible(true),最终IContentProviderProxy.newInstance生成IContentProviderProxy对象。这里还使用了RemoteContentProvider包装Uri。

protected void hookIContentProviderAsNeeded() {
    Uri uri = Uri.parse(RemoteContentProvider.getUri(mContext));
    mContext.getContentResolver().call(uri, "wakeup", null, null);
    try {
        Field authority = null;
        Field provider = null;
        ActivityThread activityThread = ActivityThread.currentActivityThread();
        Map providerMap = Reflector.with(activityThread).field("mProviderMap").get();
        Iterator iter = providerMap.entrySet().iterator();
        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(RemoteContentProvider.getAuthority(mContext))) {
                if (provider == null) {
                    provider = val.getClass().getDeclaredField("mProvider");
                    provider.setAccessible(true);
                }
                IContentProvider rawProvider = (IContentProvider) provider.get(val);
                IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
                mIContentProvider = proxy;
                Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider);
                break;
            }
        }
    } catch (Exception e) {
        Log.w(TAG, e);
    }
}

IContentProvider使用了动态代理方式

public static IContentProvider newInstance(Context context, IContentProvider iContentProvider) {
    return (IContentProvider) Proxy.newProxyInstance(iContentProvider.getClass().getClassLoader(),
            new Class[] { IContentProvider.class }, new IContentProviderProxy(context, iContentProvider));
}

看看invoke方法,使用wrapperUri后直接调用method.invoke

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Log.v(TAG, method.toGenericString() + " : " + Arrays.toString(args));
    wrapperUri(method, args);

    try {
        return method.invoke(mBase, args);
    } catch (InvocationTargetException e) {
        throw e.getTargetException();
    }
}

在RemoteContentProvider的getContentProvider可以看到加载和使用插件的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();
    ContentProvider cachedProvider = sCachedProviders.get(auth);
    if (cachedProvider != null) {
        return cachedProvider;
    }

    synchronized (sCachedProviders) {
        LoadedPlugin plugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
        if (plugin == null) {
            try {
                pluginManager.loadPlugin(new File(uri.getQueryParameter(KEY_PLUGIN)));
            } catch (Exception e) {
                Log.w(TAG, e);
            }
        }

        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));
                        ContentProvider contentProvider = (ContentProvider) Class.forName(providerInfo.name).newInstance();
                        contentProvider.attachInfo(loadedPlugin.getPluginContext(), providerInfo);
                        sCachedProviders.put(auth, contentProvider);
                    } catch (Exception e) {
                        Log.w(TAG, e);
                    }
                }
            }, true);
            return sCachedProviders.get(auth);
        }
    }

    return null;
}

八、支持插件中的Resources

资源的加载入口时加载插件时生成LoadedPlugin对象,这时开始加载资源。

this.mResources = createResources(context, getPackageName(), apk);

Constants.COMBINE_RESOURCES一直为true,直接进入ResourcesManager.createResources

protected Resources createResources(Context context, String packageName, File apk) throws Exception {
    if (Constants.COMBINE_RESOURCES) {
        return ResourcesManager.createResources(context, packageName, apk);
    } else {
        Resources hostResources = context.getResources();
        AssetManager assetManager = createAssetManager(context, apk);
        return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
    }
}

createResources加载资源后,用hook Resources一遍

public static synchronized Resources createResources(Context hostContext, String packageName, File apk) throws Exception {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        return createResourcesForN(hostContext, packageName, apk);
    }
    
    Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath());
    ResourcesManager.hookResources(hostContext, resources);
    return resources;
}

createResourcesForN是兼容N

@TargetApi(Build.VERSION_CODES.N)
private static Resources createResourcesForN(Context context, String packageName, File apk) throws Exception {
    long startTime = System.currentTimeMillis();
    String newAssetPath = apk.getAbsolutePath();
    ApplicationInfo info = context.getApplicationInfo();
    String baseResDir = info.publicSourceDir;
    
    info.splitSourceDirs = append(info.splitSourceDirs, newAssetPath);
    LoadedApk loadedApk = Reflector.with(context).field("mPackageInfo").get();

    Reflector rLoadedApk = Reflector.with(loadedApk).field("mSplitResDirs");
    String[] splitResDirs = rLoadedApk.get();
    rLoadedApk.set(append(splitResDirs, newAssetPath));

    final android.app.ResourcesManager resourcesManager = android.app.ResourcesManager.getInstance();
    ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> originalMap = Reflector.with(resourcesManager).field("mResourceImpls").get();

    synchronized (resourcesManager) {
        HashMap<ResourcesKey, WeakReference<ResourcesImpl>> resolvedMap = new HashMap<>();

        if (Build.VERSION.SDK_INT >= 28
            || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // P Preview
            ResourcesManagerCompatForP.resolveResourcesImplMap(originalMap, resolvedMap, context, loadedApk);

        } else {
            ResourcesManagerCompatForN.resolveResourcesImplMap(originalMap, resolvedMap, baseResDir, newAssetPath);
        }

        originalMap.clear();
        originalMap.putAll(resolvedMap);
    }

    android.app.ResourcesManager.getInstance().appendLibAssetForMainAssetPath(baseResDir, packageName + ".vastub");

    Resources newResources = context.getResources();

    // lastly, sync all LoadedPlugin to newResources
    for (LoadedPlugin plugin : PluginManager.getInstance(context).getAllLoadedPlugins()) {
        plugin.updateResources(newResources);
    }

    Log.d(TAG, "createResourcesForN cost time: +" + (System.currentTimeMillis() - startTime) + "ms");
    return newResources;
}

按照系统版本、厂家来兼容

private static Resources createResourcesSimple(Context hostContext, String apk) throws Exception {
    Resources hostResources = hostContext.getResources();
    Resources newResources = null;
    AssetManager assetManager;
    Reflector reflector = Reflector.on(AssetManager.class).method("addAssetPath", String.class);
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        assetManager = AssetManager.class.newInstance();
        reflector.bind(assetManager);
        final int cookie1 = reflector.call(hostContext.getApplicationInfo().sourceDir);;
        if (cookie1 == 0) {
            throw new RuntimeException("createResources failed, can't addAssetPath for " + hostContext.getApplicationInfo().sourceDir);
        }
    } else {
        assetManager = hostResources.getAssets();
        reflector.bind(assetManager);
    }
    final int cookie2 = reflector.call(apk);
    if (cookie2 == 0) {
        throw new RuntimeException("createResources failed, can't addAssetPath for " + apk);
    }
    List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();
    for (LoadedPlugin plugin : pluginList) {
        final int cookie3 = reflector.call(plugin.getLocation());
        if (cookie3 == 0) {
            throw new RuntimeException("createResources failed, can't addAssetPath for " + plugin.getLocation());
        }
    }
    if (isMiUi(hostResources)) {
        newResources = MiUiResourcesCompat.createResources(hostResources, assetManager);
    } else if (isVivo(hostResources)) {
        newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager);
    } else if (isNubia(hostResources)) {
        newResources = NubiaResourcesCompat.createResources(hostResources, assetManager);
    } else if (isNotRawResources(hostResources)) {
        newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager);
    } else {
        // is raw android resources
        newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
    }
    // lastly, sync all LoadedPlugin to newResources
    for (LoadedPlugin plugin : pluginList) {
        plugin.updateResources(newResources);
    }
    
    return newResources;
}

九、参考内容

  1. 聊聊 VirtualAPK 插件化框架的开源
  2. VirtualAPK:滴滴 Android 插件化的实践之路
  3. VirtualApk GitHub Wiki官方入门文档
  4. VirtualAPK 资源加载分析
  5. Android插件化快速入门与实例解析(VirtualApk)
  6. 深度 | 滴滴插件化方案 VirtualApk 源码解析
  7. 插件化探索,滴滴开源框架VirtualAPK的深入分析
  8. 图书推荐《Android插件化开发指南》