占位式插件化之加载静态广播

占位式插件化之加载静态广播

接着前几篇文章来:由于插件中的广播是在manifest中配置的,所以就不能使用上一篇中的方法来注册广播了,首先我们需要了解一下APK的解析原理

第一步我们要知道静态广播是什么时候注册的?

在手机开机的时候,系统谁扫描所有的app,在重新安装一遍,这也是为啥手机开机会这么慢,这时候系统会去解析AndroidManifest文件,解析的过程中遇到静态广播后就会自动注册

第二步我们来看一下应用的安装目录

主要有三个目录

/data/app

该文件夹存放着系统中安装的第三方应用的 apk 文件,当我们调试一个app的时候,可以看到控制台输出的内容,有一项是
uploading …..就是上传我们的apk到这个文件夹,上传成功之后才开始安装。Android 中应用的安装就是将应用的安装包原封不动地拷贝到 /data/app 目录下,每个应用安装包本质上就是一个 zip 格式的压缩文件。为了提升应用的启动效率,Android 会将解压出来的 dex 格式的应用代码文件解析提取后,缓存在 /data/dalvik-cache 目录下。

/data/data

该文件夹存放存储包私有数据,对于设备中每一个安装的 App,系统都会在内部存储空间的 data/data 目录下以应用包名为名字自动创建与之对应的文件夹。
用户卸载 App 时,系统自动删除 data/data 目录下对应包名的文件夹及其内容。

data/dalvik-cache

虚拟机去加载执行指令

通过上面的解释,可以知道,我们应该分析data/app这个目录,手机开机的时候就会扫描这个目录,来解析apk中的配置信息。

然后就开始看看系统是怎么来解析apk文件的,系统中的包的解析都是通过PackageManagerService这个类来解析的,当系统启动的时候,首先启动Linux内核驱动,然后启动init进程,然后启动zygote孵化进程,在然后启动SystemServer进程,然后启动PackageManagerService。

所以我们去PackageManagerService这个类中看看系统是怎么解析data/app中的 apk文件的

下面的源码是基于Android9.0的,每个版本的源码可能不一样

Ok 现在来到PackageManagerService这个类中

我们从文件目录入手,可以看到它有几个静态的成员变量

1
2
3
4
5
6
7
8
9
 /** Directory where installed applications are stored */
private static final File sAppInstallDir =
new File(Environment.getDataDirectory(), "app");
/** Directory where installed application's 32-bit native libraries are copied. */
private static final File sAppLib32InstallDir =
new File(Environment.getDataDirectory(), "app-lib");
/** Directory where code and non-resource assets of forward-locked applications are stored */
private static final File sDrmAppPrivateInstallDir =
new File(Environment.getDataDirectory(), "app-private");

其中第一个sAppInstallDir就是我们要找的安装目录data/app,所以从这里入手,看看系统是怎么解析apk文件的。

全局搜索sAppInstallDir就可以找到scanDirTracedLI这个方法,从名字也能看出,它就是扫描该目录。内部也会解析manifest文件,所以从这里开始分析,我们跟随扫描的方法一步一步的往下看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
 private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
scanDirLI(scanDir, parseFlags, scanFlags, currentTime);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
...
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
//开启线程池来解析
parallelPackageParser.submit(file, parseFlags);
fileCount++;
}
...
}
//mService是线程池
public void submit(File scanFile, int parseFlags) {
mService.submit(() -> {
ParseResult pr = new ParseResult();
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
try {
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);
pp.setCacheDir(mCacheDir);
pp.setCallback(mPackageParserCallback);
pr.scanFile = scanFile;
//解析包
pr.pkg = parsePackage(pp, scanFile, parseFlags);
} catch (Throwable e) {
pr.throwable = e;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
try {
mQueue.put(pr);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
mInterruptedInThread = Thread.currentThread().getName();
}
});
}
@VisibleForTesting
protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
int parseFlags) throws PackageParser.PackageParserException {
return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
}

这一路跟随,最后到了PackageParser这个类中的parsePackage方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//packageFile文件包的路径
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
return parsePackage(packageFile, flags, false /* useCaches */);
}
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
if (parsed != null) {
return parsed;
}

