首页 > 学院 > 开发设计 > 正文

插件化开发---DroidPlugin对广播的管理

2019-11-09 18:43:38
字体:
来源:转载
供稿:网友

回想一下我们日常开发的时候是如何使用BroadcastReceiver的:注册, 发送和接收;因此,要实现BroadcastReceiver的插件化就这三种操作提供支持;接下来我们将一步步完成这个过程。 我们可以注册一个BroadcastReceiver然后接收我们感兴趣的广播,也可以给某有缘人发出某个广播;因此,我们对源码的分析按照两条路线展开: 注册过程 不论是静态广播还是动态广播,在使用之前都是需要注册的;动态广播的注册需要借助Context类的registerReceiver方法,而静态广播的注册直接在AndroidManifest.xml中声明即可;我们首先分析一下动态广播的注册过程。 Context类的registerReceiver的真正实现在ContextImpl里面,而这个方法间接调用了registerReceiverInternal,源码如下:

@Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { return registerReceiverInternal(receiver, getUserId(), filter, broadcastPermission, scheduler, getOuterContext()); } PRivate Intent registerReceiverInternal(BroadcastReceiver receiver, int userId, IntentFilter filter, String broadcastPermission, Handler scheduler, Context context) { IIntentReceiver rd = null; if (receiver != null) { if (mPackageInfo != null && context != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } rd = mPackageInfo.getReceiverDispatcher( receiver, context, scheduler, mMainThread.getInstrumentation(), true); } else { if (scheduler == null) { scheduler = mMainThread.getHandler(); } rd = new LoadedApk.ReceiverDispatcher( receiver, context, scheduler, null, true).getIIntentReceiver(); } } try { return ActivityManagerNative.getDefault().registerReceiver( mMainThread.getapplicationThread(), mBasePackageName, rd, filter, broadcastPermission, userId); } catch (RemoteException e) { return null; } }

可以看到,BroadcastReceiver的注册也是通过AMS完成的;在进入AMS跟踪它的registerReceiver方法之前,我们先弄清楚这个IIntentReceiver类型的变量rd是什么。

public interface IIntentReceiver extends IInterfacepublic interface IInterface{ /** * Retrieve the Binder object associated with this interface. * You must use this instead of a plain cast, so that proxy objects * can return the correct result. */ public IBinder asBinder();}

这个类是通过AIDL工具生成的,它是一个Binder对象,因此可以用来跨进程传输; 由于广播的分发过程是在AMS中进行的,而AMS所在的进程和BroadcastReceiver所在的进程不一样,因此要把广播分发到BroadcastReceiver具体的进程需要进行跨进程通信,这个通信的载体就是IIntentReceiver类。另外,IIntentReceiver是一个接口,从上述代码中可以看出,它的实现类为LoadedApk.ReceiverDispatcher。

AMS类的registerReceiver方法代码有点多,这里不一一解释了,感兴趣的话可以自行查阅;这个方法主要做了以下两件事:

1、对发送者的身份和权限做出一定的校检 2、把这个BroadcastReceiver以BroadcastFilter的形式存储在AMS的mReceiverResolver变量中,供后续使用。

就这样,被传递过来的BroadcastReceiver已经成功地注册在系统之中,能够接收特定类型的广播了;

那么注册在AndroidManifest.xml中的静态广播是如何被系统感知的呢??? 在 插件加载机制 中我们知道系统会通过PackageParser解析Apk中的AndroidManifest.xml文件,因此我们有理由认为,系统会在解析AndroidMafest.xml的标签(也即静态注册的广播)的时候保存相应的信息;而Apk的解析过程是在PMS中进行的,因此静态注册广播的信息存储在PMS中。

发送和接收过程

发送过程 就是一句context.sendBroadcast(),我们顺藤摸瓜,跟踪这个方法。前文也提到过,Context中方法的调用都会委托到ContextImpl这个类,我们直接看ContextImpl对这个方法的实现:

@Override public void sendBroadcast(Intent intent) { warnIfCallingFromSystemProcess(); String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, false, getUserId()); } catch (RemoteException e) { } }

嗯,发送广播也是通过AMS进行的,我们直接查看ActivityManagerService类的broadcastIntent方法,这个方法仅仅是调用了broadcastIntentLocked方法, 某个广播被发送之后,AMS会找出所有注册过的BroadcastReceiver中与这个广播匹配的接收者,然后将这个广播分发给相应的接收者处理。

匹配过程 某一条广播被发出之后,并不是阿猫阿狗都能接收它并处理的;BroadcastReceiver可能只对某些类型的广播感兴趣,因此它也只能接收和处理这种特定类型的广播;在broadcastIntentLocked方法内部有如下代码:

