廣播插件的兩種實(shí)現(xiàn)模式
接上一篇插件化(一),已經(jīng)實(shí)現(xiàn)了通過(guò)插裝式實(shí)現(xiàn)activity插件和service插件,這兩種的實(shí)現(xiàn)是一樣的,但是廣播就不同了,廣播分為靜態(tài)廣播和動(dòng)態(tài)廣播,那么是怎么實(shí)現(xiàn)廣播插件的運(yùn)行呢。我們先從廣播的兩種注冊(cè)方式以及使用開(kāi)始分析。
靜態(tài)廣播和動(dòng)態(tài)廣播:
動(dòng)態(tài)廣播不需要再M(fèi)anifest中聲明
靜態(tài)廣播是需要的,聲明之后通過(guò)apk的安裝,系統(tǒng)解析manifest來(lái)實(shí)現(xiàn)廣播的注冊(cè),從而可以接受到跨進(jìn)程的消息
動(dòng)態(tài)廣播
動(dòng)態(tài)廣播其實(shí)和activity、service一樣,也是實(shí)現(xiàn)一個(gè)接口
public interface ProxyBroadCastInterface {
void attch(Context context);
void onReceive(Context context, Intent intent);
}
我們插件中對(duì)業(yè)務(wù)進(jìn)行處理的廣播
public class MyReceive extends BroadcastReceiver implements ProxyBroadCastInterface {
@Override
public void attch(Context context) {
// 廣播綁定成功
Toast.makeText(context,"廣播綁定成功",Toast.LENGTH_LONG).show();
}
@Override
public void onReceive(Context context, Intent intent) {
//接受到廣播
Toast.makeText(context," 接受到廣播",Toast.LENGTH_LONG).show();
}
}
然后在我們的插件的mainActivity中加兩個(gè)按鈕 一個(gè)是注冊(cè)廣播,一個(gè)發(fā)送廣播,
findViewById(R.id.mRegiestBroadCast).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
IntentFilter intent = new IntentFilter();
intent.addAction("com.plugin.app.receive");
//調(diào)用register方法 肯定要調(diào)用宿主的 所以重寫(xiě)baseactivity
registerReceiver(new MyReceive(), intent);
}
});
findViewById(R.id.mSendBroadCast).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction("com.plugin.app.receive");
sendBroadcast(intent);
}
});
那么我們這里調(diào)用注冊(cè)廣播和發(fā)送廣播的方法,就是調(diào)用的baseActivity ,那么我就重寫(xiě)B(tài)aseActivity的有關(guān)廣播的兩個(gè)方法
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return that.registerReceiver(receiver, filter);
}
@Override
public void sendBroadcast(Intent intent) {
that.sendBroadcast(intent);
}
所以最終都是調(diào)用我們宿主插件activity的兩個(gè)方法,在重寫(xiě)插件的activity的兩個(gè)方法
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
//收到調(diào)用注冊(cè)動(dòng)態(tài)廣播的申請(qǐng),那么我就幫你完成
IntentFilter newIntentFilter = new IntentFilter();
for (int i = 0; i < filter.countActions(); i++) {
newIntentFilter.addAction(filter.getAction(i));
}
return super.registerReceiver(new ProxyReceive(receiver.getClass().getName(),this), newIntentFilter);
}
下面是我們的代理的廣播
public class ProxyReceive extends BroadcastReceiver {
String className;
private ProxyBroadCastInterface receiveObj;
public ProxyReceive(String className,Context context) {
this.className = className;
//這里通過(guò)classname 得到class對(duì)象,然后
try {
Class<?> receiverClass = HookManager.getInstance().getClassLoader().loadClass(className);
Constructor constructorReceiver = receiverClass.getConstructor(new Class[]{});
receiveObj = (ProxyBroadCastInterface) constructorReceiver.newInstance(new Object[]{});
receiveObj.attch(context);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onReceive(Context context, Intent intent) {
receiveObj.onReceive(context, intent);
}
}
上運(yùn)行效果

上面完成對(duì)動(dòng)態(tài)注冊(cè)的廣播插件的運(yùn)行,下面就到靜態(tài)注冊(cè)了
靜態(tài)注冊(cè)廣播
- 內(nèi)存消耗高,因?yàn)槌qv內(nèi)存的。但是動(dòng)態(tài)注冊(cè)的廣播是受activity的生命周期影響的
靜態(tài)廣播是沒(méi)有跟隨我們的app啟動(dòng),而是手機(jī)啟動(dòng)的時(shí)候就已經(jīng)被加載到內(nèi)存中了,但是前提是你這個(gè)app已經(jīng)安裝到手機(jī)上了,所以想要實(shí)現(xiàn)我們的靜態(tài)廣播插件的實(shí)現(xiàn),的對(duì)apk的安裝進(jìn)行分析了。
apk的安裝原理
Android有四種安裝方式
-
已安裝的系統(tǒng)應(yīng)用安裝其他應(yīng)用
特點(diǎn):沒(méi)有安裝界面,直接安裝
-
手機(jī)應(yīng)用市場(chǎng)安裝apk
特點(diǎn):直接安裝,沒(méi)有安裝界面
-
ADB工具安裝
特點(diǎn):無(wú)安裝界面
-
第三方應(yīng)用安裝
特點(diǎn):有安裝界面,是由PackageInstaller.apk應(yīng)用 來(lái)處理安裝及卸載過(guò)程的界面。
那么安裝時(shí),系統(tǒng)幫我們做了什么事,BroadCastReceive又是怎么注冊(cè)的
- 安裝時(shí)吧apk文件復(fù)制到data/app這個(gè)目錄 (用戶程序安裝的目錄) .
- 然后開(kāi)辟存放應(yīng)用數(shù)據(jù)的目錄: /data/data/ 包名
- 將apk中的dex文件安裝到data/dalvik-cache目錄下(dex文件是dalvik虛擬機(jī)的可執(zhí)行文件,其大小約為apk文件大小的四分之一)。
所以安裝一個(gè)apk 系統(tǒng)幫我一共做了三件事,復(fù)制apk、開(kāi)辟存放數(shù)據(jù)的目錄、在吧dex文件進(jìn)行拷貝。
真正加載廣播,是在系統(tǒng)發(fā)生啟動(dòng)的時(shí)候,這個(gè)時(shí)候回將手機(jī)上所有的app都安裝一遍。而我們的靜態(tài)廣播是注冊(cè)在manifest文件中的,當(dāng)我們的apk通過(guò)PMS安裝的時(shí)候回去解析Manifest文件,將其中的四大組件拿出來(lái)進(jìn)行注冊(cè)。那我們的插件app是沒(méi)有安裝的,那么如何將它的清單文件中的廣播拿出來(lái)呢,所以我們有必要來(lái)梳理一些apk的安裝,也就是PMS。
PMS(PackageManagerService)服務(wù)
PMS服務(wù)是在開(kāi)機(jī)的時(shí)候由SystemServer (系統(tǒng)服務(wù)去調(diào)用的)
SystemServer.java
//這是它的main方法
public static void main(String[] args) {
new SystemServer().run();
}
private void run(){
...
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
...
}
那么我們分析一下系統(tǒng)是怎么去掃描data/app下面的程序呢
先看這個(gè)PackageManagerService.java文件
//這個(gè)是main方法,當(dāng)系統(tǒng)服務(wù)運(yùn)行起來(lái)就會(huì)調(diào)用PMS的main方法
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
//調(diào)用了構(gòu)造方法
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
m.enableSystemUserPackages();
ServiceManager.addService("package", m);
final PackageManagerNative pmn = m.new PackageManagerNative();
ServiceManager.addService("package_native", pmn);
return m;
}
可以看到main方法里面調(diào)用了構(gòu)造方法 ,看看構(gòu)造方法里面做了什么事
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore){
...
synchronized (mPackages){
...
//這個(gè)dataDir目錄就是/data
File dataDir = Environment.getDataDirectory();
mAppInstallDir = new File(dataDir, "app");//這個(gè)目錄就是我們第三方app程序的目錄
mAppLib32InstallDir = new File(dataDir, "app-lib");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
...
//下面會(huì)調(diào)用這個(gè)方法 通過(guò)方法名字,可以看出來(lái) 是掃描/data/app這個(gè)目錄下的文件 將路徑傳進(jìn)去
scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
}
}
那我們?cè)诜治鰭呙鑔ata/app 這個(gè)里面做了什么事呢
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = dir.listFiles();//顯示吧所有的文件全部拿到 盤(pán)算是不是為空 如果為空,直接返回
if (ArrayUtils.isEmpty(files)) {
Log.d(TAG, "No files in app dir " + dir);
return;
}
//接著遍歷data/app下面的目錄
for (File file : files) {
//如果是一個(gè)apk文件 那這個(gè)isPackage就為true
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
...
parallelPackageParser.submit(file, parseFlags);
}
}
從上面這個(gè)submit方法可以發(fā)現(xiàn),是將當(dāng)前一個(gè)apk文件穿進(jìn)去了
接下來(lái)到這個(gè)ParallelPackageParser.java文件了,那么我們看它的submit方法
/**
@params scanFile 當(dāng)前要解析的apk文件
*/
public void submit(File scanFile, int parseFlags) {
...
//看到在解析這個(gè)apk文件時(shí) new了一個(gè)PackageParse這個(gè)javabean
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);
pp.setCacheDir(mCacheDir);
pp.setCallback(mPackageParserCallback);
pr.scanFile = scanFile;
//調(diào)用了PackageParse里面的parsePackage方法,
pr.pkg = parsePackage(pp, scanFile, parseFlags);
...
}
從上面看到 調(diào)用了這個(gè)PackageParse這個(gè)類(lèi),很重要 ,那么我們?nèi)タ纯磒arsePackage這個(gè)方法到是怎么去解析一個(gè)apk文件的。
當(dāng)前跳到了 PackageParse.java 文件中
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
...
//packageFile 就是從PMS那邊一步一步傳過(guò)來(lái)要解析的當(dāng)前的apk文件
//最后調(diào)用了這個(gè)方法
parsed = parseMonolithicPackage(packageFile, flags);
...
}
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
final AssetManager assets = newConfiguredAssetManager();
...
//調(diào)用這個(gè)方法 并且里面new了一個(gè)AssetManager 我們可以猜到里面有調(diào)用addAssetPath這個(gè)方法
final Package pkg = parseBaseApk(apkFile, assets, flags);
pkg.setCodePath(apkFile.getAbsolutePath());
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
...
}
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
throws PackageParserException {
...
res = new Resources(assets, mMetrics, null);
// private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
}
從上面openXmlResourceParser 這個(gè)方法看出來(lái) 現(xiàn)在是解析Manifest.xml文件并且最終得到一個(gè)了一個(gè)XmlResourceParser對(duì)象,然后將這個(gè)對(duì)象傳到parseBaseApk這個(gè)方法得到當(dāng)前apk的封裝對(duì)象PackageParse.package 它是PackageParse的內(nèi)部類(lèi)
可以看下類(lèi)結(jié)構(gòu)圖:

