来自:mjsws > 馆藏分类
配色: 字号:
Android开发之做一键批量卸载App功能的详细讲解
2018-12-15 | 阅:  转:  |  分享 
  
Android开发之做一键批量卸载App功能的详细讲解首先准备一部已经Root的手机,然后打开Android?Studio,下面我们开始快乐
的写代码吧~首先我们先分析具体的业务需求:很简单的一个需求,最主要的功能就是可以卸载App;同时要求可以批量卸载;既然能够批量卸载
,也就是说我们在UI交互上可以批量选择;能大量展示待卸载的App。好的我们现在一步一步的来:首先我们先解决最主要的需求,卸载App
!有两种方式可以实现App卸载:分为静默方式和非静默方式。什么是静默方式?意思就是说卸载完全是在系统后台进行的,不需要用户去点击确
认卸载。非静默方式的意思显而易见,卸载的时候需要用户点击确认,只有用户确认卸载才会卸载。我们先说非静默方式卸载:非静默方式卸载的代
码如下;?123456789publicvoidunstallApp(StringpageName){??Intentun
installIntent=newIntent();?uninstallIntent.setAction(Intent.AC
TION_DELETE);?uninstallIntent.setData(Uri.parse("package:"+pageNa
me));???startActivityForResult(uninstall_intent,1);?}从代码中我们就可以看出来
,这里开启了一个活动,也就是所谓的应用卸载程序,然后把需要卸载的App包名交给它,它就会把这个App给卸载掉。这是正常的App卸载
步骤。开启这个应用卸载程序活动后,页面就会跳转到卸载页面,然后等待用户点击确定或者取消,点击确定就会执行卸载程序,点击取消就会回退
到原来的活动。在这里我们使用了startActivityForResult()方法来开启应用卸载活动,目的是为了卸载完成后在回掉函
数里面可以更新原来的App列表页面。非静默方式代码非常的简单,也非常容易理解,但是这里有个不足之处,那就是如果我们一次性需要卸载十
个APP应用,那么页面将会跳转十次,同时你也需要点击十次确定!别忘了我们这里可是要求批量卸载,如果让用户去连续点击十次确定,这样会
非常影响用户体验!所以非静默方式卸载在这里使用并不是很好,静默方式是更好的选择!静默方式卸载:静默方式也就是意味着我们需要绕过安卓
的界面,在后台执行卸载命令,那么怎么做呢?很显然,当然是使用命令了!使用命令的方式我们可以绕过安卓界面执行。这里有两种卸载App命
令:首先是adb命令:adbuninstall还有一个pm命令:pmuninstall我们可以看到这
两种命令写法相同,命令的开头不同,那么他们具体的差别在什么地方呢?应该用哪一种命令方式?还是两种命令方式都合适呢?我先不说区别,我
们去实地的测试一下,首先我们先用adb命令去卸载。代码如下:?1234567891011121314151617181920212
2232425262728293031323334353637383940414243444546474849505152pack
agecom.example.uninstallapk;?importandroid.util.Log;?importjav
a.io.DataOutputStream;?/?Createdby王将on2018/7/23.?/??//ad
b命令翻译执行类publicclassRootCmd{?/?@paramcommand?@return?/
?publicstaticbooleanexusecmd(Stringcommand){?Processprocess
=null;?DataOutputStreamos=null;?try{process=Runtime.getRu
ntime().exec("su");os=newDataOutputStream(process.getOutputStr
eam());os.writeBytes(command+"\n");os.writeBytes("exit\n");os.f
lush();Log.e("updateFile","======000==writeSuccess======");proce
ss.waitFor();?}catch(Exceptione){Log.e("updateFile","======1
11=writeError======"+e.toString());returnfalse;?}finally{try
{?if(os!=null){?os.close();?}?if(process!=null){?process
.destroy();?}}catch(Exceptione){?e.printStackTrace();}?}?retu
rntrue;?}??publicstaticvoidunInstallApk(StringpageName){??ex
usecmd("adbuninstall"+pageName);?}?}主活动中我们调用:?1RootCmd.unInstal
lApk("com.example.tset");把想要卸载的App包名传进去,运行一下,很快你就发现:整个应用崩溃了,出现了AN
R问题,应用无反应。好,我们改为pm命令试一下,结果发现成功了!那么现在我们分析一下为什么adb命令会导致出现ANR问题,而pm命
令就不会出现错误。乐淘棋牌http://www.jiekeqipai.net一个命令的下达,肯定会调用相应的方法去处理,只不过这个
调用过程在系统的内部,我们外界是看不到的,只能得到命令执行的结果。就好比我们使用命令去卸载App应用,同样也是在内部调用了卸载方法
,那么具体这个方法是什么?在哪里呢?下面我们就去深入的探讨一下。Android系统卸载App应用都是调用了一个类中方法,不管是非
静默模式还是静默模式,这个类就是PackageInstaller类。当然Android系统安装App也同样是调用的它里面的方法,这
个类功能从它的名字上就可以看出来:打包安装程序。当然这个类我们在平常的开发中是用不到的,同样也是无法调用的,这个类同样也是一个底层
调用的类。在这个类中我们可以找到具体的卸载App方法,让我们看一下源码:?1234567891011121314151617181
92021222324252627282930313233343536373839404142434445464748495051
525354555657585960616263646566676869707172/?Uninstallthegiv
enpackage,removingitcompletelyfromthedevice.This?method
isonlyavailabletothecurrent"installerofrecord"forthe?
package.??@parampackageNameThepackagetouninstall.?@par
amstatusReceiverWheretodelivertheresult.?/?publicvoiduni
nstall(@NonNullStringpackageName,@NonNullIntentSenderstatusR
eceiver){?uninstall(packageName,0/flags/,statusReceiver);?}
??/?Uninstallthegivenpackage,removingitcompletelyfrom
thedevice.This?methodisonlyavailabletothecurrent"insta
llerofrecord"forthe?package.??@parampackageNameThepac
kagetouninstall.?@paramflagsFlagsforuninstall.?@params
tatusReceiverWheretodelivertheresult.??@hide?/?publicvo
iduninstall(@NonNullStringpackageName,@DeleteFlagsintflags,
@NonNullIntentSenderstatusReceiver){?uninstall(newVersionedPa
ckage(packageName,PackageManager.VERSION_CODE_HIGHEST),?flags,s
tatusReceiver);?}??/?Uninstallthegivenpackagewithaspeci
ficversioncode,removingit?completelyfromthedevice.This
methodisonlyavailabletothecurrent?"installerofrecord"f
orthepackage.Iftheversioncodeofthepackage?doesnotmat
chtheonepassedintheversionedpackageargumentthis?method
isano-op.Use{@linkPackageManager#VERSION_CODE_HIGHEST}to?
uninstallthelatestversionofthepackage.??@paramversione
dPackageTheversionedpackagetouninstall.?@paramstatusRecei
verWheretodelivertheresult.?/?publicvoiduninstall(@NonNul
lVersionedPackageversionedPackage,@NonNullIntentSenderstatusR
eceiver){?uninstall(versionedPackage,0/flags/,statusReceive
r);?}??/?Uninstallthegivenpackagewithaspecificversion
code,removingit?completelyfromthedevice.Thismethodison
lyavailabletothecurrent?"installerofrecord"forthepacka
ge.Iftheversioncodeofthepackage?doesnotmatchtheonep
assedintheversionedpackageargumentthis?methodisano-op.
Use{@linkPackageManager#VERSION_CODE_HIGHEST}to?uninstallt
helatestversionofthepackage.??@paramversionedPackageThe
versionedpackagetouninstall.?@paramflagsFlagsforuninsta
ll.?@paramstatusReceiverWheretodelivertheresult.??@hid
e?/?@RequiresPermission(anyOf={Manifest.permission.DELETE_PACK
AGES,Manifest.permission.REQUEST_DELETE_PACKAGES})?publicvoidun
install(@NonNullVersionedPackageversionedPackage,@DeleteFlags
intflags,@NonNullIntentSenderstatusReceiver){?Preconditions.c
heckNotNull(versionedPackage,"versionedPackagecannotbenull");
?try{mInstaller.uninstall(versionedPackage,mInstallerPackageNam
e,?flags,statusReceiver,mUserId);?}catch(RemoteExceptione){
throwe.rethrowFromSystemServer();?}?}这个是PackageInstaller类中的四个uni
nstall()方法,具体的功能就是卸载App应用。当然这四个方法用于卸载不同状态的应用,具体的使用请看官方给出的描述文档,这里不
再具体的做出分析。现在我们知道了卸载App调用的是PackageInstaller类的uninstall()方法,那么这个和命令的
方式有什么关系呢?我们看一下PackageInstaller类的所处路径你就明白了,PackageInstaller类的所处路径为
/android/content/pm/PackageInstaller.java,具体在博主这里的完整路径为:很明显,在/pm路
径下。pm全称packagemanager,意思包的管理者,pm命令说白了就是包管理命令,进一步说,只有使用pm命令才会调用/p
m路径下的底层方法,也就是说才会执行包文件的操作。这下你明白为什么使用adb会导致ANR问题了吧,因为程序找不到执行方法啊!纯真棋
牌http://www.267774.com好了,现在我们解决了最重要的需求,静默卸载App,那么接下来的需求就很简单实现了,批量
卸载,批量选择,这里直接使用一个循环不停的执行卸载命令就好了。按照这个思路我们开始写代码。首先是界面UI部分:?12345678<
!--xmlversion="1.0"encoding="utf-8"-->out_height="match_parent"android:layout_width="match_parent"and
roid:orientation="vertical"xmlns:android="https://schemas.androi
d.com/apk/res/android">??android:layout_weight="10"android:layout_width="match_parent">?<
linearlayoutandroid:id="@+id/linear1"android:layout_height="wra
p_content"android:layout_width="match_parent"android:orientatio
n="vertical">???+id/start_delete"android:layout_height="1dp"android:layout_weig
ht="1"android:layout_width="match_parent"android:text="一键卸载">button>使用ScrollView嵌套一个LinearLayout布局来实现App列表,其中单个
的App信息使用动态加载的形式添加。下面是一个App信息子布局:?123456android:layout_width="match_parent"android:orientation="horizon
tal"xmlns:android="https://schemas.android.com/apk/res/android">
???id="@+id/page_name"android:layout_height="wrap_content"android:
layout_width="wrap_content"android:text="应用包名"android:textcolor
="#000000">很简单,两个控件组成,一个Chex
Box控件提供勾选,一个TextView用来展示App的标签。638棋牌http://dadiqipaigw.cn接下来我们就需要
写主活动中的逻辑性操作了:首先贴上我们的MainActivity代码:?12345678910111213141516171819
20212223242526272829303132333435363738394041424344454647484950515
25354555657585960616263646566676869707172737475767778798081828384
85868788899091929394959697989910010110210310410510610710810911011
1112113114115116117118119120121publicclassMainActivityextends
AppCompatActivity{??LinearLayoutlinearLayout;??Listpa
ges=newArrayList<>();?Listviews=newArrayList<>();??Progr
essDialogprogressDialog;??ListpackageInfos=newArr
ayList<>();??@Override?protectedvoidonCreate(BundlesavedInstan
ceState){?super.onCreate(savedInstanceState);?setContentView(R.l
ayout.activity_main);??linearLayout=(LinearLayout)findViewById
(R.id.linear1);??Buttonbutton=(Button)findViewById(R.id.start_d
elete);???PackageManagerpackageManager=getPackageManager();?pack
ageInfos=packageManager.getInstalledPackages(PackageManager.GET_U
NINSTALLED_PACKAGES);??intid=0;?for(PackageInfopackageInfo:pac
kageInfos){Stringstr=packageInfo.applicationInfo.loadLabel(getPa
ckageManager()).toString();linearLayout.addView(getChoiceView(lin
earLayout,str,id));id++;?}??button.setOnClickListener(newView.On
ClickListener(){@OverridepublicvoidonClick(Viewv){??newDEle
teApk().execute();}?});?}??privateViewgetChoiceView(LinearLayou
troot,finalStringpageName,intid){?finalViewview=LayoutI
nflater.from(this).inflate(R.layout.choice_layout,root,false);?
?finalCheckBoxcheckBox=(CheckBox)view.findViewById(R.id.page_i
d);?finalTextViewtextView=(TextView)view.findViewById(R.id.pag
e_name);??view.setTag(id);??checkBox.setTag(view);??checkBox.setO
nCheckedChangeListener(newCompoundButton.OnCheckedChangeListener
(){@OverridepublicvoidonCheckedChanged(CompoundButtonbuttonVi
ew,booleanisChecked){??if(isChecked){??views.add((View)check
Box.getTag());?pages.add((int)view.getTag());??}else{??Viewvie
w1=(View)checkBox.getTag();?views.remove(view1);?pages.remove(ge
tIndexPages((int)view1.getTag()));??}?}?});??textView.setText(pa
geName);??returnview;?}??publicintgetIndexPages(intid){?inti
ndex=0;?intj=0;??for(inti:pages){if(i==id){?index=j;?break;}j
++;?}??returnindex;?}??classDEleteApkextendsAsyncTask{??@Ove
rride?protectedvoidonPreExecute(){progressDialog=newProgressD
ialog(MainActivity.this);progressDialog.setTitle("正在卸载中");progres
sDialog.setMessage("请稍后...");progressDialog.setCancelable(true);p
rogressDialog.show();?}??@Override?protectedObjectdoInBackgroun
d(Object[]objects){for(intid:pages){?RootCmd.unInstallApk(pac
kageInfos.get(id).packageName);}returntrue;?}??@Override?protect
edvoidonPostExecute(Objecto){progressDialog.dismiss();?pages.
removeAll(pages);for(Viewview: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就方便多了。
献花(0)
+1
(本文系mjsws首藏)