Android插件化源码分析之VirtualApk

一、简介

VirtualAPK是滴滴出行自研的一款优秀的插件化框架,VirtualAPK其特性和主流开源框架的对比:
在这里插入图片描述
本文将去分析四大组件的代码hook过程。

二、支持Activity

启动插件中的一个activity,
在这里插入图片描述
首先PluginManager.getInstance(this).getLoadedPlugin(pkg),先去判断有没有加载com.didi.virtualapk.demo的插件包,有的话则去跳转插件中的BookManagerActivity。那么是如何跳转的呢?
PluginManager在被创建实例的时候,会去hookCurrentProcess(),
在这里插入图片描述
在这里插入图片描述
通过activityThread去获取到Instrumentation,在这个Instrumentation的基础上新建一个VAInstrumentation的实例,并且set给activityThread,同时还hook了ActivityThread的mH类的Callback。
一些底层知识可以看下,为什么去hook Instrumentation:
《写给Android App开发人员看的Android底层知识(2)》
startActivity()方法会去执行到VAInstrumentation的execStartActivity方法中,
在这里插入图片描述
在接下来的markIntentIfNeeded方法中,会去将需要跳转的activity信息保存下来,在真正去跳转到下一个页面时候在去恢复回来
在这里插入图片描述

dispatchStubActivity方法会去根据不同的launchMode和themeObj去加载不同的stubActivity在这里插入图片描述
继续回到Instrumentation的execStartActivity,
我们分析android源码以android 23为例:
在这里插入图片描述
通过ActivityManagerNative.getDefault()方法去获取IActivityManager的子类ActivityManagerProxy,调用startActivity方法
在这里插入图片描述
通过binder通信机制,
在这里插入图片描述
IApplicationThread app = ApplicationThreadNative.asInterface(b)是为了获取
到ApplicationThreadProxy并在后面的方法startActivity(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profilerInfo, options)中会用到,startActivity的具体逻辑需要在ActivityManagerService类去追踪了,
在AMS在处理完启动Activity后,会回到ActivityThread中ApplicationThread的scheduleLaunchActivity方法,最终要回到了mInstrumentation.newActivity的方法中去了
在这里插入图片描述
回到VAInstrumentation.newActivity方法中
在这里插入图片描述
从intent中获取到我们需要加载activity,通过Instrumentation.newActivity去创建新的activity,接着进入到了callActivityOnCreate中,从LoadedPlugin中取出mResources,mBase,mApplication传递给新生成的这个activity中,后面就可以完成activity的启动流程了。
在这里插入图片描述

三 、Broadcast Receiver的支持

在这里插入图片描述
对于Broadcast Receiver,比较简单,初始化LoadedPlugin插件对象的时候,直接将静态广播转为动态广播,就可以了。

四、Service的支持

service的hook采用了ActivityManagerProxy(继承了IActivityManager)的动态代理的方式处理,通过ActivityManagerProxy代理类来拦截方法去进行hook
在这里插入图片描述


public class ActivityManagerProxy implements InvocationHandler {
	@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);
            }
        }
        ...
     }
     
     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);
    }
    
    protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
        Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
        return mPluginManager.getHostContext().startService(wrapperIntent);
    }
    
    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;
    }
}

ActivityManagerProxy在处理startService方法时,如图走入到wrapperTargetIntent方法里,判断当前是否本地service启动LocalService还是RemoteService,Service多次启动都会去调用onStartCommond方法,如果已经启动了话,只会去调用onStartCommond方法。

1、LocalService

public class LocalService extends Service {
	 @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (null == intent || !intent.hasExtra(EXTRA_TARGET) || !intent.hasExtra(EXTRA_COMMAND)) {
            return START_STICKY;
        }

        Intent target = intent.getParcelableExtra(EXTRA_TARGET);
        int command = intent.getIntExtra(EXTRA_COMMAND, 0);
        if (null == target || command <= 0) {
            return START_STICKY;
        }

        ComponentName component = target.getComponent();
        LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);
        
        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;
            }
     }
   }
   ...

}

在LocalService中可以看到,onStartCommand中方法进行重写,该方法对目标Service新建、操作、销毁等,创建ComponentsHandler类去便于管理目标Service的记录、删除等。

2、RemoteService

  • onBind返回为空,说明这个Service不能被绑定。
  • onStartCommand中多了方法PluginManager.getInstance(this).loadPlugin(new File(pluginLocation)),说明它可以去加载其他的插件下的代码,也就可以调用其他Plugin下的Service

public class RemoteService extends LocalService {
    private static final String TAG = "VA.RemoteService";

    public RemoteService() {
    }

