Android的静默安装
Android的静默安装似乎是一个很有趣很诱人的东西,但是,用普通做法,如果手机没有root权限的话,似乎很难实现静默安装,因为Android并不提供显示的Intent调用,一般是通过以下方式安装apk:
|
1
2
3
|
Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); startActivity(intent); |
但是,这并没有真正的实现静默安装,因为有用户界面,会让用户知道。那么,怎么在后台悄悄的安装APK呢?只能试图去看看Android系统源码正常安装APK的过程,我这边下载的源码是Android5.0系统的,5个G的大小,但是可能由于Android5.0有一些安全方面的更新,跟之前的版本还是有一定的差距的,但是,学会一个之后再去学另一个相似的过程,那就简单许多了,就像学会了C语言,再学Java,也并非什么难事。
Android系统把所有的Permission(权限)依据其潜在风险划分为四个等级,即"normal"、 "dangerous"、
"signature"、 "signatureOrSystem"。APK的安装对应的权限是
INSTALL_PACKAGES,权限等级属于后两者。所以,最终想实现APK的静默安装,必然需要一些特殊的处理,执行安装的这个进程,须为系统进程。
那么,我们就来看看Android自身是如何实现安装APK的。安装的命令是pm install...
我们定位到系统源码的/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java这个文件,他实现了pm命令,我们看runInstall方法,这就是APK的安装过程。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
private void runInstall() { int installFlags = 0; int userId = UserHandle.USER_ALL; String installerPackageName = null; String opt; String originatingUriString = null; String referrer = null; String abi = null; while ((opt=nextOption()) != null) { if (opt.equals("-l")) { installFlags |= PackageManager.INSTALL_FORWARD_LOCK; } else if (opt.equals("-r")) { installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; } else if (opt.equals("-i")) { installerPackageName = nextOptionData(); if (installerPackageName == null) { System.err.println("Error: no value specified for -i"); return; } } else if (opt.equals("-t")) { installFlags |= PackageManager.INSTALL_ALLOW_TEST; } else if (opt.equals("-s")) { // Override if -s option is specified. installFlags |= PackageManager.INSTALL_EXTERNAL; } else if (opt.equals("-f")) { // Override if -s option is specified. installFlags |= PackageManager.INSTALL_INTERNAL; } else if (opt.equals("-d")) { installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE; } else if (opt.equals("--originating-uri")) { originatingUriString = nextOptionData(); if (originatingUriString == null) { System.err.println("Error: must supply argument for --originating-uri"); return; } } else if (opt.equals("--referrer")) { referrer = nextOptionData(); if (referrer == null) { System.err.println("Error: must supply argument for --referrer"); return; } } else if (opt.equals("--abi")) { abi = checkAbiArgument(nextOptionData()); } else if (opt.equals("--user")) { userId = Integer.parseInt(nextOptionData()); } else { System.err.println("Error: Unknown option: " + opt); return; } } if (userId == UserHandle.USER_ALL) { userId = UserHandle.USER_OWNER; installFlags |= PackageManager.INSTALL_ALL_USERS; } final Uri verificationURI; final Uri originatingURI; final Uri referrerURI; if (originatingUriString != null) { originatingURI = Uri.parse(originatingUriString); } else { originatingURI = null; } if (referrer != null) { referrerURI = Uri.parse(referrer); } else { referrerURI = null; } // Populate apkURI, must be present final String apkFilePath = nextArg(); System.err.println("\tpkg: " + apkFilePath); if (apkFilePath == null) { System.err.println("Error: no package specified"); return; } // Populate verificationURI, optionally present final String verificationFilePath = nextArg(); if (verificationFilePath != null) { System.err.println("\tver: " + verificationFilePath); verificationURI = Uri.fromFile(new File(verificationFilePath)); } else { verificationURI = null; } LocalPackageInstallObserver obs = new LocalPackageInstallObserver(); try { VerificationParams verificationParams = new VerificationParams(verificationURI, originatingURI, referrerURI, VerificationParams.NO_UID, null); mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags, installerPackageName, verificationParams, abi, userId); //注意!!最终就是调用这个方法来进行安装的 synchronized (obs) { while (!obs.finished) { try { obs.wait(); } catch (InterruptedException e) { } } if (obs.result == PackageManager.INSTALL_SUCCEEDED) { System.out.println("Success"); } else { System.err.println("Failure [" + installFailureToString(obs) + "]"); } } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); } } |
知道了这个过程之后,就大概知道怎么做了。既然系统底层把这个API屏蔽了,那就想办法去绕过这层屏蔽,来使用它。首先想到的就是使用AIDL,不知道AIDL这东西的,先问度娘去吧~~在上面的代码中,最终实现安装的那一句话,mPm.installPackageAsUser(...),mPm是个什么东西?不难发现,IPackageManager类型,那么这个类从哪里来?搜寻一下,位于/frameworks/base/core/java/android/content/pm这个包底下,拷贝到我们工程目录底下,包名不能变,只拷贝这一个文件的话,一定是不行了,会报其他的一些aidl找不到,相应地也拷贝过来。Android5.0中,aidl改动还是比较大的,所以要拷贝很多东西过来,还要进行一些改动...我也是花了挺久才改到他没报错。
最终,工程的目录如下所示~~

那么,如何来使用它呢?
- 1、先获取系统服务android.os.ServiceManager,这个又是隐藏的,怎么办?考验Java水平的时候到了~~没错,用反射机制,来获取ServiceManager类,以及该类里面的方法;
- 2、有了服务之后,我们就要去拿到IPackageManager这个对象;
- 3、调用IPackageManager里面的installPackage方法进行安装;
实现代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
package com.example.autoinstall; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import android.app.Activity; import android.content.Intent; import android.content.pm.IPackageInstallObserver2; import android.content.pm.IPackageManager; import android.content.pm.VerificationParams; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.view.View; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /** * Button点击事件 * @param view */ public void install(View view) { String path = ""; if (FileUtils.isSdcardReady()) { path = FileUtils.getSdcardPath(); } else { path = FileUtils.getCachePath(this); } String fileName = path + "/AidlServerDemo.apk"; File file = new File(fileName); try { if(!file.exists()) copyAPK2SD(fileName); Uri uri = Uri.fromFile(new File(fileName)); // 通过Java反射机制获取android.os.ServiceManager Class<?> clazz = Class.forName("android.os.ServiceManager"); Method method = clazz.getMethod("getService", String.class); IBinder iBinder = (IBinder) method.invoke(null, "package"); IPackageManager ipm = IPackageManager.Stub.asInterface(iBinder); @SuppressWarnings("deprecation") VerificationParams verificationParams = new VerificationParams(null, null, null, VerificationParams.NO_UID, null); // 执行安装(方法及详细参数,可能因不同系统而异) ipm.installPackage(fileName, new PackageInstallObserver(), 2, null, verificationParams, ""); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } // 用于显示结果 class PackageInstallObserver extends IPackageInstallObserver2.Stub { @Override public void onUserActionRequired(Intent intent) throws RemoteException { // TODO Auto-generated method stub } @Override public void onPackageInstalled(String basePackageName, int returnCode, String msg, Bundle extras) throws RemoteException { //returnCode<span style="font-family: Arial, Helvetica, sans-serif;">为1,就是安装成功</span> } }; /** * 拷贝assets文件夹的APK插件到SD * * @param strOutFileName * @throws IOException */ private void copyAPK2SD(String strOutFileName) throws IOException { FileUtils.createDipPath(strOutFileName); InputStream myInput = this.getAssets().open("AidlServerDemo.apk"); OutputStream myOutput = new FileOutputStream(strOutFileName); byte[] buffer = new byte[1024]; int length = myInput.read(buffer); while (length > 0) { myOutput.write(buffer, 0, length); length = myInput.read(buffer); } myOutput.flush(); myInput.close(); myOutput.close(); } } |
每个版本的系统源码里面的aidl可能会不一样,所以具体调用的方法和参数,还得根据实际情况而定,需要去仔细阅读Pm.java这个文件的源码。
在其他版本可能只需要拷贝这4个文件:PackageManager.java、 IPackageDeleteObserver.aidl 、IPackagerInstallObserver.aidl、 IPackageMoveObserver.aidl
然后,还需在配置清单文件里面添加INSTALL_PACKAGE权限
|
1
|
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/> |
然后把该应用的uid设置为系统级别的,在manifest标签下添加以下属性
|
1
|
android:sharedUserId="android.uid.system" |
仅仅这样的话,还是没法实现静默安装,因为系统并不认为你这个app是系统级别的应用,所以,还应该对该应用的APK进行系统签名(注意:不是那个静默安装的APK,是这个实现静默安装程序的APK)。签名过程如下:
总共需要三个文件:
- 1、SignApk.jar %系统源码%/out/host/linux-x86/framework/signapk.jar
- 2、platform.x509.pem %系统源码%/build/target/product/security/platform.x509.pem
- 3、platform.pk8 %系统源码%/build/target/product/security/platform.pk8
打开终端,执行命令 java -jar SignApk.jar platform.x509.pem platform.pk8 未签名APK 签名后APK,例如
java -jar SignApk.jar platform.x509.pem platform.pk8 AutoInstall.apk AutoInstall_new.apk
之后,把签名过后的APK安装到手机上,打开,点击静默安装,在去程序页看看,发现安装成功~~


本文主要是提供了一种实现静默安装的思路,但是具体怎么做到兼容各个系统,举一反三,
Android的静默安装的更多相关文章
- Android实现静默安装与卸载
一般情况下,Android系统安装apk会出现一个安装界面,用户可以点击确定或者取消来进行apk的安装. 但在实际的项目需求中,有一种需求,就是希望apk在后台安装(不出现安装界面的提示),这种安装方 ...
- Android对于静默安装和卸载
在一般情况下,Android系统安装apk会有一个安装界面,用户可以单击确定或取消apk设备. 但在实际的项目需求中,有一种需求.就是希望apk在后台安装(不出现安装界面的提示),这样的安装方式称为静 ...
- android apk静默安装
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/47803149 之前有很多朋友都问过我,在Android系统中怎样才能实现静默安装呢 ...
- Android使用静默安装时碰见的问题
升级时碰见的异常 private void installPackage(String appName,final File apk) { if (!apk.exists()) { setHasNew ...
- android 实现静默安装、卸载
方法1:[使用调用接口方法,由于安装卸载应用程序的部分API是隐藏的,所以必须下载Android系统源码,在源码下开发并编译之后使用MM命令编译生成APK文件] import java.io.File ...
- android实现静默安装demo
1.须要RootTools.jar 2.运行脚本 public class InstallerActivity extends Activity { /** Called when the a ...
- Android随笔之——静默安装、卸载
随笔之所以叫随笔,就是太随意了,说起来,之前的闹钟系列随笔还没写完,争取在十月结束之前找时间把它给写了吧.今天要讲的Android APK的静默安装.卸载.网上关于静默卸载的教程有很多,更有说要调用隐 ...
- Android 静默安装/后台安装
Android实现静默安装其实很简单,今天在网上找资料找半天都说的很复杂,什么需要系统安装权限.调用系统隐藏的api.需要系统环境下编译.需要跟systemUI同进程什么的.我不知道他们真的实现了静默 ...
- 非ROOT实现静默安装的一些思考与体会,AIDL获取IPackageManager,反射ServiceManager,系统签名
非ROOT实现静默安装的一些思考与体会,AIDL获取IPackageManager,反射ServiceManager,系统签名 最近自家的系统要做一个升级服务,里面有三个功能,第一个是系统升级,也就是 ...
随机推荐
- ionic 开发实例
ionic 开发实例 一.ionic初始化项目 1:安装ionic npm install -g ionic 2:初始化项目框架 我们可以用命令创建一个应用程序,可以使用我们的一个现成的应用程序模板, ...
- gulp配置文件(gulpfile.js)
需要安装的插件 "gulp": "^3.9.1","gulp-clean": "^0.3.2","gulp-c ...
- Scrum立会报告+燃尽图(十月十七日总第八次)
本次作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2246 一.小组介绍 组名:杨老师粉丝群 组长:乔静玉 组员:吴奕瑶.公 ...
- html、JSP运行原理
HTML运行原理 1.本地运行 所谓本地运行就是直接用 浏览器打开 2.远程访问的原理示意图: 何为协议?计算机互相通信(网络)的规则.常见的协议有 http .smtp. ftp.pop等 ...
- tensorflow之曲线拟合
视频链接:https://morvanzhou.github.io/tutorials/machine-learning/ML-intro/ 1.定义层 定义 add_layer() from __f ...
- 周总结web未完成的代码
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Con ...
- web.config详解(转载)
该文为转载 原文地址:http://www.cnblogs.com/gaoweipeng/archive/2009/05/17/1458762.html 花了点时间整理了一下ASP.NET Web.c ...
- Swift-自定义类的构造函数
构造函数类似oc中的init方法默认情况下,创建一个,类会调用一个构造函数即使没写任何构造函数,编译器会默认一个构造函数如果是继承NSObject,可以对构造函数重写 class Person: NS ...
- Spring Boot(八)集成Spring Cache 和 Redis
在Spring Boot中添加spring-boot-starter-data-redis依赖: <dependency> <groupId>org.springframewo ...
- Google Professional Data Engineer(PDE)考试
在国内参加PDE考试的人比较少,导致资料也很少.我在19年1月30号去上海参加PDE考试,参加前也是完全没底,因为时间短资料少,但幸运的是顺利通过了.回过头来看,其中有些技巧和重点,在此做一些总结,希 ...