Android无需root实现apk的静默安装
android的静默安装似乎是一个很有趣很诱人的东西,但是,用普通做法,如果手机没有root权限的话,似乎很难实现静默安装,因为android并不提供显示的intent调用,一般是通过以下方式安装apk:
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的安装过程。
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方法进行安装;
实现代码如下:
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权限
<uses-permission android:name="android.permission.install_packages"/>
然后把该应用的uid设置为系统级别的,在manifest标签下添加以下属性
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安装配置教程》进行学习。
本文主要是提供了一种实现静默安装的思路,但是具体怎么做到兼容各个系统,举一反三,