Android無需root實現(xiàn)apk的靜默安裝
Android的靜默安裝似乎是一個很有趣很誘人的東西,但是,用普通做法,如果手機沒有root權(quán)限的話,似乎很難實現(xiàn)靜默安裝,因為Android并不提供顯示的Intent調(diào)用,一般是通過以下方式安裝apk:
Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); startActivity(intent);
但是,這并沒有真正的實現(xiàn)靜默安裝,因為有用戶界面,會讓用戶知道。那么,怎么在后臺悄悄的安裝APK呢?只能試圖去看看Android系統(tǒng)源碼正常安裝APK的過程,我這邊下載的源碼是Android5.0系統(tǒng)的,5個G的大小,但是可能由于Android5.0有一些安全方面的更新,跟之前的版本還是有一定的差距的,但是,學會一個之后再去學另一個相似的過程,那就簡單許多了,就像學會了C語言,再學Java,也并非什么難事。
Android系統(tǒng)把所有的Permission(權(quán)限)依據(jù)其潛在風險劃分為四個等級,即"normal"、 "dangerous"、 "signature"、 "signatureOrSystem"。APK的安裝對應的權(quán)限是 INSTALL_PACKAGES,權(quán)限等級屬于后兩者。所以,最終想實現(xiàn)APK的靜默安裝,必然需要一些特殊的處理,執(zhí)行安裝的這個進程,須為系統(tǒng)進程。
那么,我們就來看看Android自身是如何實現(xiàn)安裝APK的。安裝的命令是pm install... 我們定位到系統(tǒng)源碼的/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java這個文件,他實現(xiàn)了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); //注意?。∽罱K就是調(diào)用這個方法來進行安裝的 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); } }
知道了這個過程之后,就大概知道怎么做了。既然系統(tǒng)底層把這個API屏蔽了,那就想辦法去繞過這層屏蔽,來使用它。首先想到的就是使用AIDL,不知道AIDL這東西的,先問度娘去吧~~在上面的代碼中,最終實現(xiàn)安裝的那一句話,mPm.installPackageAsUser(...),mPm是個什么東西?不難發(fā)現(xiàn),IPackageManager類型,那么這個類從哪里來?搜尋一下,位于/frameworks/base/core/java/android/content/pm這個包底下,拷貝到我們工程目錄底下,包名不能變,只拷貝這一個文件的話,一定是不行了,會報其他的一些aidl找不到,相應地也拷貝過來。Android5.0中,aidl改動還是比較大的,所以要拷貝很多東西過來,還要進行一些改動...我也是花了挺久才改到他沒報錯。
最終,工程的目錄如下所示~~
那么,如何來使用它呢?
- 1、先獲取系統(tǒng)服務android.os.ServiceManager,這個又是隱藏的,怎么辦?考驗Java水平的時候到了~~沒錯,用反射機制,來獲取ServiceManager類,以及該類里面的方法;
- 2、有了服務之后,我們就要去拿到IPackageManager這個對象;
- 3、調(diào)用IPackageManager里面的installPackage方法進行安裝;
實現(xiàn)代碼如下:
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); // 執(zhí)行安裝(方法及詳細參數(shù),可能因不同系統(tǒng)而異) ipm.installPackage(fileName, new PackageInstallObserver(), 2, null, verificationParams, ""); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } // 用于顯示結(jié)果 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(); } }
每個版本的系統(tǒng)源碼里面的aidl可能會不一樣,所以具體調(diào)用的方法和參數(shù),還得根據(jù)實際情況而定,需要去仔細閱讀Pm.java這個文件的源碼。
在其他版本可能只需要拷貝這4個文件:PackageManager.java、 IPackageDeleteObserver.aidl 、IPackagerInstallObserver.aidl、 IPackageMoveObserver.aidl
然后,還需在配置清單文件里面添加INSTALL_PACKAGE權(quán)限
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
然后把該應用的uid設(shè)置為系統(tǒng)級別的,在manifest標簽下添加以下屬性
android:sharedUserId="android.uid.system"
僅僅這樣的話,還是沒法實現(xiàn)靜默安裝,因為系統(tǒng)并不認為你這個app是系統(tǒng)級別的應用,所以,還應該對該應用的APK進行系統(tǒng)簽名(注意:不是那個靜默安裝的APK,是這個實現(xiàn)靜默安裝程序的APK)。簽名過程如下:
總共需要三個文件:
- 1、SignApk.jar %系統(tǒng)源碼%/out/host/linux-x86/framework/signapk.jar
- 2、platform.x509.pem %系統(tǒng)源碼%/build/target/product/security/platform.x509.pem
- 3、platform.pk8 %系統(tǒng)源碼%/build/target/product/security/platform.pk8
打開終端,執(zhí)行命令 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安裝到手機上,打開,點擊靜默安裝,在去程序頁看看,發(fā)現(xiàn)安裝成功~~
更多內(nèi)容可以參考專題《android安裝配置教程》進行學習。
本文主要是提供了一種實現(xiàn)靜默安裝的思路,但是具體怎么做到兼容各個系統(tǒng),舉一反三,
相關(guān)文章
Android ShareSDK快速實現(xiàn)分享功能
這篇文章主要介紹了Android ShareSDK快速實現(xiàn)分享功能的相關(guān)資料,需要的朋友可以參考下2016-02-02Flutter中實現(xiàn)交互式Webview的方法詳解
Flutter是一款強大的跨平臺移動應用開發(fā)框架,而Webview則是在應用中展示W(wǎng)eb內(nèi)容的重要組件,本文將介紹如何在Flutter應用中實現(xiàn)交互式的Webview,快跟隨小編一起學習一下吧2023-09-09Android開發(fā)獲取手機Mac地址適配所有Android版本
這篇文章主要介紹了Android開發(fā)獲取手機Mac地址適配所有Android版本,需要的朋友可以參考下2020-03-03android6.0權(quán)限動態(tài)申請框架permissiondispatcher的方法
下面小編就為大家分享一篇android6.0權(quán)限動態(tài)申請框架permissiondispatcher的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01Android仿淘寶view滑動至屏幕頂部會一直停留在頂部的位置
這篇文章主要介紹了Android仿淘寶view滑動至屏幕頂部會一直停留在頂部的位置的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-11-11