Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 非ROOT實現靜默安裝的一些思考與體會,AIDL獲取IPackageManager,反射ServiceManager,系統簽名

非ROOT實現靜默安裝的一些思考與體會,AIDL獲取IPackageManager,反射ServiceManager,系統簽名

編輯:關於Android編程

最近自家的系統要做一個升級服務,裡面有三個功能,第一個是系統升級,也就是下載OTA包推送到recovery裡升級的,而第二個是MCU升級,這就涉及到我們自家系統的一些情況了,而第三個就是應用升級了,領導要求不要騷擾用戶,於是我就想到了靜默安裝了,因為我們的系統是在wifi環境下工作的,所以不擔心流量哈,而且我們系統是沒有ROOT的,所以我們肯定野不能使用RunTime方式去推送到data/app下,那我們要怎麼做呢?幾經思考,於是找了比較多的資料,看了挺多的文章,於是自己實現了這個功能,現在把經驗也總結出來了,於是就有了本篇博文,好了,我們一起來分析到實現這個功能吧!

一.install的思考

我們安裝應用引出來的思考,我們正常情況下,應該是怎麼去安裝一個應用?

1.純手工,點擊安裝包,安裝應用 2.adb安裝
//安裝
adb install xxx.apk
//卸載
adb uninstall xxx.apk

其實大多數的應用,比如360,應用寶,都是采用命令的方式去實現的,比如

pm install -r

而我們要想搞清楚,那就得去看下源碼是怎麼實現的了,在我們源碼目錄的frameworks/base/cmds/pm工程裡,這裡推薦一個在線查看源碼的網站

http://androidxref.com

而我們的Pm.java地址在

http://androidxref.com/4.0.3_r1/xref/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java

這是一個java文件,我們安裝其實就是執行了這個java工程,我們找到他的main,可以看到,他其實就是執行了一個run方法,而我們在源碼的98-106行可以看到

98        if ("install".equals(op)) {
99            runInstall();
100            return;
101        }
102
103        if ("uninstall".equals(op)) {
104            runUninstall();
105            return;
106        }

這個就是我們安裝和卸載所執行的方法,而我們這裡重點來看一下安裝,他所執行的方法runInstall在源碼的743-812行

743    private void runInstall() {
744        int installFlags = 0;
745        String installerPackageName = null;
746
747        String opt;
748        while ((opt=nextOption()) != null) {
749            if (opt.equals("-l")) {
750                installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
751            } else if (opt.equals("-r")) {
752                installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
753            } else if (opt.equals("-i")) {
754                installerPackageName = nextOptionData();
755                if (installerPackageName == null) {
756                    System.err.println("Error: no value specified for -i");
757                    showUsage();
758                    return;
759                }
760            } else if (opt.equals("-t")) {
761                installFlags |= PackageManager.INSTALL_ALLOW_TEST;
762            } else if (opt.equals("-s")) {
763                // Override if -s option is specified.
764                installFlags |= PackageManager.INSTALL_EXTERNAL;
765            } else if (opt.equals("-f")) {
766                // Override if -s option is specified.
767                installFlags |= PackageManager.INSTALL_INTERNAL;
768            } else {
769                System.err.println("Error: Unknown option: " + opt);
770                showUsage();
771                return;
772            }
773        }
774
775        final Uri apkURI;
776        final Uri verificationURI;
777
778        // Populate apkURI, must be present
779        final String apkFilePath = nextArg();
780        System.err.println("\tpkg: " + apkFilePath);
781        if (apkFilePath != null) {
782            apkURI = Uri.fromFile(new File(apkFilePath));
783        } else {
784            System.err.println("Error: no package specified");
785            showUsage();
786            return;
787        }
788
789        // Populate verificationURI, optionally present
790        final String verificationFilePath = nextArg();
791        if (verificationFilePath != null) {
792            System.err.println("\tver: " + verificationFilePath);
793            verificationURI = Uri.fromFile(new File(verificationFilePath));
794        } else {
795            verificationURI = null;
796        }
797
798        PackageInstallObserver obs = new PackageInstallObserver();
799        try {
800            mPm.installPackageWithVerification(apkURI, obs, installFlags, installerPackageName,
801                    verificationURI, null);
802
803            synchronized (obs) {
804                while (!obs.finished) {
805                    try {
806                        obs.wait();
807                    } catch (InterruptedException e) {
808                    }
809                }
810                if (obs.result == PackageManager.INSTALL_SUCCEEDED) {
811                    System.out.println("Success");
812                } else {
813                    System.err.println("Failure ["
814                            + installFailureToString(obs.result)
815                            + "]");
816                }
817            }
818        } catch (RemoteException e) {
819            System.err.println(e.toString());
820            System.err.println(PM_NOT_RUNNING_ERR);
821        }
822    }

