1. 程式人生 > >Android PackageManager原始碼淺析以及靜默安裝實現方式

Android PackageManager原始碼淺析以及靜默安裝實現方式

Aandroid應用管理 

   >http://blog.csdn.net/sk719887916/article/details/50314017 skay整理。

   >2016了 本篇成了我的開年之博,距上次做靜默安裝和輔助服務已經有半年之多,最近一直在做專案中的外掛功能,也一直沒時間整理平時接觸的東西,甚至年終總結,今天就從經常用到的知識來開始2016的道路吧。(寫在2016年初)


  
Aandroid的應用管理主要由PMS(PackageManagerService)來負責管理;上層上來由PackageManager來進行管理,通過PM我們可以得到裝置上的所有安裝包資訊,包括未安裝和安裝過的, 未安裝的包資訊採用反射和未暴露的API也可以進行深度解析得到我們想要的資訊。而應用的的安裝和解除安裝也有PM負責。

PMS



今天我們主要說一下PackageManager,至於PMS來說和上層有Binder進行互動, PMS在實際開發中我們很少直接用到,但是我們上層通過PM來獲取的一些基礎資訊,都需要PMS來呼叫底層,當通過看原始碼得知PackageManager沿用了Android面向介面程式設計的風格,比如`viewRoot`,`WindowManger`,`ActivityManager` 都採用了面向介面程式設計,這些Mgr為我們提供了一些基礎的功能介面,具體都由各自的Service來動態注入Impl,就是我們通常說的熱插拔,至於這麼寫的好處嗎 這裡稍微說兩句,在接觸過java程式設計久的朋友都知道面向介面的可擴充套件性很強,因為安卓原始碼也需要升級,谷歌工程師在下一個版本中或許就會新增一些api,那麼這樣設計的理念也便於原始碼的維護和升級,我們平時開發中也可以借鑑這種優雅的面向協議程式設計,當然iOS同樣適用。

PackageManager 



 PackageManager 在android.content.pm包下,它主要來負責應用的解析,和APK的安裝,解除安裝和更新,那麼我們可以清晰得看到此類的以下方法

  1)負責安裝
   
     private abstract void installPackage(
         Uri packageURI, IPackageInstallObserver observer, int flags,)

  2)解除安裝

       private abstract void deletePackage(
        String packageName, IPackageDeleteObserver observer, int flags);




安卓系統通過以上api和底層的pms互動進行安裝,我們的普通apk無法直接安裝的第三方應用的, 因為原始碼沒有開放其方法,只有我們傳送一條安裝意圖才可以交友pms來安裝apk,具體由系統級別的apk(**包名com.android.packageinstaller**)來進行處理。


    Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.setAction(Intent.ACTION_VIEW);
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
            context.startActivity(intent);



以上安全限制並非是谷歌的撒手鐗,除了用未開放安裝的api,在呼叫此方法前系統也會進行許可權動態監測,我們可以看看pm的又一重要方法:


  `public abstract int checkPermission(String permName, String pkgName);`

   
 這就為何證明了普通應用為何沒有安裝的許可權,其內部會對呼叫此api的進行許可權監測,如果是普通應用那麼返回int值為1的返回值,在這裡我們要和分析下安卓apk的級別內建應用和普通應用

 >預裝程式(即相機,日曆和瀏覽器等)儲存在/system/app/中。
 使用者安裝程式(APIDemo,Any.do等)儲存在/data/app/中。
 當然目前安卓4.4以後內建預裝程式的app/下又會新增了pri-app/ 和/app,用來個使用者提供解除安裝內建程式的入口,那麼在pri下的apk無法解除安裝的,除非我們root後才能解除安裝。

實現靜默安裝


通過了解了上面pms的簡單工作原理,我們就可以想到靜默安裝的途徑

一 採用偽造系統PM(PackageManger)
 
   通過偽造自己的Pm實現開放的api,並且採用自己的IPackageInstallObserver,說道這裡你估計會不明白此類用來幹嘛的。此觀察者是用來檢測apk是否安裝的的回撥,那麼解除安裝同樣有自己的觀者這,此通過aidl和pms進行通訊,我們可以從原始碼copy一份到自己的專案下面 注意的是包名和路勁必須和原始碼保持一致。



  偽造安裝所需要的observer和PM後 在我們的程式碼裡直接掉用pm.installPackager()即可,但是又會來到許可權的問題,那麼怎麼做到繞過許可權呢,我通過改checkPermission()方法,但是沒用,即使我返回0也無法達到繞過許可權的問題,那麼,今天的靜默安裝也到此無法達到大家期望的普通靜默安裝的效果,但是在root後或者app為系統apk的時候,我們是可以做到靜默安裝的,至於安裝成功後你需要顯示什麼view 我們同樣可以在回撥中進行處理。

二 執行PM命令 

 我們可以直接在拿到系統`builder`寫入pm命令,加入到系統程序中執行install方法進行安裝
 程式碼如下:

 final ResultBuilder builder = Result.newBuilder();

    String[] args = {"pm", "install", "-r", getPackageUri(path).getPath()};
    String result = null;
    ProcessBuilder processBuilder = new ProcessBuilder(args);
    Process process= null;
    InputStream errIs = null;
    InputStream inIs = null;
    try{
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int read=-1;
        process = processBuilder.start();
        errIs=process.getErrorStream();
        while((read = errIs.read()) != -1){
            baos.write(read);
        }
        baos.write('\n');
        inIs=process.getInputStream();
        while((read=inIs.read())!=-1){
            baos.write(read);
        }

        byte[] data=baos.toByteArray();

        result = new String(data);

    } catch (IOException e) {
        e.printStackTrace();
        String log = e.toString() + ":" + "file is write ex!";
        Log.e("", log);
        builder.setCustomMessage(log);
    }

   當然此方法也不是完美的,也需要root許可權或者系統界別的apk

三 採用反射方法 

  
我們也可以採用反射方法,通過java反射原理,反射出PM然後執行insatallPackage(),PM並非直接可以反射,它是需要ActivityTherad進行提供支援,我們通過對系統匯流排程的反射出PM,接著反射出installPackage方法即可

    Class<?>pmService;
    Class<?> activityTherad;
    Method method;

    activityTherad = Class.forName("android.app.ActivityThread");
    Class<?> paramTypes[] = getParamTypes(activityTherad, "getPackageManager");
    method = activityTherad.getMethod("getPackageManager", paramTypes);
    Object PackageManagerService = method.invoke(activityTherad);
    pmService = PackageManagerService.getClass();
    Class<?> paramTypes1[] =getParamTypes(pmService, "installPackage");
    method = pmService.getMethod("installPackage", paramTypes1);
    method.invoke(PackageManagerService, getPackageUri(Path), null, 0, null);

# 總結 #


   注意:實現靜默安裝都需要系統級別許可權才能執行,具體是否監測系統給予許可權

 ` <permission android:name="android.permission.INSTALL_PACKAGES" />`

  其實root也是擁有系統級別許可權的一種方式,本質是系統許可權才有執行install的許可權,

  我們可以請求使用者root (此方式個人感覺真心拿客戶當犧牲品,建議pm不要這麼做)
程式碼:

    /**
     * check rootPerssion.
     * @return
     */
    private static boolean hasRootPerssion() {

   
        PrintWriter PrintWriter = null;

        Process process = null;

        try {
  
            process = Runtime.getRuntime().exec("su");

            PrintWriter = new PrintWriter(process.getOutputStream());

            PrintWriter.flush();
   
            PrintWriter.close();

            int value = process.waitFor();


            return returnResult(value);
   
        } catch (Exception e) {
   
            e.printStackTrace();


        } finally {
  
            if (process!=null) {
                process.destroy();
            }
        }

        return false;
    }



所以實現靜默安裝的前提必須內建或者root,其他情況無法做到靜默安裝。當然這裡我們必須要放棄了,因為面向客戶的apk是非系統的,
那麼是不是我們無法做到除了以上內建的靜默安裝了呢 其實也未必:


目前我們可以採用輔助功能(Accessibility)實現自動安裝,用來代替使用者點選,監控在`com.android.packageinstaller`包的介面元素來遍歷出所需要的按鈕文字,來執行安裝操作,微信搶紅包外掛也是利用此原理,但是採用輔助依舊會顯示安裝介面的,我們可以在原有的系統介面上新增一個view浮層來偽裝靜默安裝功能(下期將帶來免root實現靜默安裝),此種方式也需要使用者主動授權。
到此靜默安裝又一次裝逼失敗,放棄



除此之外我們也可以採用動態載入來實現一個apk的安裝,其實真正意義上並非靜默安裝,這需要一個apk來做宿主,只是將我們的apk解析出所用的元件資訊,儲存到本地,再將宿主的上下文直接注入外掛apk中,至於外掛實現apk免安裝的知識有很多方式,也可以單獨出個專題介紹一下。到此如歸沒有宿主apk存在那麼當你的apk想無緣無故的安裝起來是不可能的,到此你又一次要放棄!

疑問

這裡有人還不服,我一定要實現靜默安裝,為何QQ之類的就能實現,這裡解釋一下當你的產品龐大到使用者不能放棄的時候,那麼很多時候你想做什麼更笨不是難題,這種大品牌通過和手機廠家做OEM定製利益鏈,或者通過開發白名單的方式進行的非法採集勾當,這裡就不想再說太多,你懂得!
不死程序一樣。你終究要放棄!

--

輔助安裝請看:http://blog.csdn.net/sk719887916/article/details/46746991