接着前幾篇文章來:由於插件中的廣播是在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這個類中
我們從文件目錄入手,可以看到它有幾個靜態的成員變量
/** 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文件,所以從這裏開始分析,我們跟隨掃描的方法一步一步的往下看。
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方法。
//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獲取廣播並註冊的方法
/**
* 反射系統源碼來解析自己的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中
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中加兩個按鈕,加載註冊廣播併發送廣播
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);
}
效果: