从源码看滴滴插件化框架VirtualApk

滴滴开源插件框架地址: https://github.com/didi/VirtualAPK

本人比较渣,在阅读源码后做一个比较简单的记录,方便后期查看。
先看看从git上clone下来的工程的项目结构:
主要是四个module
1、AndroidStub
声明了一些和framework同名的类,用来在编译的时候通过检查,在实际运行的时候会被rom中的同名类替换掉。
里面的类所有方法都是抛出一个异常。该工程被CoreLibrary引用,因为在核心工程里面会用到一些引用了framework类的地方。
throw new RuntimeException("Stub!");
2、CoreLibray
核心库,里面插件框架的主要逻辑。被宿主工程引用
3、PluginDemo
一个独立的demo工程,可以单独使用,但是作为插件apk的时候,不能通过AS run的方式生成apk,必须通过gradle编译,因为在编译的过程中会去掉一些和宿主工程一样的引用类库,比如说宿主工程应用了v7包,插件工程在开发的时候也引用了v7包,那么在编译的时候不去掉这个v7包,在加载插件的时候就会出现这个问题:
07-18 21:09:23.640 4747-4747/com.didi.virtualapk I/VAInstrumentation: newActivity[com.didi.virtualapk.core.A$1 : com.didi.virtualapk.demo.aidl.BookManagerActivity]
07-18 21:09:23.648 4747-4747/com.didi.virtualapk W/dalvikvm: Class resolved by unexpected DEX: Lcom/didi/virtualapk/demo/aidl/BookManagerActivity;(0x4193c570):0x5acfc000 ref [Landroid/support/v7/app/AppCompatActivity;] Landroid/support/v7/app/AppCompatActivity;(0x4193c570):0x5aad5000
07-18 21:09:23.648 4747-4747/com.didi.virtualapk W/dalvikvm: (Lcom/didi/virtualapk/demo/aidl/BookManagerActivity; had used a different Landroid/support/v7/app/AppCompatActivity; during pre-verification)
07-18 21:09:23.648 4747-4747/com.didi.virtualapk W/dalvikvm: Unable to resolve superclass of Lcom/didi/virtualapk/demo/aidl/BookManagerActivity; (1537)
07-18 21:09:23.648 4747-4747/com.didi.virtualapk W/dalvikvm: Link of class 'Lcom/didi/virtualapk/demo/aidl/BookManagerActivity;' failed
07-18 21:09:23.648 4747-4747/com.didi.virtualapk D/AndroidRuntime: Shutting down VM
07-18 21:09:23.648 4747-4747/com.didi.virtualapk W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x40aea2a0)
07-18 21:09:23.656 4747-4747/com.didi.virtualapk E/AndroidRuntime: FATAL EXCEPTION: main
                                                                   java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
                                                                       at dalvik.system.DexFile.defineClass(Native Method)
                                                                       at dalvik.system.DexFile.loadClassBinaryName(DexFile.java:211)
                                                                       at dalvik.system.DexPathList.findClass(DexPathList.java:315)
                                                                       at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:58)
                                                                       at java.lang.ClassLoader.loadClass(ClassLoader.java:501)
                                                                       at java.lang.ClassLoader.loadClass(ClassLoader.java:495)
                                                                       at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
                                                                       at android.app.Instrumentation.newActivity(Instrumentation.java:1053)
                                                                       at com.didi.virtualapk.internal.VAInstrumentation.newActivity(VAInstrumentation.java:101)
                                                                       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1984)
                                                                       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2094)
                                                                       at android.app.ActivityThread.access$600(ActivityThread.java:134)
                                                                       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1202)
                                                                       at android.os.Handler.dispatchMessage(Handler.java:99)
                                                                       at android.os.Looper.loop(Looper.java:137)
                                                                       at android.app.ActivityThread.main(ActivityThread.java:4767)
                                                                       at java.lang.reflect.Method.invokeNative(Native Method)
                                                                       at java.lang.reflect.Method.invoke(Method.java:511)
                                                                       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:800)
                                                                       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:567)
                                                                       at dalvik.system.NativeStart.main(Native Method)
07-18 21:09:29.898 4747-4747/com.didi.virtualapk I/Process: Sending signal. PID: 4747 SIG: 9
had used a different Landroid/support/v7/app/AppCompatActivity; during pre-verification)
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
重复的类型。
gradle编译插件,看github上的wiki。