receivers = collectReceiverComponents(intent, resolvedType, callingUid, users); registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false, userId);

读者可以自行跟踪这两个方法的代码,过程比较简单,我这里直接给出结论:

receivers是对这个广播感兴趣的静态BroadcastReceiver列表;collectReceiverComponents 通过PackageManager获取了与这个广播匹配的静态BroadcastReceiver信息;这里也证实了我们在分析BroadcasrReceiver注册过程中的推论——静态BroadcastReceiver的注册过程的确实在PMS中进行的。mReceiverResolver存储了动态注册的BroadcastReceiver的信息;还记得这个mReceiverResolver吗?我们在分析动态广播的注册过程中发现,动态注册的BroadcastReceiver的相关信息最终存储在此对象之中;在这里,通过mReceiverResolver对象匹配出了对应的BroadcastReceiver供进一步使用。

现在系统通过PMS拿到了所有符合要求的静态BroadcastReceiver,然后从AMS中获取了符合要求的动态BroadcastReceiver;因此接下来的工作非常简单:唤醒这些广播接受者。简单来说就是回调它们的onReceive方法。

接收过程 通过上文的分析过程我们知道,在AMS的broadcastIntentLocked方法中找出了符合要求的所有BroadcastReceiver;接下来就需要把这个广播分发到这些接收者之中。在broadcastIntentLocked方法的后半部分有如下代码:

BroadcastQueue queue = broadcastQueueForIntent(intent);BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, resolvedType, requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId);boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r);if (!replaced) { queue.enqueueOrderedBroadcastLocked(r); queue.scheduleBroadcastsLocked();}

首先创建了一个BroadcastRecord代表此次发送的这条广播,然后把它丢进一个队列,最后通过scheduleBroadcastsLocked通知队列对广播进行处理。

在BroadcastQueue中通过Handle调度了对于广播处理的消息,调度过程由processNextBroadcast方法完成,而这个方法通过performReceiveLocked最终调用了IIntentReceiver的performReceive方法。

这个IIntentReceiver正是在广播注册过程中由App进程提供给AMS进程的Binder对象,现在AMS通过这个Binder对象进行ipC调用通知广播接受者所在进程完成余下操作。在上文我们分析广播的注册过程中提到过,这个IItentReceiver的实现是LoadedApk.ReceiverDispatcher;我们查看这个对象的performReceive方法,源码如下:

public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { Args args = new Args(intent, resultCode, data, extras, ordered, sticky, sendingUser); if (!mActivityThread.post(args)) { if (mRegistered && ordered) { IActivityManager mgr = ActivityManagerNative.getDefault(); args.sendFinished(mgr); } }}

这个方法创建了一个Args对象,然后把它post到了mActivityThread这个Handler中;我们查看Args类的run方法: 我们看到了相应BroadcastReceiver的onReceive回调;因此,广播的工作原理到这里就水落石出了;

BroadcastReceiver有一个IntentFilter的概念,也就是说,每一个BroadcastReceiver只对特定的Broadcast感兴趣;而且,AMS在进行广播分发的时候,也会对这些BroadcastReceiver与发出的广播进行匹配,只有Intent匹配的Receiver才能收到广播;在分析源码的时候也提到了这个匹配过程。如果我们尝试用替身Receiver解决静态注册的问题,那么它的IntentFilter该写什么?我们无法预料插件中静态注册的Receiver会使用什么类型的IntentFilter,就算我们在AndroidManifest.xml中声明替身也没有用——我们压根儿收不到与我们的IntentFilter不匹配的广播。其实,我们对于Activity的处理方式也有这个问题;如果你尝试用IntentFilter的方式启动Activity,这并不能成功;

可以把静态广播当作动态广播处理

既然都是广播,它们的功能都是订阅一个特定的消息然后执行某个特定的操作,我们完全可以把插件中的静态广播全部注册为动态广播,这样就解决了静态广播的问题。

静态广播非静态的实现 要把插件中的静态BroadcastReceiver当作动态BroadcastReceiver处理,我们首先得知道插件中到底注册了哪些广播;这个过程归根结底就是获取AndroidManifest.xml中的标签下面的内容,我们可以选择手动解析xml文件;这里我们选择使用系统的 PackageParser 帮助解析,这种方式在之前的 [插件加载过程][] 中也用到过,如果忘记了可以温习一下,我们要解析apk的的信息,可以使用PackageParser的generateActivityInfo方法。

