非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工程里,这里推荐一个在线查看源码的网站

而我们的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之前的方法不是这个哦

但是原理都是一样的

 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文件?

那我们新建一个工程,去导入他,在我们的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

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

目录下的

目录下的

  • 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和设备管理器去做的,也是目前比较通用的,而我这个,感觉还是要和我一样做一些平台性相关的工作才好实用,不然通配性应该是一个问题

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

有兴趣的加群:555974449

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

  1. Linux下非root用户如何安装软件

    Linux下非root用户如何安装软件 从windows转移到Linux的用户最开始总会有各种不适,因为这种不适最终放弃linux的不在少数.对于这类人只能说可惜,还没有领略到linux的美好就过早放 ...

  2. [转载]Linux下非root用户如何安装软件

    [转载]Linux下非root用户如何安装软件 来源:https://tlanyan.me/work-with-linux-without-root-permission/ 这是本人遇到的实际问题,之 ...

  3. zzw原创_非root用户下安装nginx

    想自己安装nginx,又不相用到root用户. 非root用户下(本文为用户bdctool)来ngnix安装,要依赖pcre库.zlib库等, 1. 下载依赖包:下载地址 pcre(www.pcre. ...

  4. 8、非root权限下安装perl以及perl模块

    转载:http://www.cnblogs.com/nkwy2012/p/6418669.html 转载自http://www.zilhua.com 在本博客中,所有的软件安装都在服务器上,且无roo ...

  5. linux非root用户下安装软件,搭建生产环境

    之前的用实验室的服务器,因为某些原因,使用的用户没有root权限.linux的非root用户很多软件无法安装,非常的不方便.我的方法是使用brew来代替系统的包管理工具.brew是最先用在mac上的包 ...

  6. 非root用户 gcc安装

    亲测 可以安装 过程并不复杂 但可能需要一些时间 认真一点 按照步骤 一定可以成功哒 其他版本可以将ftp.gnu.org/gnu/gcc/敲入浏览器,找到自己需要的文件:[安装过4.9.0:成功:用 ...

  7. 非root模式下安装mysql php小记

    假设你的home目录为/home/work mysql-server 安装 1. 下载mysql.tar.gz wget http://dev.mysql.com/get/Downloads/MySQ ...

  8. 非root模式下安装mysql

    1. 下载mysql.tar.gz wget http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.17.tar.gz 2. 下载cmake [ ...

  9. Samtools在Linux上非root权限的安装

    第一次在Linux上不用root权限安装软件,查看了很多博客,并实践安装成功.大致总结了一下samtools的安装过程,仅供大家参考,如有不对的地方,欢迎指正~ samtools安装过程中依赖于lzm ...

随机推荐

  1. STL rope

    rope的部分简单操作 函数 功能 push_back(x) 在末尾添加x insert(pos,x) 在pos插入x erase(pos,x) 从pos开始删除x个 replace(pos,x) 从 ...

  2. [HNOI2016]大数

    题目描述 小 B 有一个很大的数 S,长度达到了 N 位:这个数可以看成是一个串,它可能有前导 0,例如00009312345.小B还有一个素数P.现在,小 B 提出了 M 个询问,每个询问求 S 的 ...

  3. 【LA3938】"Ray, Pass me the dishes!"

    原题链接 Description After doing Ray a great favor to collect sticks for Ray, Poor Neal becomes very hun ...

  4. 2015 多校联赛 ——HDU5323(搜索)

    Solve this interesting problem Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K ...

  5. QCA4028软件平台启用双WAN指导

    1 为何要启用双WAN QCA4028的硬件方案,基板上部署了一个LTE模块插槽,同时又外留了一个USB3.0接口,因此,就可以在此硬件平台上调试基于LTE的双WAN,预期实现: A 链路备份,在任意 ...

  6. tf.contrib.seq2seq.sequence_loss example:seqence loss 实例代码

    #!/usr/bin/env python # -*- coding: utf-8 -*- import tensorflow as tf import numpy as np params=np.r ...

  7. 基于GCC的openMP学习与测试

    (一).openMP简述 Open Multiprocessing (OpenMP) 框架是一种功能极为强大的规范,可以帮助您利用 C.C++ 和 Fortran 应用程序中的多个核心带来的好处,是基 ...

  8. 判断是否是IE;自定义onkeyup事件

    <script> /*onkeyup和onchange事件在IE下冲突,在此做区分*/ if (!!window.ActiveXObject || "ActiveXObject& ...

  9. mac下怎么删除隐藏文件比如 .Trashes文件

    U盘和移动硬盘接入Mac时会产生.Trashes,.Spotlight-V100,.fseventsd等文件 每插入Mac一次,都会检查是否有这些文件,如果没有,就会创建这些文件 特别是有时候,在文件 ...

  10. IntelliJ IDEA 14.0.3 实战搭建Spring+SpringMVC+MyBatis组合框架

    简介 Spring+SpringMVC+MyBatis框架(SSM)是比较热门的中小型企业级项目开发的框架,对于新手来说也是比较容易学习入门的.虽说容易,但在框架搭建过程中仍然遇到了许多问题,因此用实 ...