頻道欄目
首頁 > 程序開發 > 移動開發 > Android > 正文
Android開發之做一鍵批量卸載App功能的詳細講解
2018-07-30 14:26:54         來源:奮進的代碼  
收藏   我要投稿

首先準備一部已經Root的手機,然后打開Android Studio,下面我們開始快樂的寫代碼吧~

首先我們先分析具體的業務需求:

很簡單的一個需求,最主要的功能就是可以卸載App;

同時要求可以批量卸載;

既然能夠批量卸載,也就是說我們在UI交互上可以批量選擇;

能大量展示待卸載的App。

好的我們現在一步一步的來:首先我們先解決最主要的需求,卸載App!

有兩種方式可以實現App卸載:分為靜默方式和非靜默方式。

什么是靜默方式?意思就是說卸載完全是在系統后臺進行的,不需要用戶去點擊確認卸載。非靜默方式的意思顯而易見,卸載的時候需要用戶點擊確認,只有用戶確認卸載才會卸載。

我們先說非靜默方式卸載:

非靜默方式卸載的代碼如下;

 public void unstallApp(String pageName){

  Intent uninstallIntent = new Intent();
  uninstallIntent.setAction(Intent.ACTION_DELETE);
  uninstallIntent.setData(Uri.parse("package:"+pageName));
  
  startActivityForResult(uninstall_intent,1);

 }

從代碼中我們就可以看出來,這里開啟了一個活動,也就是所謂的應用卸載程序,然后把需要卸載的App包名交給它,它就會把這個App給卸載掉。這是正常的App卸載步驟。開啟這個應用卸載程序活動后,頁面就會跳轉到卸載頁面,然后等待用戶點擊確定或者取消,點擊確定就會執行卸載程序,點擊取消就會回退到原來的活動。在這里我們使用了startActivityForResult()方法來開啟應用卸載活動,目的是為了卸載完成后在回掉函數里面可以更新原來的App列表頁面。

非靜默方式代碼非常的簡單,也非常容易理解,但是這里有個不足之處,那就是如果我們一次性需要卸載十個APP應用,那么頁面將會跳轉十次,同時你也需要點擊十次確定!別忘了我們這里可是要求批量卸載,如果讓用戶去連續點擊十次確定,這樣會非常影響用戶體驗!所以非靜默方式卸載在這里使用并不是很好,靜默方式是更好的選擇!

靜默方式卸載:

靜默方式也就是意味著我們需要繞過安卓的界面,在后臺執行卸載命令,那么怎么做呢?很顯然,當然是使用命令了!使用命令的方式我們可以繞過安卓界面執行。

這里有兩種卸載App命令:

首先是adb命令:adbuninstall

還有一個pm命令:pm uninstall

我們可以看到這兩種命令寫法相同,命令的開頭不同,那么他們具體的差別在什么地方呢?應該用哪一種命令方式?還是兩種命令方式都合適呢?

我先不說區別,我們去實地的測試一下,首先我們先用adb命令去卸載。

代碼如下:

package com.example.uninstallapk;

import android.util.Log;

import java.io.DataOutputStream;

/**
 * Created by 王將 on 2018/7/23.
 */


//adb命令翻譯執行類
public class RootCmd {
 /***
  * @param command
  * @return
  */
 public static boolean exusecmd(String command) {
  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();
Log.e("updateFile", "======000==writeSuccess======");
process.waitFor();
  } catch (Exception e) {
Log.e("updateFile", "======111=writeError======" + e.toString());
return false;
  } finally {
try {
 if (os != null) {
  os.close();
 }
 if (process != null) {
  process.destroy();
 }
} catch (Exception e) {
 e.printStackTrace();
}
  }
  return true;
 }

 public static void unInstallApk(String pageName){

  exusecmd("adb uninstall "+pageName);
 }

}

主活動中我們調用:

RootCmd.unInstallApk("com.example.tset");

把想要卸載的App包名傳進去,運行一下,很快你就發現:整個應用崩潰了,出現了ANR問題,應用無反應。

好,我們改為pm命令試一下,結果發現成功了!

那么現在我們分析一下為什么adb命令會導致出現ANR問題,而pm命令就不會出現錯誤。

一個命令的下達,肯定會調用相應的方法去處理,只不過這個調用過程在系統的內部,我們外界是看不到的,只能得到命令執行的結果。就好比我們使用命令去卸載App應用,同樣也是在內部調用了卸載方法,那么具體這個方法是什么?在哪里呢?下面我們就去深入的探討一下。

