非ROOT实现静默安装的一些思考与体会,AIDL获取IPackageManager,反射ServiceManager,系统签名...

    xiaoxiao2025-12-19  11

    非ROOT实现静默安装的一些思考与体会,AIDL获取IPackageManager,反射ServiceManager,系统签名


    最近自家的系统要做一个升级服务,里面有三个功能,第一个是系统升级,也就是下载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,我们先写个布局

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" android:padding="10dp"> <EditText android:id="@+id/etPackageNmae" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入应用名"/> <Button android:id="@+id/btnInstall" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="静默安装"/> </LinearLayout>

    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(); } }

    里面的几个参数看一下就明白了,但是现在安装确实会失败的,他会提示我们没有权限去做这件事,要求我们加上这个权限

    <uses-permission android:name="android.permission.INSTALL_PACKAGES"/>

    但是我们加上之后他还是不通过,他说我们不是系统的应用程序,于是我们就要想办法做成系统的应用程序了

    四.android.uid.system

    我们首先在manifest根节点添加uid

    然后我们把这个应用先打包签名,怎么签名就不说了,签名之后,我们再去源码里找这几样东西

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

    目录下的

    platform.pk8platform.x509.pemhttp://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上应该就方便很多了,好了我们本篇的思考就到这里,我之前看到郭霖的一篇文章

    Android静默安装实现方案,仿360手机助手秒装和智能安装功能

    他的做法是利用了ROOT和设备管理器去做的,也是目前比较通用的,而我这个,感觉还是要和我一样做一些平台性相关的工作才好实用,不然通配性应该是一个问题

    SilentInstall下载:http://download.csdn.net/detail/qq_26787115/9615335

    有兴趣的加群:555974449

    相关资源:python入门教程(PDF版)
    最新回复(0)