知道这一点之后,代码就比较简单了;使用反射调用相应的隐藏接口,并且在必要的时候构造相应参数的方式我们在插件化系列文章中已经讲述过很多,相信读者已经熟练,这里就不赘述,直接贴代码:

注册 我们已经解析得到了插件中静态注册的BroadcastReceiver的信息,现在我们只需要把这些静态广播动态注册一遍就可以了;但是,由于BroadcastReceiver的实现类存在于插件之后,我们需要手动用ClassLoader来加载它;这一点在 插件加载机制 已有讲述,不啰嗦了。

、、、、、、、、、、、、、、、、、、、、代码区、、、、、、、、、、、、、、、、、、、、、、、 首先是注册2个静态广播的apk MainActivity什么都没有做,只有一个空的布局文件

package com.wang.myapplication;import android.app.Activity;import android.content.Context;import android.content.Intent;import android.net.Uri;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } // 这个方法比onCreate调用早; 在这里Hook比较好. @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); }}

Receiver1

package com.wang.myapplication;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.widget.Toast;public class Receiver1 extends BroadcastReceiver { static final String ACTION = "com.weishu.upf.demo.app2.PLUGIN_ACTION"; public void onReceive(Context paramContext, Intent paramIntent) { Toast.makeText(paramContext, "我是插件, 主程序收到请回答!", 0).show(); paramContext.sendBroadcast(new Intent("com.weishu.upf.demo.app2.PLUGIN_ACTION")); }}

配置文件如下

<receiver android:name="com.wang.myapplication.Receiver1"> <intent-filter> <action android:name="com.wang.myapplication.Receiver1"></action> </intent-filter> </receiver> <receiver android:name="com.wang.myapplication.Receiver2"></receiver>

然后将其打包成apk放到到另一个项目的资源文件下,我们继续看另外一个项目的结构 这里写图片描述

MainActivity

package com.weishu.upf.receiver_management.app;import java.io.File;import android.app.Activity;import android.app.ActivityManagerNative;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.Toast;public class MainActivity extends Activity { // 发送广播到插件之后, 插件如果受到, 那么会回传一个ACTION 为这个值的广播; static final String ACTION = "com.weishu.upf.demo.app2.PLUGIN_ACTION"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button t = new Button(this); setContentView(t); t.setText("send broadcast to plugin: demo"); t.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getApplicationContext(), "插件插件!收到请回答!!", Toast.LENGTH_SHORT).show(); sendBroadcast(new Intent("com.wang.myapplication.Receiver1")); } }); Utils.extractAssets(this, "test.apk"); File testPlugin = getFileStreamPath("test.apk"); try { ReceiverHelper.preLoadReceiver(this, testPlugin); Log.i(getClass().getSimpleName(), "hook success"); } catch (Exception e) { throw new RuntimeException("receiver load failed", e); } // 注册插件收到我们发送的广播之后, 回传的广播 registerReceiver(mReceiver, new IntentFilter(ACTION)); } BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "插件插件,我是主程序,握手完成!", Toast.LENGTH_SHORT).show(); } }; @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(mReceiver); }}

ReceiverHelper

package com.weishu.upf.receiver_management.app;import java.io.File;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.HashMap;import java.util.List;import java.util.Map;import android.content.BroadcastReceiver;import android.content.Context;import android.content.IntentFilter;import android.content.pm.ActivityInfo;import android.content.pm.PackageManager;import android.util.Log;public final class ReceiverHelper { private static final String TAG = "ReceiverHelper"; public static Map<ActivityInfo, List<? extends IntentFilter>> sCache = new HashMap<ActivityInfo, List<? extends IntentFilter>>(); /** * 解析Apk文件中的 <receiver>, 并存储起来 * * @param apkFile * @throws Exception */ private static void parserReceivers(File apkFile) throws Exception { Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser"); Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class); Object packageParser = packageParserClass.newInstance(); // 首先调用parsePackage获取到apk对象对应的Package对象 Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, PackageManager.GET_RECEIVERS); // 读取Package对象里面的receivers字段,注意这是一个 List<Activity> (没错,底层把<receiver>当作<activity>处理) // 接下来要做的就是根据这个List<Activity> 获取到Receiver对应的 ActivityInfo (依然是把receiver信息用activity处理了) Field receiversField = packageObj.getClass().getDeclaredField("receivers"); List receivers = (List) receiversField.get(packageObj); // 调用generateActivityInfo 方法, 把PackageParser.Activity 转换成 Class<?> packageParser$ActivityClass = Class.forName("android.content.pm.PackageParser$Activity"); Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState"); Class<?> userHandler = Class.forName("android.os.UserHandle"); Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId"); int userId = (Integer) getCallingUserIdMethod.invoke(null); Object defaultUserState = packageUserStateClass.newInstance(); Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component"); Field intentsField = componentClass.getDeclaredField("intents"); // 需要调用 android.content.pm.PackageParser#generateActivityInfo(android.content.pm.ActivityInfo, int, android.content.pm.PackageUserState, int) Method generateReceiverInfo = packageParserClass.getDeclaredMethod("generateActivityInfo", packageParser$ActivityClass, int.class, packageUserStateClass, int.class); // 解析出 receiver以及对应的 intentFilter for (Object receiver : receivers) { ActivityInfo info = (ActivityInfo) generateReceiverInfo.invoke(packageParser, receiver, 0, defaultUserState, userId); List<? extends IntentFilter> filters = (List<? extends IntentFilter>) intentsField.get(receiver); sCache.put(info, filters); } } public static void preLoadReceiver(Context context, File apk) throws Exception { parserReceivers(apk); ClassLoader cl = null; for (ActivityInfo activityInfo : ReceiverHelper.sCache.keySet()) { Log.i(TAG, "preload receiver:" + activityInfo.name); List<? extends IntentFilter> intentFilters = ReceiverHelper.sCache.get(activityInfo); if (cl == null) { cl = CustomClassLoader.getPluginClassLoader(apk, activityInfo.packageName); } // 把解析出来的每一个静态Receiver都注册为动态的 for (IntentFilter intentFilter : intentFilters) { BroadcastReceiver receiver = (BroadcastReceiver) cl.loadClass(activityInfo.name).newInstance(); context.registerReceiver(receiver, intentFilter); } } }}

CustomClassLoader

package com.weishu.upf.receiver_management.app;import java.io.File;import java.io.IOException;import dalvik.system.DexClassLoader;public class CustomClassLoader extends DexClassLoader { public CustomClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, optimizedDirectory, libraryPath, parent); } /** * 便利方法: 获取插件的ClassLoader, 能够加载指定的插件中的类 * * @param plugin * @param packageName * @return * @throws IOException */ public static CustomClassLoader getPluginClassLoader(File plugin, String packageName) throws IOException { return new CustomClassLoader(plugin.getPath(), Utils.getPluginOptDexDir(packageName).getPath(), Utils.getPluginLibDir(packageName).getPath(), UPFApplication.getContext().getClassLoader()); }}

