由於apk的升級在所有的應用中都會使用,所以apk靜默升級是大部分應用必不可少的技術研究點,用普通做法,如果手機沒有root權限的話,似乎很難實現靜默安裝,因爲Android並不提供顯示的Intent調用,一般是通過以下方式安裝apk:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);
但是,這樣的普通升級方式需要用戶手動操作才能完成,那麼接下來我們就通過本人之前的實現思路一步步詳細介紹一下靜默安裝的實現思路吧!
Android系統把所有的Permission(權限)依據其潛在風險劃分爲四個等級,即”normal”、 “dangerous”、 “signature”、 “signatureOrSystem”。APK的安裝對應的權限是 INSTALL_PACKAGES,權限等級屬於後兩者。所以,最終想實現APK的靜默安裝,必然需要一些特殊的處理,執行安裝的這個進程,須爲系統進程。目前,絕大多數手機廠商爲了保證系統的安全性,都或多或少地將系統進行修改,所以我們必須要做好面對非常蛋疼的兼容性問題的心理準備。Android自身是實現安裝APK的原理是通過執行pm命pm install…,其系統源碼的/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java這個文件,具體這個文件的實現方式自己如果感興趣可以打開看看。
瞭解了系統安裝的基本原理後,我們看到系統已經屏蔽了自動安裝的api了,那麼我們就換一種實現思路,繞過這個瓶頸,那麼就必須使用AIDL這個協議了,通過我們對pm.jave這個文件的查閱發現最終實現安裝的方法是mPm.installPackageAsUser(…),那麼mPm指的是IPackageManager類,而這個類便是位於/frameworks/base/core/java/android/content/pm這個包底下.好了,下面是具體步驟:
1.在項目app/src/main下新建aidl目錄,然後到源碼api項目中拷貝/frameworks/base/core/java/android/content/pm/底下6個aidl基礎類到我們工程目錄底下,包名不能變,如下圖所示:
2.在導航欄中的Bulid中Make Project一下,注意這裏IPackageManager.aidl有可能會報錯,你只需要把項目的build.gradle中minSdkVersion版本號改成5.0版本的就成,如果Make Project完沒有任何異常出現,那麼打開app/build/generated/source/aidl/debug文件夾,就會看到aidl通過系統自動生成的java文件,如下圖所示:
3.將生成的java文件連同文件夾目錄一起拷貝到app/src/main/jave目錄下:
並且刪除main/目錄下的aidl文件目錄,一定要刪除,不然後面會報錯!
4.這時候我們運行打包一個高新apk放在項目的assets中,最爲我們要安裝的新apk:
5.代碼中我們將assets中的新apk拷貝到scard中:
new Thread(new Runnable() {
@Override
public void run() {
File file = new File(Environment.getExternalStorageDirectory().getPath() + File.separator + "newDemo.apk");
try {
if (file.exists())
file.delete();
InputStream inputStream = MainActivity.this.getAssets().open("newDeamo.apk");
FileOutputStream fileOutputStream = new FileOutputStream(file);
byte buffer[] = new byte[1024 * 1024 * 5];
int len = -1;
// 將輸入流中的內容先輸入到buffer中緩存,然後用輸出流寫到文件中
while ((len = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, len);
}
inputStream.close();
fileOutputStream.flush();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
6.這一步是關鍵!拷貝完我們要安裝的應用了,接着就要實現安裝:
/***** 表示安裝時以更新方式安裝,即app不存在時安裝,否則進行卸載再安裝 ****/
private static final int INSTALL_REPLACE_EXISTING = 0x00000002;
/**
* 安裝
*/
public void onClick_install(String sdPath) {
PackageInstallObserver installObserver = new PackageInstallObserver();
try {
Class<?> ServiceManager = Class.forName("android.os.ServiceManager");
Method getService = ServiceManager.getDeclaredMethod("getService", String.class);
getService.setAccessible(true);
IBinder packAgeBinder = (IBinder) getService.invoke(null, "package");
IPackageManager iPm = IPackageManager.Stub.asInterface(packAgeBinder);
iPm.installPackage(Uri.fromFile(new File(sdPath)), installObserver, INSTALL_REPLACE_EXISTING, new File(sdPath).getPath());
} catch (Exception e) {
e.printStackTrace();
try {
installObserver.packageInstalled(null, -1);
} catch (RemoteException ignore) {
e.printStackTrace();
}
}
}
1)由於android.os.ServiceManager這個類是系統一個隱藏的類,所以這裏需要利用反射來找出它;
2)找到了服務對象,那麼我們就利用服務對象拿到IPackageManager這個對象;
3)通過IPackageManager調用installPackage()方法;
4)而PackageInstallObserver是一個監聽器,具體實現代碼如下:
/**
* 安裝監聽
*/
public class PackageInstallObserver extends IPackageInstallObserver.Stub {
@Override
public void packageInstalled(String packageName, int returnCode) throws RemoteException {
// 返回1表示安裝成功,否則安裝失敗
if (returnCode == 1) {
// 表示要啓動的應用的包名和啓動類
startApp(packageName, startActivityName);
Log.e("Installed", "安裝成功!");
} else {
Log.e("Installed", "安裝失敗!");
}
}
}
7.如果安裝成功後我們就通過RunTime執行命令啓動該應用:
public static boolean startApp(String packageName, String startActivityName) {
boolean isSuccess = false;
String cmd = "am start -n " + packageName + "/" + startActivityName + " \n";
Process process = null;
try {
process = Runtime.getRuntime().exec(cmd);
int value = process.waitFor();
return returnResult(value);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (process != null) {
process.destroy();
}
}
return isSuccess;
}
private static boolean returnResult(int value) {
// 代表成功
if (value == 0) {
return true;
} else if (value == 1) { // 失敗
return false;
} else { // 未知情況
return false;
}
}
8.一定記得最後要配置權限和聲明“應用的身份”,應用的身份指的是將我們的應用上升到系統級別,也就是在manifest中添加android:sharedUserId="android.uid.system"
,具體完整的配置如下圖:
注意我標註的地方是必不可少的配置哦!
9.到這一步基本上就剩打包簽名了,這裏打包需要注意一點,就是我們必須告訴系統我們的apk應用是系統應用,那麼既然是系統應用,打包簽名就必須要利用系統打包簽名工具來完成,工具便是下圖這幾個:
這裏的工具包在我的個人雲盤上,如果你發現地址不對記得留言給我,我重新發布即可!工具包地址:https://pan.baidu.com/s/1kUVeWWR,下載完簽名文件後,然後將我們未簽名的apk和這幾個工具包放在一個文件夾裏,然後在終端/dos中來到目錄中執行命令:java -jar SignApk.jar platform.x509.pem platform.pk8 xxx.apk xxx_new.apk,這裏的xxx.apk指的是你未簽名的應用,xxx_new.apk當然就是要生成的簽名apk,執行完命令後如果不報錯,那麼恭喜你就完成了所有操作了!
10.接下來就是驗證了。如何驗證這個不用說,就是將我們簽名後的apk安裝到手機中,然後點擊靜默安裝的按鈕,你會發現應用退出了並且重啓了,這時候你會看到界面中的版本號便是我們剛開始高版本的apk:
原始包:
靜默安裝後:
Demo已上傳至個人雲盤,歡迎下載:https://pan.baidu.com/s/1boBI00J
小結:
靜默安裝對於系統來說是一個敏感的話題,因爲它嚴重涉及到安全性和私密性的問題,所以同樣的代碼,同樣的api也許在不同廠商的設備上表現的也不太一樣,所以我們對於靜默安裝要謹慎使用,並且要不斷的嘗試瞭解底層的實現原理和多看看類似的api文檔,拓展我們的知識層面和認知深度、廣度!