    public IBinder onBind(Intent intent) {
        return null;
    }

    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent == null) {
            return super.onStartCommand(intent, flags, startId);
        } else {
            Intent target = (Intent)intent.getParcelableExtra("target");
            if (target != null) {
                String pluginLocation = intent.getStringExtra("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 var9) {
                        Log.w("VA.RemoteService", var9);
                    }
                }
            }

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

五、ContentProvider的支持

ContentProvider的hook也是采用代理的方式,看调用ContentProvider的测试代码:

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

由于VAInstrumentation中injectActivity的方法,mBase是被设置成了PluginContext,实际上getContentResolver()方法是走到了PluginContext的getContentResolver()方法里面,返回PluginContentResolver对象,由于getContentResolver().query(…)方法调用会走到了PluginContentResolver的acquireProvider()/acquireUnstableProvider()方法里面,通过mPluginManager.getIContentProvider()走到hookIContentProviderAsNeeded()方法中。

VAInstrumentation的injectActivity方法:

protected void injectActivity(Activity activity) {
	...
	 Reflector.with(base).field("mResources").set(plugin.getResources());
                Reflector reflector = Reflector.with(activity);
                reflector.field("mBase").set(plugin.createPluginContext(activity.getBaseContext()));
                reflector.field("mApplication").set(plugin.getApplication());
	...
}

PluginContext的getContentResolver方法是返回PluginContentResolver对象

class PluginContext extends ContextWrapper {
	@Override
    public ContentResolver getContentResolver() {
        return new PluginContentResolver(getHostContext());
    }
}

PluginContentResolver的acquireProvider()方法:

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

mPluginManager.getIContentProvider()、hookIContentProviderAsNeeded()。

方法作用是:得到占坑的RemoteContentProvider的uri,调用call()方法,call里面流程是调用acquireProvider->mMainThread.acquireProvider->
ActivityManagerNative.getDefault().getContentProvider->installProvider。
首先调用已经注册provider,得到返回的IContentProvider对象,这个IContentProvider对象是在ActivityThread.installProvider方法中加入到mProviderMap中。
之后去遍历activityThread的mProviderMap集合就是为了去拿到占坑的RemoteContentProvider对应的IContentProvider对象,并通过创建IContentProviderProxy对象进行动态代理的设置。

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

        return mIContentProvider;
 }
 
protected void hookIContentProviderAsNeeded() {
		//得到占坑的RemoteContentProvider的uri,调用call()方法,调用acquireProvider->mMainThread.acquireProvider->
		//ActivityManagerNative.getDefault().getContentProvider->installProvider。
		//首先调用已经注册provider,得到返回的IContentProvider对象,这个IContentProvider对象是在ActivityThread.installProvider方法中加入到mProviderMap中。
		//最终拿到占坑的RemoteContentProvider对应的IContentProvider对象。
        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);
        }
    }

看IContentProviderProxy这个类,invoke()方法中的wrapperUri()主要是为了去将需要调用的uri去替换成RemoteContentProvider的uri,然后在去配置需要调用的uri到路径的参数中去

public class IContentProviderProxy implements InvocationHandler {
	...
	 @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();
        }
    }

	 private void wrapperUri(Method method, Object[] args) {
        Uri uri = null;
        int index = 0;
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Uri) {
                    uri = (Uri) args[i];
                    index = i;
                    break;
                }
            }
        }

        Bundle bundleInCallMethod = null;
        if (method.getName().equals("call")) {
            bundleInCallMethod = getBundleParameter(args);
            if (bundleInCallMethod != null) {
                String uriString = bundleInCallMethod.getString(RemoteContentProvider.KEY_WRAPPER_URI);
                if (uriString != null) {
                    uri = Uri.parse(uriString);
                }
            }
        }

        if (uri == null) {
            return;
        }

        PluginManager pluginManager = PluginManager.getInstance(mContext);
        ProviderInfo info = pluginManager.resolveContentProvider(uri.getAuthority(), 0);
        if (info != null) {
            String pkg = info.packageName;
            LoadedPlugin plugin = pluginManager.getLoadedPlugin(pkg);
            Uri wrapperUri = PluginContentResolver.wrapperUri(plugin, uri);
            if (method.getName().equals("call")) {
                bundleInCallMethod.putString(RemoteContentProvider.KEY_WRAPPER_URI, wrapperUri.toString());
            } else {
                args[index] = wrapperUri;
            }
        }
    }
}
public class RemoteContentProvider extends ContentProvider {
	...
 	@Override
    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;
    }
    
	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);
            }
        }
}

看RemoteContentProvider的query()方法,ContentProvider provider = getContentProvider(uri)去获取到真实需要调用的ContentProvider,并且进行query调用,这样就通过RemoteContentProvider去间接调用到了目标ContentProvider的方法。

参考链接:
深度 | 滴滴插件化方案 VirtualApk 源码解析
VirtualAPK 资源篇
Android开源框架源码鉴赏:VirtualAPK

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值