4、app
这个是宿主工程的主module,里面只有一个Activity和Application。
在application创建的时候,通过单例模式初始化了插件管理类PluginManager对象,同时通过宿主的context拿到当前宿主进程的activityThread对象,并通过这个对象,进而拿到instrumentation(一个进程里面只有一个instrumentation,activity中的instrumentation就是通过构造contextImpl对象的时候传进去的,该对象负责接管AMS来执行activity的生命周期)和AMS在客户端的代理对象ActivityManagerProxy(IActivityManager类型)。(后面在使用contentProvider的时候,也会去hook一个IContentProvider对象,为什么不在这里开始的时候就hook?因为IContentProvider对象是在ContentResolver执行query等几个方法的时候,去ActivityThread中查询是否有这个IContentProvider对象,没有才去告诉AMS去创建一个IContentProvider对象,所以还没到使用contentProvider的时候)
看PluginManager的构造:
private PluginManager(Context context) {
        Context app = context.getApplicationContext();
        if (app == null) {
            this.mContext = context;
        } else {
            this.mContext = ((Application)app).getBaseContext();
        }
        prepare();
    }
拿到了宿主的context保存起来,执行prepare,去hookinstrumentation和AMS的代理
private void prepare() {
        Systems.sHostContext = getHostContext();
        this.hookInstrumentationAndHandler();
        this.hookSystemServices();
    }
通过反射的方式,拿到进程的activityThread对象,并根据这个对象获取ActivityThread中的instrumentation变量,通过静态代理的方式,创建一个VAInstrumentation对象,该对象里面包含了原生instrumentation的引用。把该对象重新赋值给activityThread中的instrumentation变量。这样以后宿主执行activity的创建和生命周期都由这个新对象来完成。
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();
        }
    }
看看这个VAInstrumentation里复写了什么方法:
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;

    }
这个方法是在什么时候调用的呢?
先回到主工程,在这个宿主工程的activity中的onCreate方法中,执行了加载插件apk的逻辑(通过DexClassLoader加载外部apk),并保存在一个LoadedPlugin对象中。
在这个activity中有个button,点击该button回去执行一个启动activity的代码:
Intent intent = new Intent();
            intent.setClassName(pkg, "com.didi.virtualapk.demo.aidl.BookManagerActivity");
            startActivity(intent);
这里我们看到要启动一个插件里的activity。那么来看看startActivity的流程:
startActivity  ---> startActivityForResult ---> mInstrumentation.execStartActivity。
由于我们activity的mInstrumentation引用的是ActivityThread里面的(具体是在activityThread创建activity的时候,通过activity的attach方法传进来的),而activityThread中的instrumentation已经被hook成VAInstrumentation对象了。所以必然会执行到VAInstrumentation对象的execStartActivity方法中,也就是上面的代码。
在上面的execStartActivity中,首先根据intent去插件的包里面,找到对应的插件activity的信息,包括在menifest中设置的启动模式,风格等。拿到这些信息后,根据启动模式和风格,去找到相应的占坑的activity(在核心库中的xml中注册的)。重新组装一个inent,调用realExecStartActivity方法:
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) {
            e.printStackTrace();
        }

        return result;
    }
看,直接通过反射,直接调用了原生instrumentation的execStartActivity方法,该方法里面调用AMS的代理来告诉AMS我要启动一个activity
try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
这里传递给AMS的intent就是占坑的activity的信息,这个acitivy是在宿主的核心库中注册的,AMS必然能通过检测。
AMS通过客户端(ApplicationThread)在systemServer的代理ApplicationThreadProxy发送创建activity的指令,通过binder驱动,客户端也就是宿主进程里的ActivityThread的内部类ApplicationThread,执行scheduleLauncherActivity,通过ActivityThread的内部对象H发送msg,执行handleLaunchActivity -- > performLaunchActivity:
try {
            ClassLoader e = r.packageInfo.getClassLoader();
            activity = this.mInstrumentation.newActivity(e, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(e);
            r.intent.prepareToEnterProcess();
            if(r.state != null) {
                r.state.setClassLoader(e);
            }
        } 
在performLaunchActivity这个方法里面,又调用了instrumentation的newActivity,于是我们看VAInstrumentation的该方法是做了什么内容:
 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) {
                Activity activity = mBase.newActivity(plugin.getClassLoader(), 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);
    }
根据intent信息,还原我们的插件activity的信息,直接通过原来的instrumentation 来new一个activity出来。之后的流程还是继续走正常的。


接下来看hook AMS(也就是hook AMS在客户端的代理ActivityManagerProxy),在PluginManager的hookSystemServices方法中,通过动态代理的方式,创建了一个代理IActivityManager,通过反射把该代理替换掉原来的AMP对象。以后所有执行该代理的方法,都是默认先走到我们自己定义的ActivityManagerProxy(继承自InvocationHanlder)的invoke方法中(这是动态代理的机制)。
private void hookSystemServices() {
        try {
            Singleton<IActivityManager> defaultSingleton = (Singleton<IActivityManager>) 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();
        }
    }

看下ActivityManagerProxy(这个类并不是真正意义上的AMS的代理了,只是实现了InvocationHandler接口,有点混淆),主要看invoke方法:
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);
            }
        } else if ("stopServiceToken".equals(method.getName())) {
            try {
                return stopServiceToken(proxy, method, args);
            } catch (Throwable e) {
                Log.e(TAG, "Stop service token error", e);
            }
        } else if ("bindService".equals(method.getName())) {
            try {
                return bindService(proxy, method, args);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        } else if ("unbindService".equals(method.getName())) {
            try {
                return unbindService(proxy, method, args);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        } else if ("getIntentSender".equals(method.getName())) {
            try {
                getIntentSender(method, args);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else if ("overridePendingTransition".equals(method.getName())){
            try {
                overridePendingTransition(method, args);
            } catch (Exception e){
                e.printStackTrace();
            }
        }
看这个方法里面的method的name是不是很熟悉,就是我们主动调用startService的时候,由ContextImpl传递到AMS的代理执行的。
比如在一个activity中启动一个service是这样的
startService ---> 最终调用contextImpl的startServie。
public ComponentName startService(Intent service) {
        this.warnIfCallingFromSystemProcess();
        return this.startServiceCommon(service, this.mUser);
    }
private ComponentName startServiceCommon(Intent service, UserHandle user) {
        try {
            this.validateServiceIntent(service);
            service.prepareToLeaveProcess(this);
            ComponentName e = ActivityManagerNative.getDefault().startService(this.mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(this.getContentResolver()), this.getOpPackageName(), user.getIdentifier());
            if(e != null) {
                if(e.getPackageName().equals("!")) {
                    throw new SecurityException("Not allowed to start service " + service + " without permission " + e.getClassName());
                }

                if(e.getPackageName().equals("!!")) {
                    throw new SecurityException("Unable to start service " + service + ": " + e.getClassName());
                }
            }

            return e;
        } catch (RemoteException var4) {
            throw var4.rethrowFromSystemServer();
        }
    }
看,最终调用了AMS的代理的startService,而该代理被我们hook了,也就会执行到上面的ActivityManagerProxy(继承自InvocationHanlder)的invoke方法中。
其他的方法,比如stop等等都会被这个invoke拦截,在invoke中通过不同的方法名调用了不同的函数,比如startService
private 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);
    }
最终,如果是要执行代理的组件,就会去执行startDelegateServiceFortarget方法,看看其他的方法,比如stopService,最终都会调用这个函数。也就是说所有的AMS的操作service的命令,最终都会执行到这里。看看这个函数做了什么。
private ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
        Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
        return mPluginManager.getHostContext().startService(wrapperIntent);
    }
所有跟service生命周期相关的调用都被替换为执行启动宿主的service,也就是每次都会回调宿主service的onStartCommand方法,必然,会在该方法里面去创建我们插件的service的生命周期了。


最后,ContextProvider的处理,也是通过动态代理的方式,hook掉AMS的IContentProvider,所有执行query等操作都有invoke来分发,通过包裹uri,去调用占坑的contentProvider,占坑的contentProvider在根据这些uri,解析出真正要执行的插件中的contentProvider,然后启动该provider。
执行一个contentProvider的query流程是这样的:
在service、activity或application中调用getConentResolver.query,
getContentResolver由父类ContextWrapper实现,其又调用了内部对象mBase(是个ContextImpl的实例)的同名方法,那么具体看ContextImpl
public ContentResolver getContentResolver() {
        return this.mContentResolver;
    }


mContentResolver是ContextImpl.ApplicationContentResolver对象,在ContextImpl构造函数中创建
this.mContentResolver = new ContextImpl.ApplicationContentResolver(this, mainThread, user);
直接看这个类的query方法,这个query其实是调用的父类ContentResolver的方法:
public final Cursor query(@Read Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SeempLog.record_uri(13, uri);
        return this.query(uri, projection, selection, selectionArgs, sortOrder, (CancellationSignal)null);
    }
在其重载方法中会去调用acquireProvider,该方法是个抽象方法,具体在applicationContentResolver中实现:
protected IContentProvider acquireProvider(Context context, String auth) {
            return this.mMainThread.acquireProvider(context, ContentProvider.getAuthorityWithoutUserId(auth), this.resolveUserIdFromAuthority(auth), true);
        }
看,直接调用的是activityThread的同名方法,
public final IContentProvider acquireProvider(Context c, String auth, int userId, boolean stable) {
        IContentProvider provider = this.acquireExistingProvider(c, auth, userId, stable);
        if(provider != null) {
            return provider;
        } else {
            ContentProviderHolder holder = null;

            try {
                holder = ActivityManagerNative.getDefault().getContentProvider(this.getApplicationThread(), auth, userId, stable);
            } catch (RemoteException var8) {
                throw var8.rethrowFromSystemServer();
            }

            if(holder == null) {
                Slog.e("ActivityThread", "Failed to find provider info for " + auth);
                return null;
            } else {
                holder = this.installProvider(c, holder, holder.info, true, holder.noReleaseNeeded, stable);
                return holder.provider;
            }
        }
    }
调用了AMS的代理,获取一个IContentProvider对象存储到holder对象中。并执行了安装provider的操作,installProvider会把provider保存在activityThread的的provider的map中保存起来以便以后使用。
这是宿主启动占坑的contentProvider的主要流程,我们要hook的就是操作这个宿主占坑contentprovider对应的IContentProvider对象了,为什么这么说呢,因为插件的contentProvider都是通过宿主的contentprovider来显示的执行创建,query等操作的。
所以在操作插件contentProvider前先要启动占坑的contentProvider,这样才能拿到代理的IContentProvider对象进行hook。
所以在PluginManager的hook中,先执行了占坑contentProvider的创建过程。
private void hookIContentProviderAsNeeded() {
        Uri uri = Uri.parse(PluginContentResolver.getUri(mContext));
        mContext.getContentResolver().call(uri, "wakeup", null, null);
        try {
            Field authority = null;
            Field mProvider = null;
            ActivityThread activityThread = (ActivityThread) ReflectUtil.getActivityThread(mContext);
            Map mProviderMap = (Map) ReflectUtil.getField(activityThread.getClass(), activityThread, "mProviderMap");
            Iterator iter = mProviderMap.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(PluginContentResolver.getAuthority(mContext))) {
                    if (mProvider == null) {
                        mProvider = val.getClass().getDeclaredField("mProvider");
                        mProvider.setAccessible(true);
                    }
                    IContentProvider rawProvider = (IContentProvider) mProvider.get(val);
                    IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
                    mIContentProvider = proxy;
                    Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider);
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
头两句,先获得宿主的uri,通过该uri,通过宿主的context获取到宿主的resolver对象,执行call方法,这样就回去执行宿主占坑的contentProvider的call方法,此时,操作占坑的provider的IcontentProvider对象已经存储到activityThread的map中了,
所以在该方法最后,直接从map变量中获取了IContentProvider,然后通过动态代理的方式,替换了原来的IContentProvider。
这个时候所有操作IContentProvider的所有方法都被IContentProviderProxy的invoke方法拦截,
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();
        }
    }
在该方法里面包着了uri,携带了启动插件provider的uri,
然后继续调用宿主 contentProvider,在占坑的provider中,各个方法里面
 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        ContentProvider provider = getContentProvider(uri);
        Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));
        if (provider != null) {
            return provider.query(pluginUri, projection, selection, selectionArgs, sortOrder);
        }

        return null;
    }
getContentProvider方法拿到插件的provider,直接指向query方法。这样插件中的query方法就被调用了。就好像系统去调用了一样。

这里为什么hook宿主的provider时,要使用call方式来启动宿主provider呢
自我感觉,resolver执行call方法,比如导致provider来执行call,这里面不用做什么其他而我的操作,执行call方法必然就会让provider去执行onCreate方法来启动。感觉是个启动provider的好时机。



好吧,终于理完了~~



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值