Android系統卸載App應用都是調用了一個類 中方法,不管是非靜默模式還是靜默模式,這個類就是PackageInstaller類。當然Android系統安裝App也同樣是調用的它里面的方法,這個類功能從它的名字上就可以看出來:打包安裝程序。

當然這個類我們在平常的開發中是用不到的,同樣也是無法調用的,這個類同樣也是一個底層調用的類。在這個類中我們可以找到具體的卸載App方法,讓我們看一下源碼

/**
  * Uninstall the given package, removing it completely from the device. This
  * method is only available to the current "installer of record" for the
  * package.
  *
  * @param packageName The package to uninstall.
  * @param statusReceiver Where to deliver the result.
  */
 public void uninstall(@NonNull String packageName, @NonNull IntentSender statusReceiver) {
  uninstall(packageName, 0 /*flags*/, statusReceiver);
 }

 /**
  * Uninstall the given package, removing it completely from the device. This
  * method is only available to the current "installer of record" for the
  * package.
  *
  * @param packageName The package to uninstall.
  * @param flags Flags for uninstall.
  * @param statusReceiver Where to deliver the result.
  *
  * @hide
  */
 public void uninstall(@NonNull String packageName, @DeleteFlags int flags,
@NonNull IntentSender statusReceiver) {
  uninstall(new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
 flags, statusReceiver);
 }

 /**
  * Uninstall the given package with a specific version code, removing it
  * completely from the device. This method is only available to the current
  * "installer of record" for the package. If the version code of the package
  * does not match the one passed in the versioned package argument this
  * method is a no-op. Use {@link PackageManager#VERSION_CODE_HIGHEST} to
  * uninstall the latest version of the package.
  *
  * @param versionedPackage The versioned package to uninstall.
  * @param statusReceiver Where to deliver the result.
  */
 public void uninstall(@NonNull VersionedPackage versionedPackage,
@NonNull IntentSender statusReceiver) {
  uninstall(versionedPackage, 0 /*flags*/, statusReceiver);
 }

 /**
  * Uninstall the given package with a specific version code, removing it
  * completely from the device. This method is only available to the current
  * "installer of record" for the package. If the version code of the package
  * does not match the one passed in the versioned package argument this
  * method is a no-op. Use {@link PackageManager#VERSION_CODE_HIGHEST} to
  * uninstall the latest version of the package.
  *
  * @param versionedPackage The versioned package to uninstall.
  * @param flags Flags for uninstall.
  * @param statusReceiver Where to deliver the result.
  *
  * @hide
  */
 @RequiresPermission(anyOf = {
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.REQUEST_DELETE_PACKAGES})
 public void uninstall(@NonNull VersionedPackage versionedPackage, @DeleteFlags int flags,
@NonNull IntentSender statusReceiver) {
  Preconditions.checkNotNull(versionedPackage, "versionedPackage cannot be null");
  try {
mInstaller.uninstall(versionedPackage, mInstallerPackageName,
  flags, statusReceiver, mUserId);
  } catch (RemoteException e) {
throw e.rethrowFromSystemServer();
  }
 }

這個是PackageInstaller類中的四個uninstall()方法,具體的功能就是卸載App應用。

當然這四個方法用于卸載不同狀態的應用,具體的使用請看官方給出的描述文檔,這里不再具體的做出分析。

現在我們知道了卸載App調用的是PackageInstaller類的uninstall()方法,那么這個和命令的方式有什么關系呢?我們看一下PackageInstaller類的所處路徑你就明白了,PackageInstaller類的所處路徑為/android/content/pm/PackageInstaller.java,具體在博主這里的完整路徑為:

\

很明顯,在/pm路徑下。pm全稱package manager,意思包的管理者,pm命令說白了就是包管理命令,進一步說,只有使用pm命令才會調用/pm路徑下的底層方法,也就是說才會執行包文件的操作。這下你明白為什么使用adb會導致ANR問題了吧,因為程序找不到執行方法!

好了,現在我們解決了最重要的需求,靜默卸載App,那么接下來的需求就很簡單實現了,批量卸載,批量選擇,這里直接使用一個循環不停的執行卸載命令就好了。按照這個思路我們開始寫代碼。

首先是界面UI部分:




 
  

  
 

使用ScrollView嵌套一個LinearLayout布局來實現App列表,其中單個的App信息使用動態加載的形式添加。

下面是一個App信息子布局:




 
 

很簡單,兩個控件組成,一個ChexBox控件提供勾選,一個TextView用來展示App的標簽。

