19

深入理解VirtualApk插件化

 4 years ago
source link: https://www.tuicool.com/articles/eY7jIzI
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

什么是插件化?

插件化包括宿主和插件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);
        }
    }
    ...
}

ZZfUnu6.png!web

三、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。

IfiAJf3.png!web

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的过程。

AnAzMnU.png!web

图中可以看到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插件化开发指南》

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK