一、概述
最近公司項目爲了版本升級方便,對公司的項目做了平臺化管理,相應的也添加了新的功能。其中就包括卸載模塊,要求卸載平臺的時候附帶着卸載手機上已經安裝的與平臺相關的應用。
那麼要解決這個問題,我們就可能需要先來處理一下幾個問題:
1. 通過什麼樣的方式來監聽平臺被卸載這個事件
2. 卸載平臺的同時如何去卸載其他相關的應用
二、監聽卸載事件
1. 直接在平臺上註冊一個廣播來監聽這個卸載事件,但是仔細想想就會發現這是不可行的,因爲在系統發送卸載這個應用的廣播之前,這個應用就已經殺死並被卸載了,所以根本不會再接收到這個廣播了
2. 既然不能直接監聽卸載這個廣播,那麼我們是不是可以在卸載平臺之前做一些操作,之前有看過一個大神的帖子,發現在應用被卸載之前,系統會先刪除data目錄下對應的應用的文件夾。所以我準備通過FileObserver來監聽這個文件的狀態,如果是DELETE狀態就表示刪除。通過驗證發現是可以監聽到的,但是不知道是不是我的代碼有問題,執行速度跟不上,還沒來得級去卸載其他應用,平臺就被卸載完了。所以我放棄了這種方法。
3. 在每個apk中都註冊一個廣播,來監聽應用的卸載事件,然後根據包名來判斷
三、具體實現
1. 在相關應用裏面通過xml的形式註冊一個常住型廣播:
<receiver android:name=".broadcast.ListenerReceiver" android:enabled="true" android:exported="true"> <intent-filter android:priority="960"> <action android:name="android.intent.action.PACKAGE_ADDED" /> <action android:name="android.intent.action.PACKAGE_REMOVED" /> <action android:name="android.intent.action.BOOT_COMPLETED"/> <data android:scheme="package" /> </intent-filter> </receiver>在這裏,我添加了一個開機廣播的action,爲了防止手機重啓之後,沒有運行應用而無法監聽到卸載的這種情況。並設置了priority屬性,系統會根據這個屬性來決定接收這個廣播的先後順序。
2. 創建一個BroadcaseReceiver來接收這個廣播:
public class ListenerReceiver extends BroadcastReceiver { public ListenerReceiver() { } @Override public void onReceive(Context context, Intent intent) { //接收卸載廣播 if (intent.getAction().equals("android.intent.action.PACKAGE_REMOVED")) { String packageName = intent.getDataString(); if(packageName.equals("平臺包名") || packageName.equals("應用一的包名") || packageName.equals("應用二的包名") || packageName.equals("應用三的包名")){ ApkUtiles.hasInstall(context); abortBroadcast(); } } } }這裏先監聽卸載這個事件,然後根據包名來判斷卸載的是哪個應用,然後再進行相關操作,這裏我調用了absortBroadcast()這個方法來終止這個廣播,爲了防止多個應用重複對這個廣播進行監聽處理。
3. 判斷手機上是否已經安裝了我們的相關應用:
/** * 判斷是否已經安裝指定程序 */ public static void hasInstall(Context context){ PackageInfo packageInfo; String[] str = {"平臺包名" ,"應用一包名" , "應用二包名", "應用三包名" }; for(int i = 0; i < str.length ; i++){ try { packageInfo = context.getPackageManager().getPackageInfo(str[i], 0); }catch (PackageManager.NameNotFoundException e) { packageInfo = null; e.printStackTrace(); } if(packageInfo ==null){ LogUtils.e("沒有安裝 :" + str[i]); }else{ if(i == 0){ //表名卸載的是相關應用(不是平臺) return; } // LogUtils.e("安裝了程序 :" + str[i]); uninstall(context , str[i]); return; } } }這裏需要注意,str數組裏面存放的都是我們所有應用的包名,最後一個元素必須是當前註冊該廣播的應用的包名,否則會出現卸載不乾淨的情況,因爲廣播裏面有做absortBroadcast()處理。
4. 根據包名卸載指定的應用:
/** 卸載一個app */ public static void uninstall(Context context, String packageName) { //通過程序的包名創建URI Uri packageURI = Uri.parse("package:" + packageName); //創建Intent意圖 Intent intent = new Intent(Intent.ACTION_DELETE, packageURI); //執行卸載程序 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); }這裏有一點不是很清楚:如果沒有添加intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),就不會執行彈出讓用戶確認卸載應用的activity。這個應該和任務棧有關,以後有空再補補相關知識。
這是本人第一次寫博客,有寫的不好的地方或者錯誤的地方,希望大家能提出來一起交流
補充:
安卓3.1之後,google考慮到一些安全問題,避免一些流氓軟件、病毒什麼的幹壞事,所以對廣播機制做了一些改變。給廣播新添加了兩個Flag,分別是FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES,分別表示是否允許被force stop的應用能夠接收到這個廣播,並且系統默認給系統類型的廣播(例如:開機、卸載、短信等)添加了一個Intent.FLAG_EXCLUDE_STOPPED_PACKAGES,這也就意味着如果我們清理內存之後,我們的應用就會進入到force
stop狀態,也就不會再能接收到系統的廣播了。所以這也導致了我上門通過廣播的方式而無法監聽到平臺被卸載的bug。但是也沒辦法,這是系統廣播的機制決定的,也只能接受這個現實了。