Day09##
9.1获取正在运行的进程的信息 #
昨天将软件管理条目功能都实现了,今天要做的是进程管理条目
看金山卫士的进程管理界面中要展示的数据,有包名,名称,图标,占用内存,是否是用户进程
获取出来的进程信息很多,这些进程信息要存放到一个集合List<E>中 ,里边需要一个类型
因为进程信息里边包含有包名,名称,图标,占用内存,是否是用户进程,要想一次性获取出来就必须要找一个保存的载体,这个载体我们用的就是bean类,写个TaskInfo的bean类
然后生成get,set方法,同时生成空参数的构造方法,再来个多个参数(所有参数)的构造方法,再来个toString方法
public class TaskInfo {
//包名
private String packagname;
//名称
private String name;
//图标
private Drawable icon;
//占用内存
private long ramsize;
//是否是用户进程
private boolean isUser;
//判断checkbox是否被选中,保存就是当前条目的checkbox的状态,默认就是false
private boolean isChecked;
public String getPackagname() {
return packagname;
}
public void setPackagname(String packagname) {
this.packagname = packagname;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Drawable getIcon() {
return icon;
}
public void setIcon(Drawable icon) {
this.icon = icon;
}
public long getRamsize() {
return ramsize;
}
public void setRamsize(long ramsize) {
this.ramsize = ramsize;
}
public boolean isUser() {
return isUser;
}
public void setUser(boolean isUser) {
this.isUser = isUser;
}
public TaskInfo() {
super();
// TODO Auto-generated constructor stub
}
public TaskInfo(String packagname, String name, Drawable icon,
long ramsize, boolean isUser) {
super();
this.packagname = packagname;
this.name = name;
this.icon = icon;
this.ramsize = ramsize;
this.isUser = isUser;
}
@Override
public String toString() {
return "TaskInfo [packagname=" + packagname + ", name=" + name
“, icon=” + icon + “, ramsize=” + ramsize + “, isUser=”
isUser + “]”;
}
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean isChecked) {
this.isChecked = isChecked;
}
}
获取获取正在运行的进程的信息属于业务操作,需要在engine包下创建一个业务类TaskEngine.java,里边创建一个getTaskInfos方法
在这个方法中获取正在运行的进程的信息
获取正在运行的进程的信息,这里边有很多进程,返回时要返回的是list集合,这个操作和获取应用程序信息的操作一样,在List的泛型处来个名称为TaskInfo的bean类
要想获取正在运行的进程的信息,首先就要获取一个进程的管理者ActivityManager,它是通过上下文获取的,所以说这里要在getTaskInfos方法中传入参数Context
获取到了包的管理者am,am中有个getRunningAppProcesses()方法,是获取正在运行的进程信息,返回一个List集合,泛型是RunningAppProcessInfo
将这个正在运行的进程信息runningAppProcesses进行for循环
从runningAppProcessInfo里边可以遍历出进程的名称packageName,其实进程名称就是我们的包名,所以把它就叫做packagName
使用am调用getProcessMemoryInfo(pids)
参数pids类型是int[],之所以是int类型的数组,是因为传入几个进程的pid就返回几个进程所占用的空间
所以参数pids这里可以new一个int类型的数组,使用runningAppProcessInfo调用pid,这个是我们当前进程的pid
返回一个MemoryInfo[]
runningAppProcessInfo.pid相当于传入了一个pid, 那就有1个进程所占用的空间
所以MemoryInfo[]中其实就一个数据,所以processMemoryInfo[0]这里写个0,就可以取出相应的数据了
用processMemoryInfo[0]调用getTotalPss(),会将你的内存转化为kB,返回一个int类型的值,把它叫做totalPss
TaskInfo.java中要的类型是long类型,kb转化成long类型:totalPss*1024,它会返回一个long类型的ramsize,这样就获取到进程所占用的内存信息ramsize了
名称name,图标icon是在进程的packageManager中的application中可以获取到,这个就不是用ActivityManager了,要用到PackageManager
获取到PackageManager之后,就可以通过pm去getApplicationInfo(packageName,flags),参数packageName就是包名
参数flags就是标签,前边已经讲过,设置为0表示要获取所有的信息,这里会有一个异常,try,catch捕获一下
NameNotFoundException这个异常是一个找不到包名packageName的异常,其实这个异常不存在的,因为是通过进程名称processName去获取的
在applicationInfo中有个loadIcon(pm),传一个PackageManager进去,返回一个Drawable类型的icon,这个是获取图标
在applicationInfo中有个loadLabel(pm),注意不要忘记toString(),这些和前边做的操作一模一样
TaskInfo.java中还有一个isUser字段没有获取到,这个操作和软件管理条目中的操作一模一样,要获取application的所有标签flags
紧接着要判断是否是用户进程,这个操作还记得吗?与运算
flags与上一个applicationInfo.FLAG_SYSTEM,这里边只有SYSTEM,没有用户的进程,通过判断是否是系统进程,如果恒等于applicationInfo.FLAG_SYSTEM代表是系统进程,else是用户进程
在if判断外边来个boolean类型的isUser,如果是系统进程就将isUser设置为false,如果是用户进程就将isUser设置为true
获取进程的操作做完后,接下来要将获取到的信息保存到TaskInfo里边
因为这里有一个异常捕获,所以没办法使用TaskInfo多个参数(全部参数)的构造方法来给TaskInfo填充数据
所以只能一步一步来做,比如在增强for里边,先new一个TaskInfo对象
然后通过taskInfo.setPackagName(packagName)将包名packagName保存到TaskInfo中
然后通过taskInfo.setRamsize(ramsize),将进程所占用的内存信息ramsize传入TaskInfo
然后通过taskInfo.setIcon(icon),将图标保存到TaskInfo
然后通过taskInfo.setName(name),将应用的名称保存到TaskInfo
然后通过taskInfo.setUser(isUser),将是否是用户进程保存到TaskInfo
将数据保存到TaskInfo之后,要将TaskInfo添加到集合中,首先在getTaskInfo方法的局部变量处new一个List<TaskInfo>
把它叫做list,然后给list去add(taskInfo)即可,最后不要忘记将list去return一下
public class TaskEngine {
/**
-
获取正在运行的进程的信息
*/
public static List getTaskInfos(Context context){List<TaskInfo> list = new ArrayList<TaskInfo>(); //进程的管理者 ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); PackageManager pm = context.getPackageManager(); //获取正在运行进程信息 List<RunningAppProcessInfo> runningAppProcesses = am.getRunningAppProcesses(); for (RunningAppProcessInfo runningAppProcessInfo : runningAppProcesses) { TaskInfo taskInfo = new TaskInfo(); //获取进程的名称,就是包名 String packagName = runningAppProcessInfo.processName; taskInfo.setPackagname(packagName); //获取相应的进程所占用的空间,int[] 你传入几个进程pid就返回几个进程的所占用的空间 MemoryInfo[] processMemoryInfo = am.getProcessMemoryInfo(new int[]{runningAppProcessInfo.pid}); //将获取的内存信息转化kb的形式 int totalPss = processMemoryInfo[0].getTotalPss(); //获取进程所占用的内存信息 long ramsize = totalPss*1024; taskInfo.setRamsize(ramsize); try{ //获取applicationinfo,根据进程的名称获取相应进程的applicationinfo信息 ApplicationInfo applicationInfo = pm.getApplicationInfo(packagName, 0); //获取图标 Drawable icon = applicationInfo.loadIcon(pm); taskInfo.setIcon(icon); //获取应用的名称 String name = applicationInfo.loadLabel(pm).toString(); taskInfo.setName(name); //获取application的所有标签 int flags = applicationInfo.flags; boolean isUser; //判断是否用户进程 if ((flags & applicationInfo.FLAG_SYSTEM) == applicationInfo.FLAG_SYSTEM) { //系统进程 isUser = false; }else{ //用户进程 isUser = true; } taskInfo.setUser(isUser); } catch(NameNotFoundException e){ e.printStackTrace(); } //添加到集合中 list.add(taskInfo); } return list; } }
获取正在运行的进程的信息的操作就做完了,和前边讲的获取软件信息的操作如出一辙,只不过改成用ActivityManager加上PackageManager去实现的
9.2界面布局及bug的处理#
将获取出来的正在运行的进程的信息全部展示出来,接下来就要去实现下进程管理界面
进程管理条目的界面和软件管理条目的界面非常像,只不过底部多了个“一键清理”和“设置”按钮,可以完全粘贴复制软件管理的布局文件
首先来实现下进程管理条目的点击事件
到HomeActivity中的setOnItemClickListener中的onItemClick方法中再增加一个case 3:进程管理,实现跳转到TaskManagerActivity的操作:
case 3://进程管理
Intent intent3 = new Intent(HomeActivity.this,TaskManagerActivity.class);
startActivity(intent3);
break;
接下来去创建TaskManagerActivity.java,然后到清单文件中配置,紧接着到TaskManagerActivity中重写它的OnCreate方法,然后在onCreate方法中通过setContentView去加载布局activity_taskmanager.xml
那布局文件activity_taskmanager.xml没有,直接复制SoftManagerActivity的布局文件activity_softmanager.xml,然后改名为activity_taskmanager.xml,然后修改成需要的布局:
activity_taskmanager.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="进程管理"
android:textSize="25sp"
android:gravity="center_horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:background="#8866ff00"
/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
<TextView
android:id="@+id/tv_taskmanager_process_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运行中进程:5个"
android:gravity="center_horizontal"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:layout_marginLeft="20dp"
/>
<TextView
android:id="@+id/tv_taskmanager_ramortotal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="剩余/总内存:120MB/502M"
android:gravity="center_horizontal"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:layout_toRightOf="@+id/tv_taskmanager_process_count"
android:layout_marginLeft="20dp"
/>
</RelativeLayout>
<!-- FrameLayout : 帧布局,会用在视频播放器
在布局文件中最下面的控件,在显示的时候是在最上边
layout_weight : 设置渲染优先级的作用,值约到优先级越低
-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
<ListView
android:id="@+id/lv_taskmanager_process"
android:layout_width="match_parent"
android:layout_height="match_parent"
到TaskManagerActivity的onCreate中把控件初始化出来
在TaskManagerActivity中第一步要加载数据,那要去调用获取应用进程的方法了
找到SoftManagerActivity,按照它里边的步骤来去拷贝,首先在TaskManagerActivity的onCreate方法中调用了fillData方法
TaskManagerActivity中的onCreate方法也调用下这个方法,并把fillData拷贝到TaskManagerActivity
将fillData方法拷贝过来后报很多错误,需要去改下:
将lv_softmanager_applications.setAdapter(myAdapter)改为:lv_taskmanager_process.setAdapter(myAdapter)
这个myAdapter还没有,先不管它,紧接着在来看,doinBack方法中还有一个List
注意这里边的方法得改一下,将list = AppEngine.getAppInfos(getApplicationContext())改为
list = TaskEngine.getTaskInfos(getApplicationContext());
同样的这个list中存放的应该是TaskInfo,那list还没有,直接拷贝过来,并将AppInfo改为TaskInfo:
private List<TaskInfo> list;
//用户应用程序的集合
private List<TaskInfo> userAppInfos;
//系统应用程序的集合
private List<TaskInfo> systemAppInfos;
将doinBack方法中的:
userAppInfos = new ArrayList<AppInfo>();
systemAppInfos = new ArrayList<AppInfo>();
也改为
userAppInfos = new ArrayList<TaskInfo>();
systemAppInfos = new ArrayList<TaskInfo>();
将for (AppInfo appinfo : list)
改为for (TaskInfo appinfo : list)
现在只剩下myAdapter了,那将myAdapter也拷贝到TaskManagerActivity中来,继续改下错误
首先fillData方法中的postTask中的myAdapter提取到成员变量区域了,所以将SoftManagerActivity中全局变量处声明的MyAdapter也拷贝到TaskManagerActivity中的成员变量处,即: private MyAdapter myAdapter;
然后在myAdapter中的报错的AppInfo处按ctrl+1,按照提示就可以将所有的AppInfo改为TaskInfo
然后TaskInfo中没有isSD这个字段,所以将判断isSD的那段代码去掉
将设置版本号的getVersion()那段代码也删除掉
public class TaskManagerActivity extends Activity {
private ListView lv_taskmanager_process;
private ProgressBar loading;
private List<TaskInfo> list;
//用户应用程序的集合
private List<TaskInfo> userAppInfos;
//系统应用程序的集合
private List<TaskInfo> systemAppInfos;
private MyAdapter myAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_taskmanager);
lv_taskmanager_process = (ListView) findViewById(R.id.lv_taskmanager_process);
loading = (ProgressBar) findViewById(R.id.loading);
tv_taskmanager_process_count = (TextView) findViewById(R.id.tv_taskmanager_process_count);
tv_taskmanager_ramortotal = (TextView) findViewById(R.id.tv_taskmanager_ramortotal);
filldata();
}
private void filldata() {
//获取应用程序信息
new MyAsyncTask() {
@Override
public void preTask() {
loading.setVisibility(View.VISIBLE);
}
@Override
public void postTask() {
if (myAdapter == null) {
myAdapter = new MyAdapter();
lv_taskmanager_process.setAdapter(myAdapter);
}else{
//刷新界面
myAdapter.notifyDataSetChanged();
}
loading.setVisibility(View.INVISIBLE);//数据显示完成,隐藏进度条
}
@Override
public void doinBack() {
//获取应用程序信息
list = TaskEngine.getTaskInfos(getApplicationContext());
//根据应用是否是用户程序将应用程序分别放到用户应用程序的集合和系统应用程序的集合
userAppInfos = new ArrayList<TaskInfo>();
systemAppInfos = new ArrayList<TaskInfo>();
//遍历list集合,分拣用户程序和系统程序
for (TaskInfo appinfo : list) {
if (appinfo.isUser()) {
//用户程序
userAppInfos.add(appinfo);
}else{
//系统程序
systemAppInfos.add(appinfo);
}
}
}
}.execute();
}
private class MyAdapter extends BaseAdapter{
//刷新界面会执行
@Override
public int getCount() {
//方便我们从不同的集合中拿出数据
return isShowSystem == true ? userAppInfos.size()+1+systemAppInfos.size()+1 : userAppInfos.size()+1;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (position == 0) {
//添加用户程序多少个
TextView textView = new TextView(getApplicationContext());
textView.setText("用户程序("+userAppInfos.size()+")");
textView.setTextColor(Color.WHITE);
textView.setBackgroundColor(Color.GRAY);
return textView;
}else if(position == userAppInfos.size()+1){
//添加系统程序多少个
TextView textView = new TextView(getApplicationContext());
textView.setText("系统程序("+systemAppInfos.size()+")");
textView.setTextColor(Color.WHITE);
textView.setBackgroundColor(Color.GRAY);
return textView;
}
View view;
ViewHolder viewHolder;
//判断缓存是否能够复用,convertview不等于null并且convertview和view对象是相同控件,才可以去复用
//instanceof : 属于
if (convertView != null && convertView instanceof RelativeLayout) {
//获取view
view = convertView;
//获取viewholder
viewHolder = (ViewHolder) view.getTag();
}else{
view = View.inflate(getApplicationContext(), R.layout.item_taskmanager, null);
//new ViewHolder
viewHolder = new ViewHolder();
//将初始化的控件保存到viewholder中
viewHolder.iv_itemtaskmanager_icon = (ImageView) view.findViewById(R.id.iv_itemtaskmanager_icon);
viewHolder.tv_itemtaskmanager_name = (TextView) view.findViewById(R.id.tv_itemtaskmanager_name);
viewHolder.tv_itemtaskmanager_ramsize = (TextView) view.findViewById(R.id.tv_itemtaskmanager_ramsize);
viewHolder.cb_itemtaskmanager_ischecked = (CheckBox) view.findViewById(R.id.cb_itemtaskmanager_ischecked);
//将viewholder绑定到view上,复用
view.setTag(viewHolder);
}
//获取条目对应的信息
TaskInfo appInfo;
//判断用户程序是否展示完
if (position <= userAppInfos.size()) {
//获取用户程序
appInfo = userAppInfos.get(position-1);
}else{
//系统程序
appInfo = systemAppInfos.get(position - userAppInfos.size()-2);
}
//数据填充操作
//设置图片,null.方法,参数为null
if (appInfo.getIcon() == null) {
viewHolder.iv_itemtaskmanager_icon.setImageResource(R.drawable.ic_default);
}else{
viewHolder.iv_itemtaskmanager_icon.setImageDrawable(appInfo.getIcon());
}
//设置名称
if (TextUtils.isEmpty(appInfo.getName())) {
viewHolder.tv_itemtaskmanager_name.setText(appInfo.getPackagname());
}else{
viewHolder.tv_itemtaskmanager_name.setText(appInfo.getName());
}
//设置内存信息
//b - > mb
String size = Formatter.formatFileSize(getApplicationContext(), appInfo.getRamsize());
viewHolder.tv_itemtaskmanager_ramsize.setText("内存占用:"+size);
//根据bean类中保存的checkbox状态来设置条目的checkbox的状态
if (appInfo.isChecked()) {
viewHolder.cb_itemtaskmanager_ischecked.setChecked(true);
}else{
viewHolder.cb_itemtaskmanager_ischecked.setChecked(false);
}
//根据包名判断是否是我们应用,是就隐藏checkbox,不是就显示,在getview中,有if就必须有else
if (appInfo.getPackagname().equals(getPackageName())) {
viewHolder.cb_itemtaskmanager_ischecked.setVisibility(View.INVISIBLE);
}else{
viewHolder.cb_itemtaskmanager_ischecked.setVisibility(View.VISIBLE);
}
return view;
}
}
static class ViewHolder{
ImageView iv_itemtaskmanager_icon;
TextView tv_itemtaskmanager_name,tv_itemtaskmanager_ramsize;
CheckBox cb_itemtaskmanager_ischecked;
}
}
运行程序,对比金山卫士发现进程管理页面还应该有checkbox
item的布局文件用的还是item_softmanager.xml,改为item_taskmanager.xml
item_taskmanager.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/iv_itemtaskmanager_icon"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/ic_launcher"
/>
<!-- layout_toRightOf : 在某个控件的右边 -->
<TextView
android:id="@+id/tv_itemtaskmanager_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="手机卫士"
android:textSize="18sp"
android:textColor="#000000"
android:layout_toRightOf="@+id/iv_itemtaskmanager_icon"
/>
<TextView
android:id="@+id/tv_itemtaskmanager_ramsize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="内存占用:2.0MB"
android:textSize="16sp"
android:textColor="#880033"
android:layout_toRightOf="@+id/iv_itemtaskmanager_icon"
android:layout_below="@+id/tv_itemtaskmanager_name"
/>
<CheckBox
android:id="@+id/cb_itemtaskmanager_ischecked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/tv_itemtaskmanager_ramsize"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:clickable="false"
android:focusable="false"
/>
</RelativeLayout>
然后将checkbox控件添加到ViewHolder中:
static class ViewHolder{
ImageView iv_itemtaskmanager_icon;
TextView tv_itemtaskmanager_name,tv_itemtaskmanager_ramsize;
CheckBox cb_itemtaskmanager_ischecked;
}
但是拷贝过来的myAdapter中的findViewById控件都还报错,原因是路径问题,改成TaskManagerActivity中的控件的路径即可
将报错都解决之后,不知不觉感觉已经完成了昨天半天的东西,开发中如果你发现两个界面一样,有的更狠的它直接用一个界面activity,只不过显示不同的数据,当然那个处理起来就有点麻烦了,你会发现一个activity的java文件中写了上千行代码都有可能
运行程序,发现内存占用都是写死的2.0MB,而且有的应用图标还没有
先来处理这个内存占用的问题,首先在getView方法中设置内存信息,通过
viewHolder.tv_itemtaskmanager_ramsize.setText(appInfo.getRamsize());
将appInfo.getRamsize()放到参数处就可以了,但是appInfo.getRamsize()是long类型,long类型都是byte字节
接下来还要转化下,由B转换成MB,这个操作很简单,昨天刚讲了:
Formatter.formatFileSize(getApplicationContext(), appInfo.getRamsize());
它会返回一个String类型的size,这个就是MB的数据,把这个size通过setText设置给控件了
即:
//设置内存信息
//b - > mb
String size = Formatter.formatFileSize(getApplicationContext(), appInfo.getRamsize());
viewHolder.tv_itemtaskmanager_ramsize.setText("内存占用:"+size);
刚才看到有的没有图标和名称,那这么干,没有图标,没有名称的可以判断一下
如果appInfo.getIcon恒等于null,通过setImageResource给相应的控件显示一个默认图标
一般是小机器人,但是这个项目默认图片用的是手机卫士的这种红色的小锁,但是这里不能设置红色小锁,所以从其他项目中拷贝一张ic_default.png的小机器人图片放到项目中的drawable-hdpi文件夹下,这样就不用找红色小锁图标ic_launcher.png了,而是找一下小机器人图片ic_default.png,这样就将默认图片设置成小机器人了
即:
//数据填充操作
//设置图片,null.方法,参数为null
if (appInfo.getIcon() == null) {
viewHolder.iv_itemtaskmanager_icon.setImageResource(R.drawable.ic_default);
}else{
viewHolder.iv_itemtaskmanager_icon.setImageDrawable(appInfo.getIcon());
}
设置名称时,如果appInfo.getName()为空了,那包名不为空吧,包名就是进程名,进程都获取到了,那进程名怎么可能为空
所以如果appInfo.getName()为空,就用你的包名(进程名)来代替应用名称,如果你appInfo.getName()不等于空,直接使用你这个appInfo.getName()就可以了
即:
//设置名称
if (TextUtils.isEmpty(appInfo.getName())) {
viewHolder.tv_itemtaskmanager_name.setText(appInfo.getPackagname());
}else{
viewHolder.tv_itemtaskmanager_name.setText(appInfo.getName());
}
运行程序,点击进程管理,内存占用都显示获取到的它们自己的内存大小,往下滑动,看到一个应用名称为android.process.acore
说明原先这个应用名称为空,所以获取到它的包名展示的,没有应用名称的一般也都没有应用图标,所以看到是用小机器人图标代替的
工作中,系统类的操作,没有图标的全用小机器人代替,这个是android自己默认的
打开几个手机软件,“一键锁屏”,“三击”,“小火箭”,“震动”,然后打开手机卫士西安02,点开进程管理,发现这几个应用也展示出来了
没有什么难度,还是昨天的东西
演示个问题:
打开进程管理条目,点击手机卫士西安2号的复选框选中了它,然后向下滑动,发现下边的android.process.acore程序也选中了,即选中checkbox之后发现复用的条目也选中了
原因:在复用checkbox时也把checkbox状态给复用了,一般在复用时,经常操作的控件的值是不会复用的,一般会将复用的控件的值保存到bean对象中
比如选中手机卫士西安02,代表当前这个view里边,也就是条目里边的checkbox已经选中了
这个checkbox的状态代表选中了,那滑动ListView之后要复用,复用时,复用的checkbox的状态是什么
它在没有复用之前就把checkbox的状态改成选中了,所以在复用时,直接复用你这个checkbox
它不管这个checkbox中的状态是什么,就只是复用你这个checkbox,那如果之前把checkbox改成true,在复用时,也会把这个true的状态给复用过来,所以像这种经常修改性的控件的值,一般这么干了
找到bean类TaskInfo,在它里边添加一个boolean类型的字段isChecked,这个是判断checkbox是否被选中,记得实现它的get,set方法
即:
TaskInfo.java
private boolean isChecked;
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean isChecked) {
this.isChecked = isChecked;
}
set方法是往这个TaskInfo中存数据,所以写成setChecked,get方法是从TaskInfo中取数据去使用,所以写成isChecked比较好
isChecked字段里保存的就是当前条目的checkbox的状态,当前的意思就是说选中了手机卫士西安02,那这时它保存就只是这一个条目的checkbox的状态,其他条目是没有保存的
在TaskInfo中添加完之后,接下来在TaskManagerActivity中的MyAdapter中的getView方法中,根据bean类中保存的checkbox状态来设置条目的checkbox的状态,如果appInfo.isChecked()为true,那给相应的checkbox控件setChecked为true
如果appInfo.isChecked()为false,给相应的checkbox控件setChecked为false
即:
//根据bean类中保存的checkbox状态来设置条目的checkbox的状态
if (appInfo.isChecked()) {
viewHolder.cb_itemtaskmanager_ischecked.setChecked(true);
}else{
viewHolder.cb_itemtaskmanager_ischecked.setChecked(false);
}
比如勾选上手机卫士西安02的checkbox,就相当于把这个手机卫士西安02被选中的状态保存了
接下来就要把你这个状态保存起来,保存完了之后,当你滑动ListView复用时,条目会复用刚才这个checkbox
那它会从这个条目的appInfo中保存信息,跟第一个手机卫士西安02不一样,它会从android.process.acore这个应用程序所在的条目(复用的)中拿出checkbox信息,它保存的checkbox是不是选中,不是就不选中
比如这时再倒着滑动ListView滑动回第一个条目(手机卫士西安02所在的条目),那第一个条目时,它又会判断下第一个条目里边保存的checkbox状态是true还是false,这个时候应该改成true了,这个时候他就会判断是true,然后它就会把checkbox改成选中,是这样一个操作,在开发当中都是这么干的
其实还有一些问题,可以运行看下,进去进程管理界面,选中第一个条目手机卫士西安02,再滑动listview,发现复用第一个条目的android.process.acore这个应用程序的checkbox就不会选中了,我们不选,默认都是false
但是当倒着滑动回第一个条目时,发现手机卫士西安02的checkedbox没有选中了,这是因为还没有保存checkbox的状态
所以说接下来就要保存checkbox的状态,那保存时当点击手机卫士西安02这个条目时应该选中,现在点击下手机卫士西安02条目
发现checkbox没有选中吧,但是点击手机卫士西安02条目的checkbox就可以选中
再看软件管理界面,点击listview条目时,条目是蓝色,那为什么在进程管理里边不是呢
原因是checkbox天生带有点击事件,会把条目的点击事件给拦截掉
接下来到item_taskmanager.xml中的checkbox控件中,添加clickable属性(是否可以点击的属性),设置为false
再添加属性focusable(是否可以获取焦点),也设置为false:
item_taskmanager.xml
<CheckBox
android:id="@+id/cb_itemtaskmanager_ischecked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/tv_itemtaskmanager_ramsize"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:clickable="false"
android:focusable="false"
/>
这里设置完之后,再运行程序,进去进程管理界面,再点击checkbox就没有效果了
所以接下来就要给ListView增加条目点击事件
这时又可以复制了,SoftManagerActivity中的onCreate方法调用了ListviewitemClick()方法
那我们TaskManagerActivity的onCreate方法中也调用下这个listviewitemClick()方法,然后将SoftManagerActivity中的listviewitemClick()方法名称复制到TaskManagerActivity中:
TaskManagerActivity.java
/**
-
listview条目点击事件
*/
private void listviewitemClick() {}
其实listviewitemClick()方法中的代码也可以全部复制过来,当然有些代码是不需要的,将“第三步弹出气泡,第四步判断有没有显示气泡有的话关闭气泡,第五步添加动画效果”全部删掉
然后将第一步”屏蔽用户程序多少个和系统程序多少个弹出气泡“改为“屏蔽用户程序多少个和系统程序多少个点击事件”
将第二步获取软件信息改为获取进程信息,然后appInfo是没有的,所以在TaskManagerActivity的成员变量处来个TaskInfo
接着将lv_softmanager_applications.setOnItemClickListener的控件改为lv_taskmanager_process
到这点击事件的前半部分就做完了
即: private TaskInfo appInfo; /**
-
listview条目点击事件
*/
private void listviewitemClick() {lv_taskmanager_process.setOnItemClickListener(new OnItemClickListener() { //view :条目的view对象 @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //1.屏蔽用户程序多少个和系统程序多少个点击事件 if (position == 0 || position == userAppInfos.size()+1) { return; } //2.获取进程信息 //获取条目对应的信息 //判断用户程序是否展示完 if (position <= userAppInfos.size()) { //获取用户程序 appInfo = userAppInfos.get(position-1); }else{ //系统程序 appInfo = systemAppInfos.get(position - userAppInfos.size()-2); } } }); }
紧接着第3步设置checkbox的状态,简单来说就是保存一下checkbox的状态
点击时,如果点击之前是没有选中,点击之后就改为选中,点击之前是选中,点击之后要变成不选中,所以这里是要根据保存的状态去判断,如果appInfo.isChecked()是true,设置appInfo.setChecked()为false,else为false的话,就改为true
即: private TaskInfo appInfo; /**
-
listview条目点击事件
*/
private void listviewitemClick() {lv_taskmanager_process.setOnItemClickListener(new OnItemClickListener() { //view :条目的view对象 @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //1.屏蔽用户程序多少个和系统程序多少个点击事件 if (position == 0 || position == userAppInfos.size()+1) { return; } //2.获取进程信息 //获取条目对应的信息 //判断用户程序是否展示完 if (position <= userAppInfos.size()) { //获取用户程序 appInfo = userAppInfos.get(position-1); }else{ //系统程序 appInfo = systemAppInfos.get(position - userAppInfos.size()-2); } //3.设置checkbox的状态,保存checkbox的状态 //根据保存的状态来判断,选中就改为不选中,不选中就改为选中 if (appInfo.isChecked()) { appInfo.setChecked(false); }else{ if (!appInfo.getPackagname().equals(getPackageName())) { appInfo.setChecked(true); } } }); }
设置完checkbox的状态之后,还要有第四步更新界面,让界面及时更新显示, 这个更新界面可以这么干
即: myAdapter.notifyDataSetChanged();
在点击条目时,改变checkbox的状态是点击手机卫士西安02这一个条目就改变这一个条目的checkbox的值
更新界面及显示数据的第2种方式:
第1种方式:myAdapter.notifyDataSetChanged()是刷新整个界面 第2种方式:“刷新单个条目”
看myAdapter中的ViewHolder,绑定到了view中了,那绑定到view里边,在条目点击事件onItemClick方法的参数处有一个view
那这个view前边一直就在强调,它就是条目的View,条目的View对象就是View.inflate得到的View对象
既然ViewHolder绑定到了这个view里边,那view.getTag()得到一个ViewHolder,即:
ViewHolder viewHolder = (ViewHolder) view.getTag();
既然要更改的是这个checkbox的值,viewHolder里边就有一个checkbox的控件cb_itemtaskmanager_ischecked
给它setChecked()设置为保存的这个appInfo.isChecked()值
这个是第二种刷新方式,在开发中也是比较常用的:
TaskManagerActivity.java private TaskInfo appInfo; /**
-
listview条目点击事件
*/
private void listviewitemClick() {lv_taskmanager_process.setOnItemClickListener(new OnItemClickListener() { //view :条目的view对象 @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //1.屏蔽用户程序多少个和系统程序多少个点击事件 if (position == 0 || position == userAppInfos.size()+1) { return; } //2.获取进程信息 //获取条目对应的信息 //判断用户程序是否展示完 if (position <= userAppInfos.size()) { //获取用户程序 appInfo = userAppInfos.get(position-1); }else{ //系统程序 appInfo = systemAppInfos.get(position - userAppInfos.size()-2); } //3.设置checkbox的状态,保存checkbox的状态 //根据保存的状态来判断,选中就改为不选中,不选中就改为选中 if (appInfo.isChecked()) { appInfo.setChecked(false); }else{ if (!appInfo.getPackagname().equals(getPackageName())) { appInfo.setChecked(true); } //4.更新界面 //第一种方式
// myAdapter.notifyDataSetChanged();
//第二种方式,刷新单个条目 ViewHolder viewHolder = (ViewHolder) view.getTag(); viewHolder.cb_itemtaskmanager_ischecked.setChecked(appInfo.isChecked()); } }); }
既然你在item点击事件中有一个view对象是可以表示我们条目的view对象,那就说明我可以从条目的view对象里边拿出我绑定的viewHolder,那最终注意看,在我们myAdapter中根据bean类中保存的checkbox状态来设置条目的checkbox的状态的时候,是不是从ViewHolder中取出checkbox的控件cb_itemtaskmanager_ischecked,然后给它setChecked()为true或false是不是就可以了啊,那这个操作就完美的用到了我们上边刷新界面的第二种方式处,先获取一个ViewHolder,获取完了之后,既然你最终刷新界面的时候,在getView中是通过viewHolder.cb_itemtaskmanager_ischecked.setChecked()去获取的,那我在这里给你提前执行了,这个时候注意,就可以实现刷新单个条目,这个在开发的时候用的也是比较常见的
运行程序,进入进程管理界面,点击第一个条目手机卫士西安02,第一个条目的checkbox是不是就是选中状态了,滑动listView,下边复用第一个条目的android.process.acore这个应用程序是不是就没有选中了,那再倒着滑动回第一个条目的位置,第一个条目手机卫士西安02的checkbox是不是还是选中状态啊
checkbox的bug处理总结如下:
条目点击事件中进行:
1.在bean类AppInfo中添加保存checkbox状态的变量(也就是添加字段isChecked)
//判断checkbox是否被选中,保存当前条目的checkbox的状态,默认就是false private boolean isChecked; public boolean isChecked() { return isChecked; } public void setChecked(boolean isChecked) { this.isChecked = isChecked; }
2.在条目点击事件中给AppInfo保存checkbox的状态,并更新界面
lv_taskmanager_process.setOnItemClickListener(new OnItemClickListener() {
//view :条目的view对象 @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //3.设置checkbox的状态,保存checkbox的状态 //根据保存的状态来判断,选中就改为不选中,不选中就改为选中 if (appInfo.isChecked()) { appInfo.setChecked(false); }else{ appInfo.setChecked(true); } //4.更新界面 //第一种方式 //myAdapter.notifyDataSetChanged(); //第二种方式,刷新单个条目 ViewHolder viewHolder = (ViewHolder) view.getTag(); viewHolder.cb_itemtaskmanager_ischecked.setChecked(appInfo.isChecked()); }
});
3.在myAdapter中的getview方法中根据AppInfo中保存的checkbox状态去设置条目的checkbox状态
//根据bean类中保存的checkbox状态来设置条目的checkbox的状态 if (appInfo.isChecked()) { viewHolder.cb_itemtaskmanager_ischecked.setChecked(true); }else{ viewHolder.cb_itemtaskmanager_ischecked.setChecked(false); }
要学会刷新界面的第二种方式,刷新单个条目,即通过setTag去给条目增加控件,然后通过getTag的方式获取到控件,然后去刷新这个控件,这里刷新这个控件的方式是setChecked(appInfo.isChecked())
9.3全选&反选 #
进程管理界面下边还有个“一键清理”,“设置”按钮
它还缺两个重要的按钮,比如说每个条目的checkbox,用户只能单个选择,所以还会增加两个按钮,一个是全选,一个是反选,再加上清理,设置,总共是4个按钮
这样用户体验会好一些,会让用户节省一些时间
接下来就给进程管理界面下方添加4个按钮:
activity_taskmanager.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="进程管理"
android:textSize="25sp"
android:gravity="center_horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:background="#8866ff00"
/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
<TextView
android:id="@+id/tv_taskmanager_process_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运行中进程:5个"
android:gravity="center_horizontal"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:layout_marginLeft="20dp"
/>
<TextView
android:id="@+id/tv_taskmanager_ramortotal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="剩余/总内存:120MB/502M"
android:gravity="center_horizontal"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:layout_toRightOf="@+id/tv_taskmanager_process_count"
android:layout_marginLeft="20dp"
/>
</RelativeLayout>
<!-- FrameLayout : 帧布局,会用在视频播放器
在布局文件中最下面的控件,在显示的时候是在最上边
layout_weight : 设置渲染优先级的作用,值约到优先级越低
-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
<ListView
android:id="@+id/lv_taskmanager_process"
android:layout_width="match_parent"
android:layout_height="match_parent"
底部的四个按钮使用LinearLayout来包裹,既然这个LinearLayout是水平排列的,为了让它们平均分配,应该给每个按钮设置权重为1
然后将每个按钮的width改为0dp,它们四个按钮就会平均分配,这个是常见的weight权重使用
预览时发现没有按钮,这个问题前边讲过,因为FrameLayout高度是match_perent,它把底部的按钮挤出屏幕了
给FrameLayout设置weight权重为1,就可以让底部的按钮显示出来了
原因: layout_weight:设置渲染优先级的作用,值越大优先级越低
到TaskManagerActivity中创建全选的点击事件方法all,记得参数处给它传个View
全选的操作是让checkbox的条目都变成true,在这个方法里边来个增强for(iterable_type,iterable_element:iterable)遍历
把集合分成了两个,先设置用户程序的checkbox状态为true,这个简单了,不管是不是true,直接setChecked为true
接着是不是再来设置系统程序的checkbox状态为true
最后记得要更新下界面,让用户看到,这里更新的是所有的条目,所以必须得用myAdapter.notifyDataSetChanged()来更新所有的界面
即:
/**
-
全选
*/
public void all(View v){
//设置用户程序的checkbox状态为true
for(TaskInfo taskinfo : userAppInfos){
taskinfo.setChecked(true);
}
//设置系统程序的checkbox状态为true
for(TaskInfo taskinfo : systemAppInfos){
taskinfo.setChecked(true);
}
//更新界面
myAdapter.notifyDataSetChanged();
}运行程序,来到进程管理条目的界面中,点击全选就全部选中了
接下来就是反选
同理,在TaskManagerActivity中创建反选的点击事件方法cancel,记得参数处给它传个View
因为刷新界面时会执行到MyAdapter中的方法getCount的,所以在全选的all方法中才用增强for去分别处理的
反选的操作当你checkbox状态是true时反选是fasle,当你状态是false时,反选是true
所以接下来还是用增强for去循环了,但是在taskInfo.setChecked(taskInfo.isChecked())
参数taskInfo.isChecked()前边加上!,意思就是如果是true改为false,false改为true
这个就是对用户程序做的反选操作,接下来同理对系统程序进行反选操作,最后同样要记得更新界面,即:
/**
-
反选
-
@param v
*/
public void cancel(View v){
//用户程序
for (TaskInfo taskInfo : userAppInfos) {
//true改为false,false改为true
taskInfo.setChecked(!taskInfo.isChecked());
}
//系统程序
for (TaskInfo taskInfo : systemAppInfos) {
//true改为false,false改为true
taskInfo.setChecked(!taskInfo.isChecked());
}
//更新界面
myAdapter.notifyDataSetChanged();
}运行程序,进入进行管理界面,点击全选,所有当前正在运行的进程item都被选中了,再点击反选又都没有选中了
那我只选中小火箭这一个应用,那我点击反选时,小火箭变成不被选中,其他条目应该全部变成选中
9.4清理 #
全选反选已经处理完了,接下来还有清理的操作
在清理时就要杀死进程管理中的所有进程
看下金山卫士的清理方式,点击“一键清理”,进程管理条目中正在运行的所有进程就都被清理了
只留下金山卫士它自己,这个只留下它自己的操作一会儿再处理,那这个清理其实就是要杀死进程
进程的种类:
前台进程 : 在通知栏上的都是前台进程,杀不死
可见进程 : 现在用的进程,杀不死
服务进程 : 运行服务的进程,当内存不足时杀死服务进程,当内存充足时不杀死,当内存不足杀死后在内存充足时系统会自动重启服务
后台进程 : 运行在后台进程,可以被杀死
空进程 : 程序退出之后,它的进程会变成空进程,为了方便下次启动时能快速启动,可以被杀死的
除非是安全类或者墨迹天气可以放在通知栏中显示,如果新闻类的把它放在通知栏中变成前台进程,你会被用户认为是流氓程序
那行者app变成前台进程之后,只要你用,它会一直定位你的路径,然后不用时,它会提醒你说是否在后台进行运行,你点击确定,那它这个通知栏中的这个通知是不会消失的,这就说明你不管用360还是其他的,全部杀不死行者app,如果你点取消,行者app它自己就会把自己在通知栏的这个通知给干掉
进程优先级如下:
前台进程 -> 可见进程 -> 服务进程 -> 后台进程 -> 空进程
前台进程和可见进程的区别:
前台进程即使退出,它也是前台进程,可见进程退出就变成后台进程或空进程了
在安卓中能杀死的进程只是空进程和后台进程,而前台进程,可见进程,以及服务进程,是干不掉的
在TaskManagerActivity中创建清理的点击事件方法clear,记得参数处给它传个View
与进程相关的操作都是用ActivityManager来操作的
sm它有一个方法killBackgroundProcesses(packageName),参数packageName是包名
既然有杀死后台进程的操作,在进程管理界面中,选中谁杀死谁,所以接下来又是用增强for循环
在for循环中用if判断进程是否选中,选中杀死,没选中不杀死
如果taskInfo.isChecked()为true表示选中了,那就杀死后台进程
这个是杀死用户进程的操作,同理杀死系统进程的操作也一样,接着就要更新界面了
需要权限:
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
运行程序,进入进程管理,点击全选然后点击清理,发现DDMS界面的左边模拟器上少了好多个应用的包名,但是进城管理界面没有显示少掉
当系统把进程杀掉之后,还要在集合里边把相应的数据给删掉,能在增强for一遍循环时,一边把taskInfo集合中的数据给删掉吗?
这个是不行的,不能在taskInfo集合循环时从taskInfo集合中移除数据
new一个ArrayList数组,里边泛型写成TaskInfo,这个集合用来保存被杀死的进程的信息
系统每杀死一个用户进程,就将这个杀死进程的信息保存到这个deletinfos集合中,系统进程也是一样的操作
根据杀死进程集合deletinfos中的信息,去删除用户进程userAppInfos和系统进程systemAppInfos集合中的数据
再来一个增强for循环,在它里边if判断,taskInfo里边有个isUser(),如果是用户程序,userAppInfos有个remove(object)方法
将taskInfo放在参数object处,else用systemAppInfos去remove(object),同样将taskInfo放在参数处
即:
/**
-
清理
-
@param v
*/
public void clear(View v){
//杀死进程
//1.获取进程的管理者
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
//保存被杀死的进程的信息
List deletinfos = new ArrayList();
//2.杀死用户进程,不能再集合循环的时候,从集合中移出数据
for (TaskInfo taskInfo : userAppInfos) {
//判断进程是否选中,选中杀死,没选中不杀死
if (taskInfo.isChecked()) {
//杀死后台进程
//packageName : 包名
am.killBackgroundProcesses(taskInfo.getPackagname());
deletinfos.add(taskInfo);
}
}
//3.杀死系统进程
for (TaskInfo taskInfo : systemAppInfos) {
//判断进程是否选中,选中杀死,没选中不杀死
if (taskInfo.isChecked()) {
//杀死后台进程
//packageName : 包名
am.killBackgroundProcesses(taskInfo.getPackagname());
deletinfos.add(taskInfo);
}
}
//根据杀死进程集合中的信息,去删除用户进程和系统进程集合中的数据
for (TaskInfo taskInfo : deletinfos) {
if (taskInfo.isUser()) {
userAppInfos.remove(taskInfo);
}else{
systemAppInfos.remove(taskInfo);
}
}
//更新界面
myAdapter.notifyDataSetChanged();
}这个操作就是只要你系统杀死一个进程,就将被杀死的进程的信息保存到被杀死的这个进程的集合deleinfos中
然后再去遍历deleinfos这个集合,根据这个集合里边存储的信息,如果是用户程序,就从userAppInfos中删除
如果是系统程序,就从systemAppInfos中删除,最后才去更新一下界面
上边没有添加deleinfos时,运行程序点击删除,虽然也有刷新界面,但是界面中还是有进程显示的原因就是因为TaskInfo中的应用进程和系统进程信息没有被移除掉
运行程序,点击全选后,再点击清理,进程管理界面中的进程信息数据就都清理掉了,但是发现它把自己(手机卫士西安02)也清理掉了,这个问题稍后再处理
到这杀死进程的操作就处理完了
现在不像以前那样一个功能分几步去写了,而是一个功能一个功能的去写,最后一个功能一个功能的去组成整个模块的大功能
这就是我为什么说后边会越来越难,从现在开始去写代码,你们都是这样去写的了,只分功能,不分一些模块
这里主要是killBackgroundProcesses(packageName)这个方法,还有记得权限,有些同学有时候会忘了加权限,没关系哈,直接去运行,当运行报权限错误的时候,你再去加权限也行,所以不要在权限这里去苦恼,权限有好多,记不住没关系
9.5进程的细节处理#
当你杀死进程之后,用户再次进入进程管理界面,又会展示出被杀死的进程,有些进程杀不掉,但是在进程管理界面点全选再点击清理时可以把进程管理界面上的所有进程都杀掉,其实你在杀时,系统会判定有些是杀不掉的,这个是系统原因
360卫士做的比较好,点击全选然后再点击清理清理掉了,用户会点击返回,用户在30分钟或10几分钟之内又点这个进程管理,360会提醒用户“您的手机已经非常干净了,不需要清理了”,就是直接让用户点不进去,注意这个就是我要给大家说的一个小技巧了
所以一般当用户清理过一次进程之后,再点击进程管理条目时,就不让她进去
这方面比较简单,我就不做了,还有一方面比较能满足用户的操作是当用户点击清理按钮时,我们弹出toast框,给用户提示“共清理xx进程,释放了xx内存”
这个也是让用户有一种很强的满足感,其实它有些进程是根本就没有清理掉,只不过它是把所有进程占用的内存加起来,告诉用户说已经清理掉了xx进程,释放xx内存
这个就是给大家介绍的一些小技巧了,也是现在app中常见的一些手段
点击清理就弹出toast,提醒用户共清理多少个进程,释放多少内存
根据杀死进程集合中的信息,去删除用户进程和系统进程集合中的数据之后,来个toast提醒下用户就可以了
共杀死多少个进程就是deletinfos.size()这个长度,释放多少内存就是taskInfo.getRamsize()来获取系统所占用的空间
可以在增强for外边来个long memory=0,然后累加操作,即:
memory+=taskInfo.getRamsize()
注意这个是long类型,还要将这个B转化成MB,那这个操作很简单,即:
String size = Formatter.formatFileSize(getApplicationContext(), memory);
最后将这个size放到taost内存处
运行程序,点击清理,就会提醒共杀死14个进程,释放110MB内存,这样用户看起来就很爽了
那接下来还有一个问题我们要处理
比如说在deletinfos中保存了所有杀死的进程信息,所以接下来注意,显示完了之后,我们还要做这么一步操作,把这个deleinfos去clear()清空掉,同时把deleinfos置为null
即:
/**
-
清理
-
@param v
*/
public void clear(View v){
//杀死进程
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
//保存被杀死的进程的信息
List deletinfos = new ArrayList();
//杀死用户进程,不能再集合循环的时候,从集合中移出数据
for (TaskInfo taskInfo : userAppInfos) {
//判断进程是否选中,选中杀死,没选中不杀死
if (taskInfo.isChecked()) {
//杀死后台进程和空进程
//packageName : 包名
am.killBackgroundProcesses(taskInfo.getPackagname());
deletinfos.add(taskInfo);
}
}
//杀死系统进程
for (TaskInfo taskInfo : systemAppInfos) {
//判断进程是否选中,选中杀死,没选中不杀死
if (taskInfo.isChecked()) {
//杀死后台进程
//packageName : 包名
am.killBackgroundProcesses(taskInfo.getPackagname());
deletinfos.add(taskInfo);
}
}
long memory=0;
//根据杀死进程集合中的信息,去删除用户进程和系统进程集合中的数据
for (TaskInfo taskInfo : deletinfos) {
if (taskInfo.isUser()) {
userAppInfos.remove(taskInfo);
}else{
systemAppInfos.remove(taskInfo);
}
memory+=taskInfo.getRamsize();
}
//b -> mb
String size = Formatter.formatFileSize(getApplicationContext(), memory);
Toast.makeText(getApplicationContext(), “共杀死”+deletinfos.size()+“个进程,释放”+size+“内存”, 0).show();
//修改显示的进程数
availableProcess = availableProcess - deletinfos.size();
tv_taskmanager_process_count.setText(“运行中进程:”+availableProcess);
memory = avaliableRam+memory;
//转化成字符串
String formatFileSize = Formatter.formatFileSize(getApplicationContext(), memory);
//空闲空间处理
tv_taskmanager_ramortotal.setText(“剩余/总内存:”+formatFileSize+"/"+totalsize);//杀死进程做准备 deletinfos.clear(); deletinfos = null; //更新界面 myAdapter.notifyDataSetChanged(); }
这个是为下一次杀死进程做准备了,因为在clear方法中是new出来一个deletinfos
那有可能会出现这么一种情况,比如deletinfos保存了以前杀死进程的信息,那紧接着没有在这里清空deletinfos时
你再进行这种杀死进程的操作,即:
am.killBackgroundProcesses(taskInfo.getPackagname()); deletinfos.add(taskInfo);
有可能会再在deleinfos里边存放一些信息,那以前的信息加上现在的信息,会造成你在taost提示共杀死xx进程的时候,杀死进程数不正确,所以说才要在后边将deletinfos去clear(),并且置为null
运行程序,点击进程管理,全选,再点击清理,提示杀死14个进程,释放111MB内存
再来解决第3个问题,就是将自己都杀掉了的问题,用户不是傻子,用户会想你把所有的进程全都清理掉了,那反过来一想,我现在用的这个是谁啊,用户一下子就会发现我们是在欺骗她,所以说为了做的比较逼真一点,一般我们的进程是不会被杀死的
那来看怎么对我们的进程做处理,在判断进程能否被杀死的时候,直接判断了如果你是选中状态的话,就杀死
现在这么干,在选中条目时,就让它不能去选中手机卫士西安02,不能选中永远是false,false的话是永远不会被杀掉的
所以找到条目点击事件listviewitemClick中的onItemClick方法的3.设置checkbox的状态,保存checkbox的状态
即: //3.设置checkbox的状态,保存checkbox的状态 //根据保存的状态来判断,选中就改为不选中,不选中就改为选中 if (appInfo.isChecked()) { appInfo.setChecked(false); }else{ appInfo.setChecked(true); }
在setChecked(true)时,添加判断如果appInfo里边有一个getPackagname().equals(getPackageName())
如果你的包名和我的包名不相同,再设置为true,即:
//3.设置checkbox的状态,保存checkbox的状态 //根据保存的状态来判断,选中就改为不选中,不选中就改为选中 if (appInfo.isChecked()) { appInfo.setChecked(false); }else{ if (!appInfo.getPackagname().equals(getPackageName())) { appInfo.setChecked(true); } }
全选也可以选中手机卫士,所以在全选的方法all中也要做这样一个判断
即:
/**
-
全选
*/
public void all(View v){
//设置用户程序的checkbox为true
TaskInfo taskInfo2 = userAppInfos.get(0);
System.out.println(taskInfo2);
for (TaskInfo taskinfo : userAppInfos) {
if (!taskinfo.getPackagname().equals(getPackageName())) {
taskinfo.setChecked(true);
}
}
//系统程序的checkbox为true
for (TaskInfo taskinfo : systemAppInfos) {
taskinfo.setChecked(true);
}
//更新界面
myAdapter.notifyDataSetChanged();
}反选cancel方法中也需要去作做这个判断
即: /**
-
反选
-
@param v
*/
public void cancel(View v){
//用户程序
for (TaskInfo taskInfo : userAppInfos) {if (!taskInfo.getPackagname().equals(getPackageName())) { //true改为false,false改为true taskInfo.setChecked(!taskInfo.isChecked()); } } //系统程序 for (TaskInfo taskInfo : systemAppInfos) { //true改为false,false改为true taskInfo.setChecked(!taskInfo.isChecked()); } //更新界面 myAdapter.notifyDataSetChanged(); }
运行程序, 点击手机卫士选择不上,全选,反选也选择不上手机卫士,现在就可以把我们的进程让它不处于选中状态
不属于选中状态在clear方法中清理时注意,在判断进程是否选中,选中杀死,没选中不杀死的判断处
当taskInfo.isChecked()为false时就不会去清理
即: clear方法中: //杀死用户进程,不能再集合循环的时候,从集合中移出数据 for (TaskInfo taskInfo : userAppInfos) { //判断进程是否选中,选中杀死,没选中不杀死 if (taskInfo.isChecked()) { //杀死后台进程和空进程 //packageName : 包名 am.killBackgroundProcesses(taskInfo.getPackagname()); deletinfos.add(taskInfo); } } //杀死系统进程 for (TaskInfo taskInfo : systemAppInfos) { //判断进程是否选中,选中杀死,没选中不杀死 if (taskInfo.isChecked()) { //杀死后台进程 //packageName : 包名 am.killBackgroundProcesses(taskInfo.getPackagname()); deletinfos.add(taskInfo); } }
再来看第4个问题:
既然你这个checkbox永远都是false,那要不要都无所谓了 找到getView方法,在getView方法的最后边做判断,即: //根据包名判断是否是我们的手机卫士西安02应用,是就隐藏checkbox,不是就显示,在getview中,有if就必须有else if (appInfo.getPackagname().equals(getPackageName())) { viewHolder.cb_itemtaskmanager_ischecked.setVisibility(View.INVISIBLE); }else{ viewHolder.cb_itemtaskmanager_ischecked.setVisibility(View.VISIBLE); }
注意:有些同学会在getView方法中只写个if就完事,else他觉得会默认
但是注意在getView中,有if就必须有else,这个是不能忘的,是规定,必须得这么去写了
运行程序,看下手机卫士进程所在的条目没有checkbox显示了
再点击全选,然后清理,就只剩下手机卫士西安02这一个进程了
这个关于进程的一些细节的处理,我们就处理完了
进程处理的细节问题2个总结如下,其他2个自己看上边:
1.在清理操作中,添加提醒用户"杀死xx个进程和释放xxMB内存"的操作
long memory=0; //根据杀死进程集合中的信息,去删除用户进程和系统进程集合中的数据 for (TaskInfo taskInfo : deletinfos) { if (taskInfo.isUser()) { userAppInfos.remove(taskInfo); }else{ systemAppInfos.remove(taskInfo); } memory+=taskInfo.getRamsize(); } //b -> mb String size = Formatter.formatFileSize(getApplicationContext(), memory); Toast.makeText(getApplicationContext(), "共杀死"+deletinfos.size()+"个进程,释放"+size+"内存", 0).show(); //杀死进程做准备 deletinfos.clear(); deletinfos = null;
2.处理不应该选中手机卫士自己的效果:
a.在条目点击事件中的处理 if (appInfo.isChecked()) { appInfo.setChecked(false); }else{ if (!appInfo.getPackagname().equals(getPackageName())) { appInfo.setChecked(true); } } b.在全选中进行的处理 //设置用户程序的checkbox为true for (TaskInfo taskinfo : userAppInfos) { if (!appInfo.getPackagname().equals(getPackageName())) { taskinfo.setChecked(true); } } c.在反选中进行的处理 //用户程序 for (TaskInfo taskInfo : userAppInfos) { if (!appInfo.getPackagname().equals(getPackageName())) { //true改为false,false改为true taskInfo.setChecked(!taskInfo.isChecked()); } }
9.6获取进程个数和内存 #
关于进程管理界面的操作就都做完了,底部的全选,反选,清理,设置,前三个做完了,设置比较麻烦一些先不做
先把进程管理界面完成一下
看下金山卫士中的进程管理界面,最上边还有一个“运行中进程:5个 剩余/总内存:433/504MB"
来到activity_taskmanager.xml中,添加相应布局
即:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="进程管理"
android:textSize="25sp"
android:gravity="center_horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:background="#8866ff00"
/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
<TextView
android:id="@+id/tv_taskmanager_process_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运行中进程:5个"
android:gravity="center_horizontal"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:layout_marginLeft="20dp"
/>
<TextView
android:id="@+id/tv_taskmanager_ramortotal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="剩余/总内存:120MB/502M"
android:gravity="center_horizontal"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:layout_toRightOf="@+id/tv_taskmanager_process_count"
android:layout_marginLeft="20dp"
/>
</RelativeLayout>
<!-- FrameLayout : 帧布局,会用在视频播放器
在布局文件中最下面的控件,在显示的时候是在最上边
layout_weight : 设置渲染优先级的作用,值约到优先级越低
-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
<ListView
android:id="@+id/lv_taskmanager_process"
android:layout_width="match_parent"
android:layout_height="match_parent"
回到TaskManagerActivity中的onCreate方法中来初始化需要的控件
即:
private TextView tv_taskmanager_process_count;
private TextView tv_taskmanager_ramortotal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_taskmanager);
tv_taskmanager_process_count = (TextView) findViewById(R.id.tv_taskmanager_process_count);
tv_taskmanager_ramortotal = (TextView) findViewById(R.id.tv_taskmanager_ramortotal);
}
要在这两个控件上显示出进程个数和剩余内存/总内存
1.写一个工具类ProcessUtils
在它里边写一个方法叫做getAvailableProcess,用来获取正在运行的进程的个数
这个前边刚刚写过怎么获取正在运行的进程,getAvailableProcess方法的参数处需要传入一个Context
getSystemService()返回一个ActivityManager,把它叫做am
通过am可以去获取一个getRunningAppProcesses(),它会返回一个List<RunningAppProcessInfo>集合,把它叫做processes
最后return一个processes.size()
即:
public class ProcessUtils {
/**
-
获取正在运行的进程的个数
-
@return
*/
public static int getAvailableProcess(Context context){
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List processes = am.getRunningAppProcesses();
return processes.size();
}
}进程的个数就可以通过这个getAvailableProcess方法去获取到了
再来获取剩余的内存,写个方法名称叫getAvaliableRam,在这个方法中来获取剩余的内存
剩余的内存是long类型的,那返回值类型就写成long,然后getAvaliableRam方法的参数处也需要传入一个context
用ActivityManager调用getMemoryInfo(outInfo)
它的参数处需要一个名称为outInfo的MemoryInfo,那把这个MemoryInfo给new出来,这个相当于要创建一张白纸
am.getMemoryInfo(outInfo)相当于将内存的信息写到白纸上
写到白纸outInfo上之后,outInfo里边有个availMem,这个就是空闲的内存,直接将它返回去
outInfo里边还有个totalMem,它是总共的内存
通过这一种方式就可以获取出两个内存,一个是空闲的内存,一个是总共的内存,我们要获取的是空闲的内存
即: /**
-
获取空闲的内存信息
-
@param context
-
@return
*/
public static long getAvaliableRam(Context context){
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
MemoryInfo outInfo = new MemoryInfo();//创建一个白纸
am.getMemoryInfo(outInfo);//将内存的信息写到白纸上
return outInfo.availMem;//空闲内存
// outInfo.totalMem;//总共内存
// return 0;
}那接下来还要获取总的内存信息,那将上边的空闲内存注释掉,将获取到的总共内存放开
即: /**
-
获取总的内存信息
-
@param context
-
@return
-
在高版本中使用的
*/
public static long getTotalRam(Context context){
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
MemoryInfo outInfo = new MemoryInfo();//创建一个白纸
am.getMemoryInfo(outInfo);//将内存的信息写到白纸上
// return outInfo.availMem;//空闲内存
return outInfo.totalMem;//总共内存,高版本中有的字段,在低版本中不能使用
// return 0;
}但是发现outInfo.totalMem报错了,原因是:field requires API level 16 (current min is 8)
意思totalMem是api16里边才有的版本,高版本中有的字段,在低版本中不能使用,那来到清单文件中看下android:targetSdkVersion=“17” 有一个高版本17
打开project-properties文件,看下当前的版本target=android-18,当前版本是18说明是可以运行这个totalMem方法的
这个模拟器是4.1.2,是16,在这个里边是可以使用这个字段totalMem
在另外一个2.3.3的模拟器里边就不能去用了,这个应用是要去兼容低版本到8的(清单文件中minSdkVersion=“8”)
那这个getTotalRam方法是在高版本中使用的,那再写一个在低版本中使用的方法,名字还叫getTotalRam
这种方式叫重载
在低版本中不能通过getMemoryInfo()去获取内存信息
在DDMS下边的模拟器里边有个proc目录,它里边存储的都是应用或者设备的一些信息
打开cpuinfo,还有一个meminfo,打开cmd.exe,输入"adb -s emulator-5554 shell"
按enter,再输入 “cd proc”,再按enter,cd它里边有个meminfo,再输入“cat meminfo”,再按enter
下边会出来一大串信息,第一个是:MemTotal:513000kB,这个就是总的内存,第二个是MemFree 305064kB,它是空闲内存
都是在这里边写的,所以在这里写下:
proc -> meminfo(存储的就是内存的信息)
@deprecated : 可以将方法设置成过时的方法
那可以直接解析这个文件得到内存信息,先new一个File,参数写成“/proc/meminfo”
有了这个之后还要去读取一行(第一行为:MemTotal 513000kB),读取一行用BufferReader
new一个BufferReader(in),紧接着可以用br调用readLine()来读取出一行信息,这里边也有一个异常,把两个异常合并下
读取出一行之后,返回一个String类型的readLine,最后要的内存信息是数字,所以接下来解析出数字
readLine有个toCharArray()方法,返回一个字符数组charArray,再增强for遍历一下这个charArray
在增强for中判断一下如果c大于等于0,或者c小于等于9时,就把所有的数字给判断出来了,如果c满足" 0<= c <=9 "
就添加组合
添加组合一般会用StringBuilder,在getTotalRam这个方法中再new一个StringBuilder,返回一个StringBuilder
把它叫做sb,那添加组合就可以通过sb去append©,循环完了之后,用sb去调用toString()方法
这时候拿到的就只是需要的数字了
这个增强for的作用就是拿出string类型中的所有数字,最后sb.toString()返回的String类型的string就只是数字
接下来最终要的是long类型,那其实转化成int类型就可以了,用Integer.parseInt(string);
将这个String类型的string转化成int类型,但是转化完之后这个int类型有时候会超出范围
所以可以将返回的Int类型改写成long类型,最后return时得到的是kB
最终要的是B,所以返回时应该返回parseInt*1024,这也是为什么要用long类型的原因
因为乘以1024之后,可能会超出int类型的范围,所以才用long类型
这时可以在这个方法的注释中加个注解@deprecated,此时这个在低版本中使用的getTotalRam方法上是不是就会显示一个横线,表示过时的方法,那这个写法就是在模仿系统中的一些低版本过时方法的写法
即: /**
-
获取总的内存信息
-
@param context
-
@return
-
在低版本中使用的
-
@deprecated
/
public static long getTotalRam(){
StringBuilder sb = new StringBuilder();
File file = new File("/proc/meminfo");
try {
BufferedReader br = new BufferedReader(new FileReader(file));
//读取出一行信息
String readLine = br.readLine();
//解析出数字
//转化成字符数组
char[] charArray = readLine.toCharArray();
//拿出string类型中的所有数字
for (char c : charArray) {
//判断字符数组中的元素是否数字
if (c >=‘0’ && c<=‘9’) {
//添加组合
sb.append©;
}
}
String string = sb.toString();//数字
//转化成int
long parseInt = Integer.parseInt(string);
return parseInt1024;
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}写完在低版本中使用的getTotalRam方法之后,在高版本中使用的getTotalRam方法中调用的totalMem方法还报红报错
这个totalMem方法虽然在低版本中没法使用,但是在高版本中可以用
你在低版本中报错,是因为在清单文件中的minSdkVersion=“8”,将清单文件中的这个8改成16,这样totalMem方法就不会报错了
再回到清单文件中,将16改回8,再返回来看下这个totalMem方法还不报红
刚开始totalMem方法报错,把清单文件中的minSdkVersion由8改成16的版本,这时会重新编译一下
这时把最小版本改成了16,而totalMem方法是可以在版本为16上用,所以编译完之后自然就不会报错了
这个时候再改成8,它再去编译时,这个totalMem方法是不会再编译报错的,只要你不修改,它永远都不会编译出错的
只要你一修改,它就会再次报错,那再次报错之后,再重复上边的方式就可以解决这个问题
这是给大家讲的开发技巧了,和下边在低版本中使用的方法getTotalRam相结合的
2.使用
找到TaskManagerActivity,在它的onCreate方法中可以先获取一个正在运行的进程的个数availableProcess
紧接着将availableProcess设置给tv_taskmanager_process_count
接下来再去获取下“空闲内存和总内存”,那这时候先去获取空闲内存了
紧接着还要将这个空闲内存avaliableRam转化一下,转化成字符串用Formatter.formatFileSize(context, number)
这样就将获取到的空闲内存转化成了字符串freesize,接下来获取总内存
获取总内存就比较特别:
有2个方法,要根据sdk版本的不同去调用不同的方法 首先获取sdk的版本,然后根据sdk版本执行相应的方法,来避免程序出错,判断如果sdk大于等于16时,可以去使用高版本的方法 else当sdk小于16时应该用过时的方法 这个“根据sdk的版本执行相应的方法,来避免程序出错”的方式,在开发中经常要用到 因为在高版本中有一些效果是特别炫的,但是在低版本中用的时候就报错,那所以说为了兼容低版本 在开发时都会根据sdk的版本来实现不同效果,比如在高版本中你的效果特别炫,在低版本中我就不要效果了,直接实现功能,先满足用户的需求,然后再考虑其他的,保证程序不会出错
总内存totalRam获取到之后,接下来也把它转化成字符串,最后将空闲内存和总内存设置给tv_taskmanager_ramortotal
即: TaskManagerActivity.java public class TaskManagerActivity extends Activity { private int availableProcess; private long avaliableRam; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //获取进程个数 availableProcess = ProcessUtils.getAvailableProcess(getApplicationContext()); tv_taskmanager_process_count.setText("运行中进程:"+availableProcess+"个"); //获取空闲内存和总内存 avaliableRam = ProcessUtils.getAvaliableRam(getApplicationContext()); //转成成字符串 String freesize = Formatter.formatFileSize(getApplicationContext(), avaliableRam); //获取总内存 //获取sdk的版本 int sdk = android.os.Build.VERSION.SDK_INT; //根据sdk的版本执行相应的方法,来避免程序出错 long totalRam; if(sdk >=16){ totalRam = ProcessUtils.getTotalRam(getApplicationContext()); }else{ totalRam = ProcessUtils.getTotalRam(); } //转化成字符串 totalsize = Formatter.formatFileSize(getApplicationContext(), totalRam); tv_taskmanager_ramortotal.setText("剩余/总内存:"+freesize+"/"+totalsize); } }
运行程序,进入进程管理条目,在进程管理界面的上边有:
“运行中进程:14个 剩余/总内存:406MB/501MB”
再在低版本2.3.3的模拟器上运行看下,也没有问题,也显示:
“运行中进程:12个 剩余/总内存:422MB/504MB”
这个就是为什么要在ProcessUtils.java中写两个获取总内存信息的方法的原因,因为要根据sdk版本执行相应的方法,来达到兼容不同版本的目的
获取进程个数及内存步骤总结如下:
1.创建一个工具类,创建获取进程数和内存的方法
public class ProcessUtils { /**
-
获取正在运行的进程的个数
-
@return
/
public static int getAvailableProcess(Context context){
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List processes = am.getRunningAppProcesses();
return processes.size();
}
/* -
获取空闲的内存信息
-
@param context
-
@return
/
public static long getAvaliableRam(Context context){
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
MemoryInfo outInfo = new MemoryInfo();//创建一个白纸
am.getMemoryInfo(outInfo);//将内存的信息写到白纸上
return outInfo.availMem;//空闲内存
// outInfo.totalMem;//总共内存
// return 0;
}
/* -
获取总的内存信息
-
@param context
-
@return
-
在高版本中使用的
/
public static long getTotalRam(Context context){
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
MemoryInfo outInfo = new MemoryInfo();//创建一个白纸
am.getMemoryInfo(outInfo);//将内存的信息写到白纸上
// return outInfo.availMem;//空闲内存
return outInfo.totalMem;//总共内存,高版本中有的字段,在低版本中不能使用
// return 0;
}
/* -
获取总的内存信息
-
@param context
-
@return
-
在低版本中使用的
-
@deprecated
/
public static long getTotalRam(){
StringBuilder sb = new StringBuilder();
File file = new File("/proc/meminfo");
try {
BufferedReader br = new BufferedReader(new FileReader(file));
//读取出一行信息
String readLine = br.readLine();
//解析出数字
//转化成字符数组
char[] charArray = readLine.toCharArray();
//拿出string类型中的所有数字
for (char c : charArray) {
//判断字符数组中的元素是否数字
if (c >=‘0’ && c<=‘9’) {
//添加组合
sb.append©;
}
}
String string = sb.toString();//数字
//转化成int
long parseInt = Integer.parseInt(string);
return parseInt1024;
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}2.使用
着重掌握根据sdk执行不同方法操作
public class TaskManagerActivity extends Activity { private int availableProcess; private long avaliableRam; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //获取进程个数 availableProcess = ProcessUtils.getAvailableProcess(getApplicationContext()); tv_taskmanager_process_count.setText("运行中进程:"+availableProcess+"个"); //获取空闲内存和总内存 avaliableRam = ProcessUtils.getAvaliableRam(getApplicationContext()); //转成成字符串 String freesize = Formatter.formatFileSize(getApplicationContext(), avaliableRam); //获取总内存 //获取sdk的版本 int sdk = android.os.Build.VERSION.SDK_INT; //根据sdk的版本执行相应的方法,来避免程序出错 long totalRam; if(sdk >=16){ totalRam = ProcessUtils.getTotalRam(getApplicationContext()); }else{ totalRam = ProcessUtils.getTotalRam(); } //转化成字符串 totalsize = Formatter.formatFileSize(getApplicationContext(), totalRam); tv_taskmanager_ramortotal.setText("剩余/总内存:"+freesize+"/"+totalsize); } }
9.7清理进程的细节处理#
点击全选时,会报空指针异常,点击原因跳转到TaskManagerActivity中的284行
即:
for (TaskInfo taskinfo : userAppInfos)
它说这个userAppInfos是为空的,那在它这个all方法中这么干了
即:
TaskInfo taskInfo2 = userAppInfos.get(0);
这时候把这个taskInfo2输出一下
即:
TaskInfo taskInfo2 = userAppInfos.get(0);
System.out.println(taskInfo2);
然后给TaskInfo taskInfo2 = userAppInfos.get(0)这行打个断点,debug运行,点击进程管理条目,再点击全选按钮
它会弹出弹框点击yes,直接进入到这个断点处了,接下来就是看断点了,将鼠标放到光标高亮的断点处,看到taskInfo2提示是有数据的,紧接着往下走一步,再往下走一步,再往下走,将这个增强for循环和它里边的if判断走过来了
那全部执行一下(点击全部执行的按钮),当这个全部执行的按钮可以点击时,继续点击,直到它永远变成灰色不可点击,最后它会来到一个ZygoteInit$MethodAnd处,看到它报错为:
Source not found
还有一个按钮显示:“Edit Source lookup Path...”
直接来到logcat处看:
“System.out TaskInfo [packagename=cn.itcast.mobilesafexian02,name="手机卫士西安02",icon=android.g...]”
这个“...”表示它后边还有数据,只不过显示不完,那继续看错误信息,还是说报空指针异常,往下看,还是这个地方,但是点击跳转报错点时,它跳转到增强for里边的if判断处了,即:
if (!appInfo.getPackagname().equals(getPackageName())) {
那问题就看到了,原因是这里增强for循环中用到的是taskinfo,而if判断处却用的是appInfo,所以将appInfo改为taskinfo即可,即:
if (!taskinfo.getPackagname().equals(getPackageName())) {
那下边的反选方法cancel中的if判断处也是一样的,将appinfo改为taskinfo,即:
if (!taskInfo.getPackagname().equals(getPackageName())) {
找到上边的点击事件那里,用的是appinfo,这个问题是因为拷贝之后没有改导致的,吓我一跳
运行程序,再点击全选,反选就没有问题了,但是当点击全选之后,再点击清理,提示:
“共杀死13个进程,释放107MB内存”
但是上边居然没有修改,还是:
“运行中进程:14个 剩余/总内存:398MB/501MB”
接下来要改一下上边这个操作,好让清理之后及时的刷新
当点击清理时,找到TaskManagerActivity中的清理方法clear,在清理时,要去修改下textView的值,所以在clear方法中当清理完成之后,首先修改显示的进程数,用tv_taskmanager_process_count去setText()
这个时候设置的应该是从原先总进程中减去清理的进程,那需要把原来进程的个数availableProcess设置成成员变量了
然后在这里让availableProcess等于availableProcess减去一个deletinfos.size()就可以了
这个deletinfos.size()里边存放的就是要删除的进程,那你有多少个,从原先的进程里边减去就能得到我现在清理之后的进程
那这个运行中进程处理完了之后,再来处理空闲空间,同样也是需要修改下显示,即给tv_taskmanager_ramortotal去setText
剩余内存memory的大小要把点击清理之后,清理出来的内存空间avaliableRam加到原来的剩余内存memory里边
这样表示清理之后空闲空间就多了,空闲的多了就表示剩余内存多了
clear删除的时候这个剩余内存空间memory是long类型的,把空闲内存avaliableRam改成成员变量
那在这里让memory等于清理出来的内存空间avaliableRam加上剩余内存memory,那因为memory是long类型的,所以还需要将它转化成String类型的formatFileSize,然后将formatFileSize设置给tv_taskmanager_ramortotal控件
即:
/**
-
清理
*/
public void clear(View v){//修改显示的进程数 availableProcess = availableProcess - deletinfos.size(); tv_taskmanager_process_count.setText("运行中进程:"+availableProcess); memory = avaliableRam+memory; //转化成字符串 String formatFileSize = Formatter.formatFileSize(getApplicationContext(), memory); //空闲空间处理 tv_taskmanager_ramortotal.setText("剩余/总内存:"+formatFileSize+"/"+totalsize); }
运行程序,发现点击全选,然后清理进程之后,上边显示:
“运行中进程:1 剩余/总内存:505MB/501MB”
看到运行中进程变成1是正确的,但是剩余内存505MB居然都大于总内存501MB了
原因是:
程序运行就需要内存,程序退出之后内存应该会释放的,但是android系统为了快速启动应用,不会完全释放,当再次打开获取内存时,就会把原来的内存加上去
这就是为什么会获取出来剩余内存比以前总内存大的原因,处理起来很简单,把总内存变大一点就没事了
或者说在处理时,不用这种加内存的处理方式,而是重新去获取一下比较好,这个留给你们下去去写了,时间来不及了我就不做了
清理进程之后,对“运行中进程:xx个 剩余/总内存:xxxMB/501MB”的刷新显示,总结如下:
//修改显示的进程数 availableProcess = availableProcess - deletinfos.size(); tv_taskmanager_process_count.setText("运行中进程:"+availableProcess); memory = avaliableRam+memory; //转化成字符串 String formatFileSize = Formatter.formatFileSize(getApplicationContext(), memory); //空闲空间处理 tv_taskmanager_ramortotal.setText("剩余/总内存:"+formatFileSize+"/"+totalsize);
这个主要是锻炼大家成员变量的使用方式,其实逻辑非常简单,只不过你要处理的是一些成员变量的使用