作者介紹:高闊(京東金融客戶端研發(fā)工程師峭跳,前360手機助手安裝模塊技術(shù)負責人)
? ? 在Android系統(tǒng)中如何實現(xiàn)靜默安裝蒂破,所謂靜默安裝就是在不展示安裝界面的情況下扒接,后臺悄悄的安裝某個應用精钮,雖然看上去不打擾用戶,但會存在以下兩個風險乞娄。
????第一瞬逊,但是由于在安裝的時候,Installer會展示該應用所申請的權(quán)限仪或,由用戶根據(jù)申請的權(quán)限選擇是否安裝确镊,靜默安裝相當于用戶被動的接受了這些權(quán)限。第二:在未經(jīng)用戶允許的情況下安裝某個應用范删,極易造成病毒的傳播蕾域,符合計算機病毒的傳染特性。很顯然這種做法是非常危險的,所以Android系統(tǒng)不會把此功能提供給普通開發(fā)者旨巷。而在Google Play巨缘,小米應用市場,華為應用市場下載的應用也無需彈出安裝界面采呐,是因為相關(guān)廠家的ROM賦予了自家應用市場靜默安裝權(quán)限若锁。自家的ROM想怎么折騰就怎么折騰,普通開發(fā)者也不需要關(guān)注靜默安裝的功能斧吐,只要把自己的應用上傳的某個應用市場即可
????如360手機助手又固,廣泛的安裝在各型號的手機上,它沒有小米市場煤率,華為市場等手機廠商賦予的特殊權(quán)利仰冠,360手機助手是如何做到的?360手機助手提供了兩個功能:秒裝和智能安裝
一:秒裝
秒裝就是在應用擁有Root權(quán)限的情況下蝶糯,實現(xiàn)靜默安裝(用戶無感知)
方法1:在開發(fā)者安裝安裝應用時洋只,多數(shù)會采取adb shell pm install命令,我們也可以在有root權(quán)限的情況下使用Runtime來調(diào)用此命令裳涛。
我們創(chuàng)建install(String apk_path,int flag)方法
private?void install(String apk_path,String flag) ?{??
String?command?="pm install "+flag +?filePath;??
Process?process?=null;??
DataOutputStream?os?=null;??
try?{??
process?=?Runtime.getRuntime().exec("su");??
os?=new?DataOutputStream(process.getOutputStream());??
os.writeBytes(command?+"\n");??
os.writeBytes("exit\n");??
????????os.flush();??
}catch?(Exception?e)?{??
????????e.printStackTrace();??
????}??
}??
首先組裝一個安裝命令木张,命令的格式就是pm install -r ,-r參數(shù)表示如果要安裝的apk已經(jīng)存在了就覆蓋安裝的意思端三。然后調(diào)用?Runtime.getRuntime().exec("su") 來申請root權(quán)限,隨后執(zhí)行組裝的安裝命令即可鹃彻,這種方法理解起來比較簡單郊闯,但是會存在一定的局限性,因為在中國這個特殊的市場環(huán)境下蛛株,很多的國產(chǎn)ROM廠商已經(jīng)禁用了app來執(zhí)行pm命令团赁,接下來我會著重介紹第二種安裝方法
方法2:
在應用中安裝第三方應用是通過
Intent?intent?=new?Intent(Intent.ACTION_VIEW);??
intent.setDataAndType(Uri.fromFile(new?File(mUrl)),??
"application/vnd.android.package-archive");??
?mContext.startActivity(intent);??
此段代碼實現(xiàn)的,而最終處理安裝的是PackageInstaller程序谨履,既然PackageInstaller可以實現(xiàn)安裝欢摄,那程序在擁有root權(quán)限的情況下,就可以使用期核心安裝方法來實現(xiàn)笋粟,好在android是開源的怀挠,下面分析PackageInstaller代碼:
通過以上代碼可得,PackageInstaller最終調(diào)用的是PackageManager的installPackageWithVerificationAndEncryption實現(xiàn)的靜默安裝害捕。但現(xiàn)在又有一個問題擺在我們面前绿淋,應用啟動的進程是一個普通用戶進程,linux里也無法動態(tài)的改變進程的用戶組尝盼,如何在root進程中運行我們編寫的秒裝代碼吞滞?繼續(xù)去android源代碼中找答案
????在Android系統(tǒng)中,所有的應用程序進程以及系統(tǒng)服務進程SystemServer都是由Zygote進程孕育(fork)出來的盾沫,我們知道裁赠,Android系統(tǒng)是基于Linux內(nèi)核的殿漠,而在Linux系統(tǒng)中,所有的進程都是init進程的子孫進程佩捞,也就是說绞幌,所有的進程都是直接或者間接地由init進程fork出來的。Zygote進程也不例外失尖,它是在系統(tǒng)啟動的過程啊奄,由init進程創(chuàng)建的。在系統(tǒng)啟動腳本system/core/rootdir/init.rc文件中掀潮,我們可以看到啟動Zygote進程的腳本命令:
前面的關(guān)鍵字service告訴init進程創(chuàng)建一個名為"zygote"的進程菇夸,這個zygote進程要執(zhí)行的程序是/system/bin/app_process,后面是要傳給app_process的參數(shù)仪吧。因此我們可以得知app_process是可以調(diào)用Java代碼的庄新。
讓我們看下app_process的說明:
root@android:/ # app_process
app_process [vm-options] cmd-dir [options] start-class-name [main-options]
vm-options – VM 選項
cmd-dir –父目錄 (/system/bin)
options –運行的參數(shù) :
–zygote
–start-system-server
–application (api>=14)
–nice-name=nice_proc_name (api>=14)
所以第二種秒裝實現(xiàn)思路應為
1:編寫InstallCommand.java類
public class InstallCommand{
public static void main(String[] args) {
int returnCode = installPackageCore(packageName, strInstallPath, installParam);
}
//核心安裝方法
private static int installPackageCore(String packageName, String path, String installToParam) {
? ? ? ? Object ipm = ReflectUtils.invokeStaticMethod("android.app.ActivityThread", "getPackageManager", null);
? ? ? ? final AtomicInteger resultType = new AtomicInteger(Code.SilentlyInstallCommandCoreInit);
? ? ? ? if (ipm == null) {
? ? ? ? ? ? resultType.set(Code.SilentlyInstallIPackageManagerNULL);
? ? ? ? ? ? return resultType.get();
? ? ? ? }
? ? ? ? Constructor constructor = null;
? ? ? ? try {
? ? ? ? ? ? constructor = ReflectUtils.getDeclaredConstructor("android.app.ApplicationPackageManager", Class.forName("android.app.ContextImpl"), Class.forName("android.content.pm.IPackageManager"));
? ? ? ? } catch (ClassNotFoundException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? log(TAG, "installPackageCore_getDeclaredConstructor:" + e.getMessage());
? ? ? ? }
? ? ? ? if (constructor == null) {
? ? ? ? ? ? resultType.set(Code.SilentlyInstallApplicationPackageManagerConstructorException);
? ? ? ? ? ? return resultType.get();
? ? ? ? }
? ? ? ? Object mApplicationPackageManager = null;
? ? ? ? try {
? ? ? ? ? ? Object context = null;
? ? ? ? ? ? if (Build.VERSION.SDK_INT >= 24) {
? ? ? ? ? ? ? ? context = createFakeContextImpl();
? ? ? ? ? ? ? ? if (context == null) {
? ? ? ? ? ? ? ? ? ? resultType.set(Code.SilentlyInstallCreateFakeContextException);
? ? ? ? ? ? ? ? ? ? return resultType.get();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? mApplicationPackageManager = constructor.newInstance(context, ipm);
? ? ? ? } catch (InstantiationException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? log(TAG, "installPackageCore_ConstructApplicationPackageManager:" + e.getMessage());
? ? ? ? } catch (IllegalAccessException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? log(TAG, "installPackageCore_ConstructApplicationPackageManager:" + e.getMessage());
? ? ? ? } catch (InvocationTargetException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? log(TAG, "installPackageCore_ConstructApplicationPackageManager:" + e.getMessage());
? ? ? ? }
? ? ? ? if (mApplicationPackageManager == null) {
? ? ? ? ? ? resultType.set(Code.SilentlyInstallConstructApplicationPackageManagerException);
? ? ? ? ? ? return resultType.get();
? ? ? ? }
? ? ? ? final Method method;
? ? ? ? final CountDownLatch countDownLatch = new CountDownLatch(1);
? ? ? ? try {
? ? ? ? ? ? method = ReflectUtils.getMethod(mApplicationPackageManager.getClass().getName(), "installPackage", Uri.class, Class.forName("android.content.pm.IPackageInstallObserver"), int.class, String.class);
? ? ? ? ? ? method.setAccessible(true);
? ? ? ? ? ? Object IPackageInstallObserver = WrapIPackageHelper.wrapIPackageInstallObserverStub(new IPackageInstallObserver.Stub() {
? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? public void packageInstalled(String packageName, int returnCode) throws RemoteException {
? ? ? ? ? ? ? ? ? ? log(TAG, String.format("IPackageInstallObserver packageInstalled packageName:%s,returnCode:%s", packageName, returnCode));
? ? ? ? ? ? ? ? ? ? resultType.set(returnCode);
? ? ? ? ? ? ? ? ? ? countDownLatch.countDown();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? ? ? method.invoke(mApplicationPackageManager, Uri.fromFile(new File(path)), IPackageInstallObserver, parseInstallParam(installToParam), packageName);
? ? ? ? ? ? countDownLatch.await(90, TimeUnit.SECONDS);
? ? ? ? } catch (ClassNotFoundException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? log(TAG, "installPackageCore_installPackage:" + e.getMessage());
? ? ? ? ? ? resultType.set(Code.SilentlyInstallClassNotFoundException);
? ? ? ? } catch (InvocationTargetException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? log(TAG, "installPackageCore_installPackage:" + e.getMessage());
? ? ? ? ? ? resultType.set(Code.SilentlyInstallInvocationTargetException);
? ? ? ? } catch (IllegalAccessException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? log(TAG, "installPackageCore_installPackage:" + e.getMessage());
? ? ? ? ? ? resultType.set(Code.SilentlyInstallIllegalAccessException);
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? log(TAG, "installPackageCore_installPackage:" + e.getMessage());
? ? ? ? ? ? resultType.set(Code.SilentlyInstallInterruptedException);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? log(TAG, "installPackageCore_installPackage:" + e.getMessage());
? ? ? ? ? ? resultType.set(Code.SilentlyInstallOtherException);
? ? ? ? }
? ? ? ? return resultType.get();
? ? }
? ? @TargetApi(24)
? ? private static Object createFakeContextImpl() {
? ? ? ? Looper.prepare();
? ? ? ? Constructor constructor = ReflectUtils.getDeclaredConstructor("android.app.ActivityThread");
? ? ? ? Object mActivityThread = null;
? ? ? ? try {
? ? ? ? ? ? mActivityThread = constructor.newInstance();
? ? ? ? } catch (InstantiationException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? log(TAG, "createFakeContextImpl:" + e);
? ? ? ? } catch (IllegalAccessException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? log(TAG, "createFakeContextImpl:" + e);
? ? ? ? } catch (InvocationTargetException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? StringBuffer s = new StringBuffer(e.getTargetException().toString() + "\n");
? ? ? ? ? ? StackTraceElement elements[] = e.getTargetException().getStackTrace();
? ? ? ? ? ? for (StackTraceElement element : elements) {
? ? ? ? ? ? ? ? s.append(element.toString()).append("\n");
? ? ? ? ? ? }
? ? ? ? ? ? log(TAG, "createFakeContextImpl:" + s.toString());
? ? ? ? }
? ? ? ? if (mActivityThread == null) {
? ? ? ? ? ? mActivityThread = ReflectUtils.invokeStaticMethod("android.app.ActivityThread", "systemMain", null);
? ? ? ? }
? ? ? ? if (mActivityThread != null) {
? ? ? ? ? ? Object mContext = ReflectUtils.invokeMethod(mActivityThread, "getSystemContext", null);
? ? ? ? ? ? if (mContext == null) {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? mContext = ReflectUtils.invokeStaticMethod("android.app.ContextImpl", "createSystemContext", new Class[]{Class.forName("android.app.ActivityThread")}, mActivityThread);
? ? ? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? log(TAG, "createFakeContextImpl mContext:" + mContext);
? ? ? ? ? ? return mContext;
? ? ? ? }
? ? ? ? return null;
? ? }
}
2:在通過Runtime.getRuntime().exec("su") 調(diào)用app_process啟動InstallCommand.java進行秒裝
未完待續(xù)(下一章會講解通過輔助功能進行智能安裝)