long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
if (packageFile.isDirectory()) {
parsed = parseClusterPackage(packageFile, flags);
} else {
parsed = parseMonolithicPackage(packageFile, flags);
}
...
return parsed;
}

到这里我们就知道系统是通过parsePackage这个方法来解析apk文件的,那么我们是不是可以反射得到这个方法来解析我们自己的apk包呢?当然可以啦,最后我们只要拿到Package这个对象就行了,这个Package对象是parsePackage的内部类,它里面包含了AndroidManifest中的所有信息包含Permission,Activity,Service,Provider,广播等等。能拿到静态广播的信息就可以给它注册了。

所以现在接着前两篇文章来,在PluginManager类中添加一个解析apk获取广播并注册的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 反射系统源码来解析自己的apk 注册广播
*/
public void parseApkGetReceiver(){
try {
File file = new File(Environment.getExternalStorageDirectory()+File.separator+"p.apk");
if(!file.exists()){
Log.i(TAG,"插件包不存在");
}
//执行系统 PackageParser中的parsePackage方法来解析
Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
Object packageParser = packageParserClass.newInstance();

Method parsePackage = packageParserClass.getMethod("parsePackage",File.class,int.class);
//mPackage就是PackageParser中的Package类
Object mPackage = parsePackage.invoke(packageParser, file, PackageManager.GET_ACTIVITIES);
//分析 mPackage拿到里面的广播的集合
Field receiversField = mPackage.getClass().getDeclaredField("receivers");
//本质是ArrayList集合 public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
Object receivers = receiversField.get(mPackage);

ArrayList list = (ArrayList) receivers;
//集合内部的元素activity是PackageParse的内部类 是一个广播的封装类
for (Object activity : list) {
//拿到intentfilter
Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component");
Field intentsFile = componentClass.getDeclaredField("intents");
ArrayList<IntentFilter> intents = (ArrayList) intentsFile.get(activity);

Class<?> packageUserState = Class.forName("android.content.pm.PackageUserState");
Class<?> userHandle = Class.forName("android.os.UserHandle");
int userId = (int) userHandle.getDeclaredMethod("getCallingUserId").invoke(null);

//拿到广播的全类名
Method generateActivityInfoMethod = packageParserClass.
getDeclaredMethod("generateActivityInfo", activity.getClass(),
int.class,packageUserState,int.class);
generateActivityInfoMethod.setAccessible(true);
ActivityInfo activityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null, activity, 0, packageUserState.newInstance(),
userId);
//插件包中的广播的全类名
String receiverClassName = activityInfo.name;
Class<?> receiverClass = getClassLoader().loadClass(receiverClassName);
BroadcastReceiver broadcastReceiver = (BroadcastReceiver) receiverClass.newInstance();
for (IntentFilter intentFilter : intents) {
//注册广播
mContext.registerReceiver(broadcastReceiver,intentFilter);
}
}
}catch (Exception e){
e.printStackTrace();
}
}

写了这么多反射的代码,其实我们主要的目的只有两个
第一个拿到intentFilter,第二个拿到broadcastReceiver, 最后调用mContext.registerReceiver方法注册广播。其余代码都是为了找到这两个参数来服务的。

在插件包中定义一个广播并注册到manifest中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class PluginStaticReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {

Toast.makeText(context, "我是静态注册的广播,我收到广播啦", Toast.LENGTH_SHORT).show();

}
}
<receiver android:name=".PluginStaticReceiver">

<intent-filter>

<action android:name="plugin.package.PluginStaticReceiver" />

</intent-filter>

</receiver>

最后在MainActivity中加两个按钮,加载注册广播并发送广播

1
2
3
4
5
6
7
8
9
public void loadStaticReceiver(View view) {
PluginManager.getInstance(this).parseApkGetReceiver();
}

public void sendStaticReceiver(View view) {
Intent intent = new Intent();
intent.setAction("plugin.package.PluginStaticReceiver");
sendBroadcast(intent);
}

效果:

# 架构

コメント

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×