Android無(wú)需root實(shí)現(xiàn)apk的靜默安裝
Android的靜默安裝似乎是一個(gè)很有趣很誘人的東西,但是,用普通做法,如果手機(jī)沒(méi)有root權(quán)限的話,似乎很難實(shí)現(xiàn)靜默安裝,因?yàn)锳ndroid并不提供顯示的Intent調(diào)用,一般是通過(guò)以下方式安裝apk:
Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); startActivity(intent);
但是,這并沒(méi)有真正的實(shí)現(xiàn)靜默安裝,因?yàn)橛杏脩艚缑?,?huì)讓用戶知道。那么,怎么在后臺(tái)悄悄的安裝APK呢?只能試圖去看看Android系統(tǒng)源碼正常安裝APK的過(guò)程,我這邊下載的源碼是Android5.0系統(tǒng)的,5個(gè)G的大小,但是可能由于Android5.0有一些安全方面的更新,跟之前的版本還是有一定的差距的,但是,學(xué)會(huì)一個(gè)之后再去學(xué)另一個(gè)相似的過(guò)程,那就簡(jiǎn)單許多了,就像學(xué)會(huì)了C語(yǔ)言,再學(xué)Java,也并非什么難事。
Android系統(tǒng)把所有的Permission(權(quán)限)依據(jù)其潛在風(fēng)險(xiǎn)劃分為四個(gè)等級(jí),即"normal"、 "dangerous"、 "signature"、 "signatureOrSystem"。APK的安裝對(duì)應(yīng)的權(quán)限是 INSTALL_PACKAGES,權(quán)限等級(jí)屬于后兩者。所以,最終想實(shí)現(xiàn)APK的靜默安裝,必然需要一些特殊的處理,執(zhí)行安裝的這個(gè)進(jìn)程,須為系統(tǒng)進(jìn)程。
那么,我們就來(lái)看看Android自身是如何實(shí)現(xiàn)安裝APK的。安裝的命令是pm install... 我們定位到系統(tǒng)源碼的/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java這個(gè)文件,他實(shí)現(xiàn)了pm命令,我們看runInstall方法,這就是APK的安裝過(guò)程。
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); //注意!!最終就是調(diào)用這個(gè)方法來(lái)進(jìn)行安裝的
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);
}
}
知道了這個(gè)過(guò)程之后,就大概知道怎么做了。既然系統(tǒng)底層把這個(gè)API屏蔽了,那就想辦法去繞過(guò)這層屏蔽,來(lái)使用它。首先想到的就是使用AIDL,不知道AIDL這東西的,先問(wèn)度娘去吧~~在上面的代碼中,最終實(shí)現(xiàn)安裝的那一句話,mPm.installPackageAsUser(...),mPm是個(gè)什么東西?不難發(fā)現(xiàn),IPackageManager類型,那么這個(gè)類從哪里來(lái)?搜尋一下,位于/frameworks/base/core/java/android/content/pm這個(gè)包底下,拷貝到我們工程目錄底下,包名不能變,只拷貝這一個(gè)文件的話,一定是不行了,會(huì)報(bào)其他的一些aidl找不到,相應(yīng)地也拷貝過(guò)來(lái)。Android5.0中,aidl改動(dòng)還是比較大的,所以要拷貝很多東西過(guò)來(lái),還要進(jìn)行一些改動(dòng)...我也是花了挺久才改到他沒(méi)報(bào)錯(cuò)。
最終,工程的目錄如下所示~~