接下來我們就需要寫主活動中的邏輯性操作了:

首先貼上我們的MainActivity代碼:

public class MainActivity extends AppCompatActivity {

 LinearLayout linearLayout;

 List pages=new ArrayList<>();
 List views=new ArrayList<>();

 ProgressDialog progressDialog;

 List packageInfos=new ArrayList<>();

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  linearLayout = (LinearLayout) findViewById(R.id.linear1);

  Button button=(Button) findViewById(R.id.start_delete);


  PackageManager packageManager=getPackageManager();
  packageInfos=packageManager.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);

  int id=0;
  for (PackageInfo packageInfo:packageInfos){
String str=packageInfo.applicationInfo.loadLabel(getPackageManager()).toString();
linearLayout.addView(getChoiceView(linearLayout,str,id));
id++;
  }

  button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

 new DEleteApk().execute();
}
  });
 }

 private View getChoiceView(LinearLayout root, final String pageName, int id){
  final View view = LayoutInflater.from(this).inflate(R.layout.choice_layout, root, false);

  final CheckBox checkBox=(CheckBox) view.findViewById(R.id.page_id);
  final TextView textView=(TextView) view.findViewById(R.id.page_name);

  view.setTag(id);

  checkBox.setTag(view);

  checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

 if (isChecked){

  views.add((View) checkBox.getTag());
  pages.add((int) view.getTag());

 }else {

  View view1=(View) checkBox.getTag();
  views.remove(view1);
  pages.remove(getIndexPages((int) view1.getTag()));

 }

}
  });

  textView.setText(pageName);

  return view;
 }

 public int getIndexPages(int id){
  int index=0;
  int j=0;

  for (int i:pages){
if (i==id){
 index=j;
 break;
}
j++;
  }

  return index;
 }

 class DEleteApk extends AsyncTask {

  @Override
  protected void onPreExecute() {
progressDialog=new ProgressDialog(MainActivity.this);
progressDialog.setTitle("正在卸載中");
progressDialog.setMessage("請稍后...");
progressDialog.setCancelable(true);
progressDialog.show();
  }

  @Override
  protected Object doInBackground(Object[] objects) {
for (int id:pages){
 RootCmd.unInstallApk(packageInfos.get(id).packageName);
}
return true;
  }

  @Override
  protected void onPostExecute(Object o) {
progressDialog.dismiss();

pages.removeAll(pages);
for (View view:views){
 linearLayout.removeView(view);
}
views.removeAll(views);
  }
 }
}

在代碼中有兩部分講解一下:

首先是getChoiceView()方法。在這個方法中我們主要獲取用戶勾選的App是哪些。當用戶點擊勾選的時候,我們就把對應App的下標值給存下來,同時存下來的還有相應的子View,存放子View的目的是為了在卸載完成之后更新我們的App列表。在用戶點擊取消勾選,我們還需要把之前存放的相關信息給移除掉,確保卸載的都是用戶最終確定刪除的。

存好了相應的信息,下面就是執行pm命令部分。在這里我使用線程來開啟pm命令,可以很清楚地看到,在這里我使用了AsyncTask 框架。在線程開啟前,也就是pm命令開始之前,我們彈出一個ProgressDialog,目的就是告訴用戶正在卸載請稍等,因為pm命令執行起來到結束會需要一定的時間;然后就開始執行pm命令,使用循環挨著挨卸載List中用戶選定的App,執行結束后關閉ProgressDialog,然后清空我們的Liset,同時還要更改我們的UI界面。

這里選用AsyncTask 框架有一個好處,那就是可以明確的知道命令執行結束的時間,在命令結束之后更改UI。如果不使用AsyncTask 框架,那么就比較難以掌握pm命令執行結束的時候,畢竟這個也沒有什么相關的回掉函數,在結束后UI處理上難以下手。使用AsyncTask 框架后,就不需要擔心這個問題,執行結束后自然會執行收尾工作,這樣更新IUI就方便多了。

好了,本篇文章到此結束。有需要引用的請標明出處,謝謝!

點擊復制鏈接 與好友分享!回本站首頁
上一篇:Android開發之引用外部數據庫操作講解
下一篇:Android開發之占位符應用實例解析
相關文章
圖文推薦
點擊排行

關于我們 | 聯系我們 | 廣告服務 | 投資合作 | 版權申明 | 在線幫助 | 網站地圖 | 作品發布 | Vip技術培訓 | 舉報中心

版權所有: 紅黑聯盟--致力于做實用的IT技術學習網站

加拿大28火车判定方法