這個方法就是安裝了,不過我們也可以不去關注這個方法,我們只要關注他精髓的一行代碼

mPm.installPackageWithVerification(apkURI, obs, installFlags, installerPackageName,verificationURI, null);

這行代碼位於800-801行,他最終執行的也就是這行代碼,其實就是installPackageWithVerification方法,所以,我們如果調用這個方法,是不是也是可以直接安裝而不用去走界面安裝的流程?這裡要注意一下,我們在4.0之前的方法不是這個哦

http://androidxref.com/2.1/xref/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java

但是原理都是一樣的

 mPm.installPackage(Uri.fromFile(new File(apkFilePath)), obs, installFlags,installerPackageName);

而我們要調用installPackage這個方法,就需要使用mPm,那mPm是個什麼東西呢?

IPackageManager mPm;

他是一個AIDL的接口,他初始化的內容

 mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));

而正常情況下,我們是無法調用的,如果我們想實現這一點的話,我們就要拿到系統服務中的IPackageManager,我們要怎麼做?肯定是實現我們的AIDL

二.IPackageManager.aidl

我們通過AIDL去實現我們的靜默安裝,這是有必要的,那我們去哪裡找這個aidl文件?

http://androidxref.com/2.1/xref/frameworks/base/core/java/android/content/pm/IPackageManager.aidl

那我們新建一個工程,去導入他,在我們的Android中怎麼去做呢?在main裡面新建一個aidl文件,同時,我們新建一個包名:android.content.pm,然後把IPackageManager.aidl拷貝進去

這裡寫圖片描述

裡面的內容就不多說了,我們sync一下,你就會看到

這裡寫圖片描述

說明我們還需要這幾個引用的aidl,於是我們找啊找,找到之後再次sync一下,他又提示我們需要一些aidl了

這裡寫圖片描述

這一步其實不麻煩,只是我寫的步驟分開了而已,我們要一步步去實現是吧,於是我們又繼續的找啊找,終於把他所需要的aidl文件全部給拷貝進來了,我們現在可以測試一下

這裡寫圖片描述

可以看到,我們可以使用IPackageManager了呢,那好,小司機們,我們現在就可以去嘗試的干點什麼有趣的事情了

三.靜默安裝的實現

好的,我們仿照我們上面的runInstall方法來實現我們的靜默安裝,一起代碼邏輯請參考Pm.java,我們先寫個布局




    

OK,那我們就去實現了,我們要做的很簡單,就是我們比如把qq.apk放在sd卡根目錄,然後我們再輸入框上輸入一個應用名,點擊靜默安裝,就去執行,是不是很簡單,那好,我們邏輯是這樣的,但是我們還是會碰到一些問題的,比如我們要去初始化IPackageManager的時候,源碼中是這樣子的

 mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));

但是當我們去做的時候,就比較尴尬的發現

這裡寫圖片描述