那么,如何來(lái)使用它呢?
- 1、先獲取系統(tǒng)服務(wù)android.os.ServiceManager,這個(gè)又是隱藏的,怎么辦?考驗(yàn)Java水平的時(shí)候到了~~沒(méi)錯(cuò),用反射機(jī)制,來(lái)獲取ServiceManager類,以及該類里面的方法;
- 2、有了服務(wù)之后,我們就要去拿到IPackageManager這個(gè)對(duì)象;
- 3、調(diào)用IPackageManager里面的installPackage方法進(jìn)行安裝;
實(shí)現(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點(diǎn)擊事件
* @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));
// 通過(guò)Java反射機(jī)制獲取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í)行安裝(方法及詳細(xì)參數(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();
}
}
每個(gè)版本的系統(tǒng)源碼里面的aidl可能會(huì)不一樣,所以具體調(diào)用的方法和參數(shù),還得根據(jù)實(shí)際情況而定,需要去仔細(xì)閱讀Pm.java這個(gè)文件的源碼。
在其他版本可能只需要拷貝這4個(gè)文件:PackageManager.java、 IPackageDeleteObserver.aidl 、IPackagerInstallObserver.aidl、 IPackageMoveObserver.aidl
然后,還需在配置清單文件里面添加INSTALL_PACKAGE權(quán)限
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
然后把該應(yīng)用的uid設(shè)置為系統(tǒng)級(jí)別的,在manifest標(biāo)簽下添加以下屬性
android:sharedUserId="android.uid.system"
僅僅這樣的話,還是沒(méi)法實(shí)現(xiàn)靜默安裝,因?yàn)橄到y(tǒng)并不認(rèn)為你這個(gè)app是系統(tǒng)級(jí)別的應(yīng)用,所以,還應(yīng)該對(duì)該應(yīng)用的APK進(jìn)行系統(tǒng)簽名(注意:不是那個(gè)靜默安裝的APK,是這個(gè)實(shí)現(xiàn)靜默安裝程序的APK)。簽名過(guò)程如下:
總共需要三個(gè)文件:
- 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
之后,把簽名過(guò)后的APK安裝到手機(jī)上,打開,點(diǎn)擊靜默安裝,在去程序頁(yè)看看,發(fā)現(xiàn)安裝成功~~

更多內(nèi)容可以參考專題《android安裝配置教程》進(jìn)行學(xué)習(xí)。
本文主要是提供了一種實(shí)現(xiàn)靜默安裝的思路,但是具體怎么做到兼容各個(gè)系統(tǒng),舉一反三,
相關(guān)文章
Android添加指紋解鎖功能的實(shí)現(xiàn)代碼
當(dāng)開發(fā)的APP需要加密驗(yàn)證時(shí)可以考慮添加系統(tǒng)指紋解鎖功能。這篇文章主要介紹了Android添加指紋解鎖功能的實(shí)現(xiàn)代碼,需要的朋友可以參考下2018-07-07
解決webview調(diào)用goBack()返回上一頁(yè)自動(dòng)刷新閃白的情況
本文主要介紹了解決webview調(diào)用goBack()返回上一頁(yè)自動(dòng)刷新閃白的情況。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-03-03
Android Studio中生成aar文件及本地方式使用aar文件的方法
這篇文章給大家講解Android Studio中生成aar文件以及本地方式使用aar文件的方法,也就是說(shuō) *.jar 與 *.aar 的生成與*.aar導(dǎo)入項(xiàng)目方法,本文給大家介紹的非常詳細(xì),需要的朋友參考下吧2017-12-12
Android 廣播監(jiān)聽網(wǎng)絡(luò)狀態(tài)詳解及實(shí)例代碼
這篇文章主要介紹了Android 廣播監(jiān)聽網(wǎng)絡(luò)狀態(tài)詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02
Android自定義Notification添加點(diǎn)擊事件
這篇文章主要為大家詳細(xì)介紹了Android自定義Notification添加點(diǎn)擊事件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
安卓(Android)實(shí)現(xiàn)3DTouch效果
3DTouch是什么效果的大家應(yīng)該都知道了。本文將介紹在Android中如何實(shí)現(xiàn)3DTouch的效果,有需要的可以參考學(xué)習(xí)。2016-08-08
Android開發(fā)Launcher進(jìn)程啟動(dòng)流程
這篇文章主要為大家介紹了Android開發(fā)Launcher進(jìn)程啟動(dòng)流程示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
Android EditText隨輸入法一起移動(dòng)并懸浮在輸入法之上的示例代碼
這篇文章主要介紹了Android EditText隨輸入法一起移動(dòng)并懸浮在輸入法之上,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06

