非ROOT实现静默安装的一些思考与体会,AIDL获取IPackageManager,反射ServiceManager,系统签名
非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,那我们要怎么才能拿到呢?其实说实在的,这个也是不难的,我们可以通过反射去实现,有了思路,我们就去尝试一下
//反射获取ServiceManagertry {//指定反射类Class<?> forName = Class.forName("android.os.ServiceManager");//获取方法,参数是String类型Method method = forName.getMethod("getService", String.class);//传入参数IBinder iBinder = (IBinder) method.invoke(null, "package");//初始化AIDLmPm = 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.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 状态,详见PackageManagerprivate static final int INSTALL_REPLACE_EXISTING = 0X00000002;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}/*** 初始化View*/private void initView() {//反射获取ServiceManagertry {Class<?> forName = Class.forName("android.os.ServiceManager");Method method = forName.getMethod("getService", String.class);IBinder iBinder = (IBinder) method.invoke(null, "package");//初始化AIDLmPm = 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*/@Overridepublic 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 {@Overridepublic 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
非ROOT实现静默安装的一些思考与体会,AIDL获取IPackageManager,反射ServiceManager,系统签名相关推荐
- 安装cuda 非root_linux非root用户下安装软件,搭建生产环境
之前的用实验室的服务器,因为某些原因,使用的用户没有root权限.linux的非root用户很多软件无法安装,非常的不方便.我的方法是使用brew来代替系统的包管理工具.brew是最先用在mac上的包 ...
- linux系统下安装pfam数据库中hmmer软件以及python3非root用户的安装
linux系统下安装pfam数据库中hmmer软件以及python3非root用户的安装 http://hmmer.org/从该链接下载源,其中有Userguide.pdf 下载,解压缩并切换目录 之 ...
- oracle安装在非图形,非图形化静默安装Oracle 11g
非图形化静默安装Oracle 11g [日期:2013-01-19] 来源:Linux社区 作者:loofeer [字体:大 中 小] cat >> /etc/security/limit ...
- linux是不是在根目录下安装的软件其它用户就可以使用,[转载]Linux下非root用户如何安装软件...
[转载]Linux下非root用户如何安装软件 这是本人遇到的实际问题,之前用到的所有机器,无论是自己的PC还是云服务器,root权限都是妥妥的,但是现在发现实验室的服务器原来自己并没有root权限2 ...
- Android系统Root与静默安装
Android系统Root与静默安装 静默安装,指的是安装时无需任何用户干预,直接按默认设置安装应用.因为,它的无需用户干预,很多情况下变成了用户压根不知道,应用不知不觉就安装上了.是在推广上极为流氓 ...
- Linux 下非 root 用户 Conda 安装生物信息 R 软件包 MetaboAnalystR 演示
(首发地址:学习日记 https://www.learndiary.com/2022/06/metaboanalystr/ ) 前些天演示了一下 Linux 下非 root 用户 Conda 安装生 ...
- zzw原创_非root用户下安装nginx
想自己安装nginx,又不相用到root用户. 非root用户下(本文为用户bdctool)来ngnix安装,要依赖pcre库.zlib库等, 1. 下载依赖包:下载地址 pcre(www.pcre. ...
- adb 静默安装_Android ROOT下静默安装并打开APP
最近项目有自动更新的需求,因为是无人值守的项目,所以需要静默更新并且更新完成后打开app,网上搜了一通,大概有两个方案.设备是root过的. 1.shell安装,广播开启 通过shell命令安装app ...
- mysql 必须安装php_非root模式下安装mysql php小记
假设你的home目录为/home/work mysql-server 安装 1. 下载mysql.tar.gz wget http://dev.mysql.com/get/Downloads/MySQ ...
最新文章
- git 修改全局配置
- uploadify多文件上传插件
- FFmpeg在Linux下编译使用
- 如何在Windows 8.1中获取Windows 10样式的开始菜单
- MySQL索引类型总结和使用技巧以及注意事项
- php熊掌号怎么设置json-ld,织梦DEDECMS熊掌号JSON LD结构化数据代码分享
- python简述列表特征_python高级特性简介
- php sqlsrv 分页,sqlsrv php分页
- VC 无标题栏对话框移动
- EMS设置发送连接器和接收连接器邮件大小
- 信息系统分析与设计杨选辉_信息系统分析与设计课后题答案(杨选辉)
- Python数据可视化之南丁格尔玫瑰图
- Django-Templates模板语法(二)
- 【黄啊码】解决微信小程序showToast不显示
- Qt中QOpengl的QMatrix4x4矩阵作用原理以及使用方法
- SEO实战(二) 分析网站在搜索结果中的曝光和点击
- 令人激动的语音UI背后
- STM32F103C8核心板原理图
- 【Java】简述断言(assert)的使用以及使用场景
- APP——功耗测试(耗电测试)——adb命令复杂获取分析