DroidPlugin之獲取插件APK信息

最近在研究DroidPlugin源碼及其實現(xiàn)機制葱淳,讀這種大神級的代碼甚是費腦。我基本是逐行閱讀代碼谦铃,為求甚解耘成,花了不少時間和心思。這里驹闰,我也做個筆記供大家參考瘪菌,這樣就可以少走彎路。

Run it

為了解其工作DroidPlugin的工作原理嘹朗,我們第一步應(yīng)該是把Demo運行起來师妙,其項目地址是:DroidPlugin。作者的Demo比較復雜骡显,畢竟他要測試到所有的功能疆栏。所以這里,建議各位自己重寫一個簡單的Demo惫谤,然后運行起來壁顶。這樣起碼你知道如何使用DroidPlugin了。
在Demo中溜歪,與我們打交道的類是PluginManager若专,用該類中以下方法就可以完成最基本的插件調(diào)用。

//判斷插件服務(wù)是否處于連接狀態(tài)
public boolean isConnected() {
    return mHostContext != null && mPluginManager != null;
}

//將插件的ServiceConnection添加到緩存中
public void addServiceConnection(ServiceConnection sc) {
    sServiceConnection.add(new WeakReference<ServiceConnection>(sc));
}

//將插件的ServiceConnection從緩存中移除
public void removeServiceConnection(ServiceConnection sc) {
    Iterator<WeakReference<ServiceConnection>> iterator = sServiceConnection.iterator();
    while (iterator.hasNext()) {
        WeakReference<ServiceConnection> wsc = iterator.next();
        if (wsc.get() == sc) {
            iterator.remove();
        }
    }
}

//安裝插件
public int installPackage(String filepath, int flags) throws RemoteException {
    try {
        if (mPluginManager != null) {
            int result = mPluginManager.installPackage(filepath, flags);
            Log.w(TAG, String.format("%s install result %d", filepath, result));
            return result;
        } else {
            Log.w(TAG, "Plugin Package Manager Service not be connect");
        }
    } catch (RemoteException e) {
        throw e;
    } catch (Exception e) {
        Log.e(TAG, "forceStopPackage", e);
    }
    return -1;
}
//刪除插件
public void deletePackage(String packageName, int flags) throws RemoteException {
    try {
        if (mPluginManager != null) {
            mPluginManager.deletePackage(packageName, flags);
        } else {
            Log.w(TAG, "Plugin Package Manager Service not be connect");
        }
    } catch (RemoteException e) {
        throw e;
    } catch (Exception e) {
        Log.e(TAG, "deletePackage", e);
    }
}

Analyse it

首先來看看PluginManager相關(guān)的類圖蝴猪。


PluginManager相關(guān)的類圖

從類圖中我們可以看到调衰,整個插件管理器是通過Binder機制來完成運作的膊爪。絕大部分插件管理相關(guān)的方法都在IPluginManagerImpl類中實現(xiàn)。

獲取插件Apk的基本信息

Apk包信息用PackageInfo來表示嚎莉,通過PackageInfo我們可以獲取包名米酬、版本號、版本代碼以及四大組件相關(guān)信息等趋箩。通過以下類圖赃额,我們可以知道包信息相關(guān)的內(nèi)容。


包相關(guān)信息類圖.jpg

我們知道叫确,要獲取一個Apk的包相關(guān)信息跳芳,可以通過PackageManager來獲取,但是這僅限于已經(jīng)安裝的apk竹勉,對于一個未安裝的apk該如何獲取飞盆。
在PackageManager中有一個名為getPackageArchiveInfo的方法,代碼如下次乓。

 /**
     * Retrieve overall information about an application package defined
     * in a package archive file
     *
     * @param archiveFilePath The path to the archive file
     * @param flags Additional option flags. Use any combination of
     * @return Returns the information about the package. Returns
     * null if the package could not be successfully parsed.
     *
     * @see #GET_ACTIVITIES
     * @see #GET_GIDS
     * @see #GET_CONFIGURATIONS
     * @see #GET_INSTRUMENTATION
     * @see #GET_PERMISSIONS
     * @see #GET_PROVIDERS
     * @see #GET_RECEIVERS
     * @see #GET_SERVICES
     * @see #GET_SIGNATURES
     *
     */
    public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
        final PackageParser parser = new PackageParser();
        final File apkFile = new File(archiveFilePath);
        try {
            PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
            if ((flags & GET_SIGNATURES) != 0) {
                parser.collectCertificates(pkg, 0);
                parser.collectManifestDigest(pkg);
            }
            PackageUserState state = new PackageUserState();
            return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
        } catch (PackageParserException e) {
            return null;
        }
    }

通過getPackageArchiveInfo方法吓歇,我們可以讀取未安裝插件的AndroidManifest.xml文件來獲取packageName、versionName檬输、versionCode照瘾、ActivityInfo、ServiceInfo丧慈、InstrumentationInfo等信息析命。

插件安裝

插件的安裝是通過IPluginManagerImpl的installPackage方法來完成的。

public int installPackage(String filepath, int flags) throws RemoteException {
    String apkfile = null;
    try {
        PackageManager pm = mContext.getPackageManager();
        /**
             * 調(diào)用getPackageArchiveInfo方法獲取插件包的信息
             * */
        PackageInfo info = pm.getPackageArchiveInfo(filepath, 0);
        if (info == null) {
            return PackageManagerCompat.INSTALL_FAILED_INVALID_APK;
        }
        /**
             * 獲取插件將要放置的路徑逃默,/data/data/com.HOST.PACKAGE/Plugin/PLUGIN.PKG/apk/apk-1.apk下
             * */
        apkfile = PluginDirHelper.getPluginApkFile(mContext, info.packageName);
        /**
             * 這里是是判斷是否允許替換相同的插件包鹃愤,這種寫法應(yīng)該是模仿系統(tǒng)的寫法。
             * 可以通過PackageManager的getPackageArchiveInfo方法源碼來看完域。
             * 通過標志位與系統(tǒng)預(yù)設(shè)類變量進行與運算
             * */
        if ((flags & PackageManagerCompat.INSTALL_REPLACE_EXISTING) != 0) {
            /**
                 * 強制停止插件進程软吐,所有的插件都是運行在獨立的進程
                 * */
            forceStopPackage(info.packageName);
            /**
                 * 刪除先前插件的緩存,ApplicationInfo的dataDir路徑下的所有file
                 * */
            if (mPluginCache.containsKey(info.packageName)) {
                deleteApplicationCacheFiles(info.packageName, null);
            }
            /**
                 * 刪除原有插件apk
                 * */
            new File(apkfile).delete();
            /**
                 * 將插件apk復制到apkfile路徑
                 * */
            Utils.copyFile(filepath, apkfile);
            /**
                 * PluginPackageParser可以理解為插件的PackageManager吟税,作者基本上把所有組件相關(guān)的信息都解析出來了凹耙,牛逼!
                 * 所以作者寫了PluginPackageParser這個類來獲取四大組件肠仪、簽名肖抱、權(quán)限等相關(guān)信息。
                 * */
            PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile));
            /**
                 * 獲取證書信息
                 * */
            parser.collectCertificates(0);
            /**
                 * 獲取插件的權(quán)限信息和簽名信息
                 * */
            PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES);
            if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) {
                for (String requestedPermission: pkgInfo.requestedPermissions) {
                    boolean b = false;
                    /**
                         * 如果插件申明的權(quán)限异旧,宿主沒有的話意述,則安裝插件失敗
                         * **/
                    try {
                        b = pm.getPermissionInfo(requestedPermission, 0) != null;
                    } catch(NameNotFoundException e) {}
                    if (!mHostRequestedPermission.contains(requestedPermission) && b) {
                        Log.e(TAG, "No Permission %s", requestedPermission);
                        new File(apkfile).delete();
                        return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION;
                    }
                }
            }
            /**
                 * 保存簽名信息到DISK
                 * */
            saveSignatures(pkgInfo);
            //                if (pkgInfo.reqFeatures != null && pkgInfo.reqFeatures.length > 0) {
            //                    for (FeatureInfo reqFeature : pkgInfo.reqFeatures) {
            //                        Log.e(TAG, "reqFeature name=%s,flags=%s,glesVersion=%s", reqFeature.name, reqFeature.flags, reqFeature.getGlEsVersion());
            //                    }
            //                }
            /**
                 * 把插件的lib下so文件拷貝到/data/data/.../lib 下
                 * */
            copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0));
            //優(yōu)化dex
            dexOpt(mContext, apkfile, parser);
            //緩存已經(jīng)解析出來插件的PluginPackageParser信息(類似PackageInfo)
            mPluginCache.put(parser.getPackageName(), parser);
            //回調(diào)方法,通知已經(jīng)完成
            mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName());
            //發(fā)送安裝完成的廣播
            sendInstalledBroadcast(info.packageName);
            return PackageManagerCompat.INSTALL_SUCCEEDED;
        }
        //以下分析與上述通覆蓋安裝同理
        else {
        //代碼省略........
           }
}

通過以上分析,我們可以知道荤崇,安裝插件主要做了解析插件apk的PackageInfo拌屏,獲取證書信息,讀取so文件术荤,加載dex等倚喂。前面我們有提到過使用getPackageArchiveInfo可以獲取插件的PackageInfo,但是在installPackage方法中有PluginPackageParser類實例創(chuàng)建的過程喜每,用PluginPackageParser來完成包相關(guān)信息的解析务唐。
PluginPackageParser主要是通過反射來獲取PackageInfo雳攘,而且還要做版本適配带兜,很復雜。那為什么作者不直接getPackageArchiveInfo方法獲取的PackageInfo吨灭?
上面有介紹到PackageInfo類相關(guān)的類圖刚照,其實整個PackageInfo所包含的信息是相當多的。我們通過getPackageArchiveInfo獲取插件的PackageInfo僅僅是讀取AndroidManifest.xml文件上的信息喧兄,有些信息是無法獲取无畔,比如ApplicationInfo中的processName、publicSourceDir吠冤、sourceDir浑彰,PackageInfo中的gids等,這些信息都是需要安裝或運行后才能確定的拯辙。
下面郭变,首先來看看PluginPackageParser相關(guān)的類圖


PluginPackageParser相關(guān)類圖

整個解析過程,看似很復雜涯保,但是只要掌握了核心思想就很好理解了诉濒。

PluginPackageParser分析

我們繼續(xù)分析上面提到過的getPackageArchiveInfo方法。

public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
        //PackageParser用于解析在disk上的apk安裝包
        final PackageParser parser = new PackageParser();
        final File apkFile = new File(archiveFilePath);
        try {
            //獲取Package對象
            PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
            if ((flags & GET_SIGNATURES) != 0) {
                //獲取證書信息
                parser.collectCertificates(pkg, 0);
                parser.collectManifestDigest(pkg);
            }
           //每個用戶關(guān)于package的狀態(tài)信息夕春,例如package是否安裝未荒,是否隱藏,是否停止等
            PackageUserState state = new PackageUserState();
           //調(diào)用generatePackageInfo方法返回PackageInfo
            return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
        } catch (PackageParserException e) {
            return null;
        }
    }

    /**
     *從以上代碼分析中及志,我們可以看到PackageParser是完成包解析的核心步驟片排,接著分析
     *PackageParser類中的generatePackageInfo方法,因為generatePackageInfo方法在PackageParser中重載了速侈, 
     *我們直接看該方法最終實現(xiàn)
     **/
public static PackageInfo generatePackageInfo(PackageParser.Package p,
            int gids[], int flags, long firstInstallTime, long lastUpdateTime,
            Set<String> grantedPermissions, PackageUserState state, int userId) {

        if (!checkUseInstalledOrHidden(flags, state)) {
            return null;
        }
        //創(chuàng)建PackageInfo率寡,將Package中的信息賦給PackageInfo
        PackageInfo pi = new PackageInfo();
        pi.packageName = p.packageName;
        pi.splitNames = p.splitNames;
        pi.versionCode = p.mVersionCode;
        pi.baseRevisionCode = p.baseRevisionCode;
        pi.splitRevisionCodes = p.splitRevisionCodes;
        pi.versionName = p.mVersionName;
        pi.sharedUserId = p.mSharedUserId;
        pi.sharedUserLabel = p.mSharedUserLabel;
        pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
        pi.installLocation = p.installLocation;
        pi.coreApp = p.coreApp;
        //......省略中間代碼,
        //生成ActivityInfo是通過調(diào)用generateActivityInfo方法锌畸,其他組件或包相關(guān)信息在PackageParser都有對應(yīng)的生產(chǎn)方法勇劣。
        if ((flags&PackageManager.GET_ACTIVITIES) != 0) {
            int N = p.activities.size();
            if (N > 0) {
                if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
                    pi.activities = new ActivityInfo[N];
                } else {
                    int num = 0;
                    for (int i=0; i<N; i++) {
                        if (p.activities.get(i).info.enabled) num++;
                    }
                    pi.activities = new ActivityInfo[num];
                }
                for (int i=0, j=0; i<N; i++) {
                    final Activity activity = p.activities.get(i);
                    if (activity.info.enabled
                        || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
                        pi.activities[j++] = generateActivityInfo(p.activities.get(i), flags,
                                state, userId);
                    }
                }
            }
        }
        //省略部分代碼.......
        }
        return pi;
    }

通過以上簡要分析,我們可以知道在PackageParser類中有很多可以獲取包信息的方法,比如獲取PackageInfo的generatePackageInfo比默,獲取ServiceInfo的generateServiceInfo等幻捏,這些方法的名字絕大部分都是以generate開頭的。
DroidPlugin作者通過反射的方法來獲取PackageInfo命咐、ActivityInfo等信息篡九,因為PackageParser是hide類,所以每個系統(tǒng)版本獲取包相關(guān)信息的實現(xiàn)可能不一樣醋奠,所以作者做了版本兼容榛臼,這需要讀完各個系統(tǒng)版本PackageParser類源碼,辛苦作者了窜司!
作者自己也定義了一個PackageParser類沛善,用于完成與系統(tǒng)PackageParser類類似的功能。在PluginPackageParser的構(gòu)造方法中通過調(diào)用自定義PackageParser類的newPluginParser方法來獲取包相關(guān)信息塞祈。

public PluginPackageParser(Context hostContext, File pluginFile) throws Exception {
        mHostContext = hostContext;
        mPluginFile = pluginFile;
        //通過反射系統(tǒng)PackageParser相關(guān)方法來完成包相關(guān)信息解析
        mParser = PackageParser.newPluginParser(hostContext);
        //解析插件包
        mParser.parsePackage(pluginFile, 0);
        mPackageName = mParser.getPackageName();
        mHostPackageInfo = mHostContext.getPackageManager().getPackageInfo(mHostContext.getPackageName(), 0);
        //以下操作是將解析到的ActivityInfo進行緩存操作金刁。
        //下面代碼出現(xiàn)ComponentName,我想作者是為了模仿系統(tǒng)PackageManager類中相關(guān)的api
        //例如PackageManager中獲取ActivityInfo的方法议薪,getActivityInfo(ComponentName,int)
        List datas = mParser.getActivities();
        for (Object data : datas) {
            ComponentName componentName = new ComponentName(mPackageName, mParser.readNameFromComponent(data));
            synchronized (mActivityObjCache) {
                mActivityObjCache.put(componentName, data);
            }
            synchronized (mActivityInfoCache) {
                ActivityInfo value = mParser.generateActivityInfo(data, 0);
               //這個方法很重要尤蛮,前面有提到過未安裝的apk某些屬性是無法獲取到的,
               //該方法完成了ApplicationInfo中部分字段的賦值斯议,比如processName产捞、dataDir等。
               //類似的還有fixPackageInfo方法哼御。
                fixApplicationInfo(value.applicationInfo);
                if (TextUtils.isEmpty(value.processName)) {
                    value.processName = value.packageName;
                }
                mActivityInfoCache.put(componentName, value);
            }

            List<IntentFilter> filters = mParser.readIntentFilterFromComponent(data);
            synchronized (mActivityIntentFilterCache) {
                mActivityIntentFilterCache.remove(componentName);
                mActivityIntentFilterCache.put(componentName, new ArrayList<IntentFilter>(filters));
            }
        }
    //......省略代碼
    }

至此坯临,parser package的過程大致講完,如果遺漏的地方艇搀,歡迎指出尿扯。建議大家多讀下DroidPlugin關(guān)于包解析的源碼,作者某些思路相當精彩焰雕。

最后

不知道衷笋,大家有沒有一個疑問,其實我們可以通過getPackageArchiveInfo方法來獲取PackageInfo矩屁,然后通過類似fixApplicationInfo來完成包解析辟宗。但是作者并沒有這樣做,而是全部通過反射來完成吝秕。
我覺得應(yīng)該有以下考量泊脐。
1.全部使用反射獲取包信息,邏輯更清楚烁峭,而且更保障容客。
2.有些信息getPackageArchiveInfo獲取不到秕铛,例如PermissionGroupInfo、PermissionInfo缩挑、IntentFilterInfo等但两。
3.作者將PluginParserManager完成類似PackageManager功能,比如getActivityInfo(ComponentName,int)方法供置,必須通過反射獲取到PackageParser.Activity類實例谨湘。
如有疑問,歡迎交流芥丧!非常感謝紧阔!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市续担,隨后出現(xiàn)的幾起案子擅耽,更是在濱河造成了極大的恐慌,老刑警劉巖赤拒,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秫筏,死亡現(xiàn)場離奇詭異,居然都是意外死亡挎挖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門航夺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蕉朵,“玉大人,你說我怎么就攤上這事阳掐∈夹疲” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵缭保,是天一觀的道長汛闸。 經(jīng)常有香客問我,道長艺骂,這世上最難降的妖魔是什么诸老? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮钳恕,結(jié)果婚禮上别伏,老公的妹妹穿的比我還像新娘。我一直安慰自己忧额,他們只是感情好厘肮,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著睦番,像睡著了一般类茂。 火紅的嫁衣襯著肌膚如雪耍属。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天巩检,我揣著相機與錄音恬涧,去河邊找鬼。 笑死碴巾,一個胖子當著我的面吹牛溯捆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播厦瓢,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼提揍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了煮仇?” 一聲冷哼從身側(cè)響起劳跃,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浙垫,沒想到半個月后刨仑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡夹姥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年杉武,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辙售。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡轻抱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出旦部,到底是詐尸還是另有隱情祈搜,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布士八,位于F島的核電站容燕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏婚度。R本人自食惡果不足惜蘸秘,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望陕见。 院中可真熱鬧秘血,春花似錦、人聲如沸评甜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忍坷。三九已至粘舟,卻和暖如春熔脂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柑肴。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工霞揉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晰骑。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓适秩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親硕舆。 傳聞我的和親對象是個殘疾皇子秽荞,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容