Utils

package com.weishu.upf.receiver_management.app;import java.io.Closeable;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import android.content.Context;import android.content.res.AssetManager;public class Utils { /** * 把Assets里面得文件复制到 /data/data/files 目录下 * * @param context * @param sourceName */ public static void extractAssets(Context context, String sourceName) { AssetManager am = context.getAssets(); InputStream is = null; FileOutputStream fos = null; try { is = am.open(sourceName); File extractFile = context.getFileStreamPath(sourceName); fos = new FileOutputStream(extractFile); byte[] buffer = new byte[1024]; int count = 0; while ((count = is.read(buffer)) > 0) { fos.write(buffer, 0, count); } fos.flush(); } catch (IOException e) { e.printStackTrace(); } finally { closeSilently(is); closeSilently(fos); } } /** * 待加载插件经过opt优化之后存放odex得路径 */ public static File getPluginOptDexDir(String packageName) { return enforceDirExists(new File(getPluginBaseDir(packageName), "odex")); } /** * 插件得lib库路径, 这个demo里面没有用 */ public static File getPluginLibDir(String packageName) { return enforceDirExists(new File(getPluginBaseDir(packageName), "lib")); } // -------------------------------------------------------------------------- private static void closeSilently(Closeable closeable) { if (closeable == null) { return; } try { closeable.close(); } catch (Throwable e) { // ignore } } private static File sBaseDir; // 需要加载得插件得基本目录 /data/data/<package>/files/plugin/ private static File getPluginBaseDir(String packageName) { if (sBaseDir == null) { sBaseDir = UPFApplication.getContext().getFileStreamPath("plugin"); enforceDirExists(sBaseDir); } return enforceDirExists(new File(sBaseDir, packageName)); } private static synchronized File enforceDirExists(File sBaseDir) { if (!sBaseDir.exists()) { boolean ret = sBaseDir.mkdir(); if (!ret) { throw new RuntimeException("create dir " + sBaseDir + "failed"); } } return sBaseDir; }}

UPFApplication

package com.weishu.upf.receiver_management.app;import android.app.Application;import android.content.Context;public class UPFApplication extends Application { private static Context sContext; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); sContext = base; } public static Context getContext() { return sContext; }}

最后看这个项目在data/data/目录下的结构 这里写图片描述


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表