這個(gè)xml解析我跟到最后發(fā)現(xiàn)是一個(gè)native方法,所以也不必看了,直接看parseBaseApk這個(gè)方法
還是在PackageParser.java類(lèi)里面
/**
@params apkPath 當(dāng)前要解析的apk文件
@params res 是在上一步new出來(lái)的
@params parser 是manifest的解析器
*/
private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {
...
//可以看到它將我們的pkgName和apkPath轉(zhuǎn)成一個(gè)數(shù)組,然后添加到我們的res里面了
String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath);
if (overlayPaths != null && overlayPaths.length > 0) {
for (String overlayPath : overlayPaths) {
res.getAssets().addOverlayPath(overlayPath);
}
}
...
final Package pkg = new Package(pkgName);
...
//又調(diào)用這個(gè)方法
return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
}
一入源碼深似海,反正都是各種調(diào)用,其實(shí)發(fā)現(xiàn)真正做事情就那么幾個(gè)方法,只能時(shí)候封裝的太好了,題外話。。
再看看parseBaseApkCommon 這個(gè)方法
private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
IOException {
...
//private static final String TAG_APPLICATION = "application";
if (tagName.equals(TAG_APPLICATION)) {
//從這里我們看出來(lái) 才TM是真正解析application標(biāo)簽了,前面分析了那么多,現(xiàn)在才是目的
...
//又來(lái)一個(gè)調(diào)用的方法 只能進(jìn)去看看了
if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
return null;
}
}
}
// 從名字就能看出來(lái) 解析application標(biāo)簽的
private boolean parseBaseApplication(Package owner, Resources res,
XmlResourceParser parser, int flags, String[] outError)
...
while(...){
String tagName = parser.getName();
//得到我們當(dāng)前解析的標(biāo)簽名
if (tagName.equals("activity")) {
//如果是activity
Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
owner.baseHardwareAccelerated);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.activities.add(a);
} else if (tagName.equals("receiver")) {
// 如果是receiver
Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
true, false);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.receivers.add(a);
} else if (tagName.equals("service")) {
Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
if (s == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.services.add(s);
} else if (tagName.equals("provider")) {
Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
if (p == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.providers.add(p);
}
}
}
從上面我們可以看出來(lái),通過(guò)xml解析將四大組件解析出來(lái),放到Package這個(gè)類(lèi)的四個(gè)集合中去,如下
PackageParse$Package
public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
public final ArrayList<Service> services = new ArrayList<Service>(0);
我們看到存放activity的集合的泛型的是Activity,這里要說(shuō)明的是不是我們四大組件的activity,而是我們的PackageParse的內(nèi)部類(lèi) Activity(PackageParse$Package)。并且activity和receiver集合的泛型都是一樣的,那么這是為啥?因?yàn)槲覀冊(cè)谇鍐挝募?里面注冊(cè)activity或者receiver都要聲明IntetFilter,所以在設(shè)計(jì)的時(shí)候,能復(fù)用就復(fù)用。對(duì)于Service和Provider就不一樣了。
那么我們就看看是如何是怎樣將標(biāo)簽變成一個(gè)Activity的,看parseActivity方法
private Activity parseActivity(Package owner, Resources res,
XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs,
boolean receiver, boolean hardwareAccelerated)
throws XmlPullParserException, IOException {
...
//如果是reveicer就是true 否則就是false
cachedArgs.mActivityArgs.tag = receiver ? "<receiver>" : "<activity>";
if (!receiver) {
//如果是activity
}else{
// 如果是receiver
}
...
//開(kāi)始解析activity和receiver的intent-Filter
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > outerDepth)) {
if (parser.getName().equals("intent-filter")) {
//可以看到這里就解析的intent-filter
ActivityIntentInfo intent = new ActivityIntentInfo(a);
...
//這個(gè)a 就是我們的Activity,intents就是用來(lái)裝Intent的一個(gè)集合,這里是ActivityIntenrInfo,那么ActivityIntentInfo是繼承IntentInfo,IntentInfo又是繼承自IntentFilter的
a.intents.add(intent);
}
}
}
那么是在哪里解析activity和receiver的name的呢,那一個(gè)標(biāo)簽肯定有名字的對(duì)吧,通過(guò)查找,發(fā)現(xiàn)名字是在一個(gè)
Activity類(lèi)里面的ActivityInfo里面 而且ActivityInfo繼承ComponentInfo 繼承PackageItemInfo,最終發(fā)現(xiàn)在PackageItemInfo這個(gè)類(lèi)里面,看源碼的注釋可以看到
public class PackageItemInfo {
/**
* Public name of this item. From the "android:name" attribute.
*/
public String name;// android:name 就是我們要找的組件的名字
}
而且這個(gè)ActivityInfo 是通過(guò)調(diào)用這個(gè)方法生成的
public static final ActivityInfo generateActivityInfo(ActivityInfo ai, int flags,
PackageUserState state, int userId) {
if (ai == null) return null;
if (!checkUseInstalledOrHidden(flags, state, ai.applicationInfo)) {
return null;
}
// This is only used to return the ResolverActivity; we will just always
// make a copy.
ai = new ActivityInfo(ai);
ai.applicationInfo = generateApplicationInfo(ai.applicationInfo, flags, state, userId);
return ai;
}
至此分析的差不多了 ,也知道開(kāi)啟啟動(dòng),Android為啥啟動(dòng)的這么慢,就是因?yàn)橐闅v所有的apk,安裝一遍,解析MainFest文件這些操作。
插件中靜態(tài)廣播的解析
通過(guò)上面的分析,知道廣播是怎樣解析的,怎樣加載到內(nèi)存中去的,通過(guò)什么樣的方式,那些方法,怎么調(diào)用的,最終又是封裝在那些類(lèi)里面,知道了這些原理,我們才可以去加載插件apk中的一個(gè)靜態(tài)廣播,其實(shí)上面分析了這么多,就是為了干這個(gè)事情,也順帶著把我們的PMS稍微分析了一遍
那么我們就開(kāi)始解析我們插件中的廣播,在HookManager 的loadPathToPlugin這個(gè)方法中解析我們的清單文件
/**
* 通過(guò)解析清單文件來(lái) 拿到靜態(tài)廣播并且進(jìn)行注冊(cè)
*
* @param activity
* @param path
*/
private void parseReceivers(Activity activity, String path) {
try {
//我們知道解析一個(gè)apk文件的入口就是PackageParse.parsePackage 這個(gè)方法
//所以我們使用反射 來(lái)調(diào)用這個(gè)方法 最終得到了一個(gè) PackageParse$Package 這個(gè)類(lèi)
Class<?> mPackageParseClass = Class.forName("android.content.pm.PackageParser");
Method mParsePackageMethod = mPackageParseClass.getDeclaredMethod("parsePackage", File.class, int.class);
Object mPackageParseObj = mPackageParseClass.newInstance();
Object mPackageObj = mParsePackageMethod.invoke(mPackageParseObj, new File(path), PackageManager.GET_ACTIVITIES);
//解析出來(lái)的receiver就存在PackageParse$Package 這個(gè)類(lèi)里面的一個(gè)receivers集合里面
Field mReceiversListField = mPackageObj.getClass().getDeclaredField("receivers");
//然后得到反射得到這個(gè)屬性的值 最終得到一個(gè)集合
List mReceiverList = (List) mReceiversListField.get(mPackageObj);
//接下來(lái)我們要拿到 IntentFilter 和name屬性 這樣才能反射創(chuàng)建對(duì)象,動(dòng)態(tài)在宿主里面注冊(cè)廣播
Class<?> mComponetClass = Class.forName("android.content.pm.PackageParser$Component");
Field mIntentFields = mComponetClass.getDeclaredField("intents");
//這兩行是為了調(diào)用generateActivityInfo 而反射拿到的參數(shù)
Class<?> mPackageParse$ActivityClass = Class.forName("android.content.pm.PackageParser$Activity");
Class<?> mPackageUserStateClass = Class.forName("android.content.pm.PackageUserState");
Object mPackzgeUserStateObj = mPackageUserStateClass.newInstance();
// 拿到generateActivityInfo這個(gè)方法
Method mGeneReceiverInfo = mPackageParseClass.getMethod("generateActivityInfo", mPackageParse$ActivityClass, int.class, mPackageUserStateClass, int.class);
Class<?> mUserHandlerClass = Class.forName("android.os.UserHandle");
Method getCallingUserIdMethod = mUserHandlerClass.getDeclaredMethod("getCallingUserId");
int userId = (int) getCallingUserIdMethod.invoke(null);
//然后for循環(huán) 去拿到name和 intentFilter
for (Object activityObj : mReceiverList) {
//調(diào)用generateActivityInfo
// 這個(gè)是我們要調(diào)用的方法的形參 public static final ActivityInfo generateActivityInfo(Activity a, int flags,PackageUserState state, int userId);
//得到一個(gè)ActivityInfo
ActivityInfo info = (ActivityInfo) mGeneReceiverInfo.invoke(mPackageParseObj, activityObj, 0, mPackzgeUserStateObj, userId);
//拿到這個(gè)name 相當(dāng)于我們?cè)谇鍐挝募蠥ndroid:name 這樣,是一個(gè)全類(lèi)名,然后通過(guò)反射去創(chuàng)建對(duì)象
BroadcastReceiver broadcastReceiver = (BroadcastReceiver) getClassLoader().loadClass(info.name).newInstance();
//在拿到IntentFilter
List<? extends IntentFilter> intents = (List<? extends IntentFilter>) mIntentFields.get(activityObj);
//然后直接調(diào)用registerReceiver方法發(fā)
for (IntentFilter intentFilter : intents) {
activity.registerReceiver(broadcastReceiver, intentFilter);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
ok 然后創(chuàng)建廣播和插件進(jìn)行注冊(cè),下面是效果圖