我們拿不到ServiceManager,那我們要怎麼才能拿到呢?其實說實在的,這個也是不難的,我們可以通過反射去實現,有了思路,我們就去嘗試一下

    //反射獲取ServiceManager
        try {
            //指定反射類
            Class forName = Class.forName("android.os.ServiceManager");
            //獲取方法,參數是String類型
            Method method = forName.getMethod("getService", String.class);
            //傳入參數
            IBinder iBinder = (IBinder) method.invoke(null, "package");
            //初始化AIDL
            mPm = IPackageManager.Stub.asInterface(iBinder);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

現在我們可以直接調用installPackage方法了

     /**
     * 安裝apk
     *
     * @param apkPath 路徑
     */
    private void runInstall(String apkPath) {
        /**
         *  install方法
         *  1.uri:安裝文件路徑
         *  2.observer:觀察者,安裝成功還是失敗
         *  3.flags:標記狀態
         *  4.installer: 整個路徑
         */
        try {
            mPm.installPackage(Uri.fromFile(new File(apkPath)),
                    new PackInstallObserver(), INSTALL_REPLACE_EXISTING,
                    new File(apkPath).getPath());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

裡面的幾個參數看一下就明白了,但是現在安裝確實會失敗的,他會提示我們沒有權限去做這件事,要求我們加上這個權限

但是我們加上之後他還是不通過,他說我們不是系統的應用程序,於是我們就要想辦法做成系統的應用程序了

四.android.uid.system

我們首先在manifest根節點添加uid

這裡寫圖片描述

然後我們把這個應用先打包簽名,怎麼簽名就不說了,簽名之後,我們再去源碼裡找這幾樣東西

http://androidxref.com/2.1/xref/build/target/product/security/

目錄下的

platform.pk8

platform.x509.pem

http://androidxref.com/2.1/xref/build/tools/signapk/

目錄下的

SignApk.java

我們把這幾個文件放在一起,然後使用命令

這裡寫圖片描述

簽完名之後我們可以看到NewApk.apk

這裡寫圖片描述

到這裡我們算是實現了,看下完整的代碼

package com.liuguilin.silentinstall;

import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 靜默安裝 --by 劉桂林
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    //輸入框
    private EditText etPackageNmae;
    //執行按鈕
    private Button btnInstall;

    //安裝路徑
    private String path = Environment.getExternalStorageDirectory().getAbsolutePath();

    //安裝類
    private IPackageManager mPm;

    //install flags 狀態,詳見PackageManager
    private static final int INSTALL_REPLACE_EXISTING = 0X00000002;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    /**
     * 初始化View
     */
    private void initView() {

        //反射獲取ServiceManager
        try {
            Class forName = Class.forName("android.os.ServiceManager");
            Method method = forName.getMethod("getService", String.class);
            IBinder iBinder = (IBinder) method.invoke(null, "package");
            //初始化AIDL
            mPm = IPackageManager.Stub.asInterface(iBinder);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        //初始化
        etPackageNmae = (EditText) findViewById(R.id.etPackageNmae);
        btnInstall = (Button) findViewById(R.id.btnInstall);
        btnInstall.setOnClickListener(this);
    }

    /**
     * 點擊事件
     *
     * @param view
     */
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btnInstall:
                String apkPath = path + "/" + etPackageNmae.getText().toString().trim();
                runInstall(apkPath);
                break;
        }
    }

    /**
     * 安裝apk
     *
     * @param apkPath 路徑
     */
    private void runInstall(String apkPath) {
        /**
         *  install方法
         *  1.uri:安裝文件路徑
         *  2.observer:觀察者,安裝成功還是失敗
         *  3.flags:標記狀態
         *  4.installer: 整個路徑
         */
        try {
            mPm.installPackage(Uri.fromFile(new File(apkPath)),
                    new PackInstallObserver(), INSTALL_REPLACE_EXISTING,
                    new File(apkPath).getPath());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 觀察者
     */
    class PackInstallObserver extends IPackageInstallObserver.Stub {

        @Override
        public void packageInstalled(String packageName, int returnCode) throws RemoteException {
            //根據returnCode判斷是否成功失敗
        }
    }
}

這路要說明的幾點,我在自家平台使用了framework.jar哦,但是覺得原理是通用的,在Andorid Studio上終究是有一些問題,如果在Eclipse上應該就方便很多了,好了我們本篇的思考就到這裡,他的做法是利用了ROOT和設備管理器去做的,也是目前比較通用的,而我這個,感覺還是要和我一樣做一些平台性相關的工作才好實用,不然通配性應該是一個問題

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved