第十一天 手机卫士

##day11##
- 系统进程显示和隐藏


- 创建进程管理设置页面:ProcessManagerSettingActivity
- 编写设置页面布局文件
- 监听Checkbox的勾选事件,更新本地SharePreference


// 根据本地记录,更新checkbox状态
boolean showSystem = mPrefs.getBoolean("show_system_process", true);
if (showSystem) {
cbShowSystem.setChecked(true);
cbShowSystem.setText("显示系统进程");
} else {
cbShowSystem.setChecked(false);
cbShowSystem.setText("不显示系统进程");
}

// 设置状态勾选监听
cbShowSystem.setOnCheckedChangeListener(new OnCheckedChangeListener() {

@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {
cbShowSystem.setText("显示系统进程");
mPrefs.edit().putBoolean("show_system_process", true).commit();
} else {
cbShowSystem.setText("不显示系统进程");
mPrefs.edit().putBoolean("show_system_process", false)
.commit();
}
}
});


- 根据sp记录的是否显示系统进程,更新listview的显示个数


@Override
public int getCount() {
// 通过判断是否显示系统进程,更新list的数量
boolean showSystem = mPrefs.getBoolean("show_system_process", true);
if (showSystem) {
return 1 + mUserProcessList.size() + 1 + mSystemProcessList.size();
} else {
return 1 + mUserProcessList.size();
}
}


- 保证勾选框改变后,listview可以立即刷新


public void setting(View view) {
startActivityForResult(new Intent(this,
ProcessManagerSettingActivity.class), 0);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 当从设置页面回跳回来之后,刷新listview
mAdapter.notifyDataSetChanged();
}


- 锁屏清理


- 演示金山进程管理效果
- 后台启动服务,监听广播


//判断锁屏清理的广播是否正在运行
boolean serviceRunning = ServiceStatusUtils.isServiceRunning(
"com.itheima.mobilesafeteach.service.AutoKillService", this);

if (serviceRunning) {
cbLockClear.setChecked(true);
cbLockClear.setText("当前状态:锁屏清理已经开启");
} else {
cbLockClear.setChecked(false);
cbLockClear.setText("当前状态:锁屏清理已经关闭");
}

cbLockClear.setOnCheckedChangeListener(new OnCheckedChangeListener() {

@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
Intent intent = new Intent(ProcessManagerSettingActivity.this,
AutoKillService.class);
if (isChecked) {
// 启动锁屏清理的服务
startService(intent);
cbLockClear.setText("当前状态:锁屏清理已经开启");
} else {
// 关闭锁屏清理的服务
stopService(intent);
cbLockClear.setText("当前状态:锁屏清理已经关闭");
}
}
});


-------------------------------------


/**
* 锁屏清理进程的服务

* @author Kevin

*/
public class AutoKillService extends Service {

private InnerScreenOffReceiver mReceiver;

@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
super.onCreate();
//监听屏幕关闭的广播, 注意,该广播只能在代码中注册,不能在清单文件中注册
mReceiver = new InnerScreenOffReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(mReceiver, filter);
}

@Override
public void onDestroy() {
super.onDestroy();

unregisterReceiver(mReceiver);
mReceiver = null;
}

/**
* 锁屏关闭的广播接收者

* @author Kevin

*/
class InnerScreenOffReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
System.out.println("屏幕关闭...");
// 杀死后台所有运行的进程
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
List<RunningAppProcessInfo> runningAppProcesses = am
.getRunningAppProcesses();

for (RunningAppProcessInfo runningAppProcessInfo : runningAppProcesses) {
// 跳过手机卫士的服务
if (runningAppProcessInfo.processName.equals(ctx.getPackageName())) {
return;
}


am.killBackgroundProcesses(runningAppProcessInfo.processName);
}
}

}
}


- 定时器清理(介绍)


// 在AutoKillService的onCreate中启动定时器,定时清理任务
mTimer = new Timer();
mTimer.schedule(new TimerTask() {

@Override
public void run() {
System.out.println("5秒运行一次!");
}

}, 0, 5000);


@Override
protected void onDestroy() {
super.onDestroy();
mTimer.cancel();
mTimer = null;
}


- 桌面Widget(窗口小部件)


- widget介绍(Android, 瑞星,早期word)
- widget谷歌文档查看(API Guide->App Components->App Widget)
- widget开发流程


1. 在com.itheima.mobilesafe.receiver目录下创建MyWidget并且继承AppWidgetProvider
2. 在功能清单文件注册,参照文档


<receiver android:name=".receiver.MyWidget" >
           <intent-filter>
               <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
           </intent-filter>

           <meta-data
               android:name="android.appwidget.provider"
               android:resource="@xml/appwidget_info" />
       </receiver>


3. 在res/xml/创建文件example_appwidget_info.xml拷贝文档内容

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"    
   android:minWidth="294dp"    
   android:minHeight="72dp"//能被调整的最小宽高,若大于minWidth minHeight 则忽略    
   android:updatePeriodMillis="86400000"//更新周期,毫秒,最短默认半小时    
   android:previewImage="@drawable/preview"//选择部件时 展示的图像,3.0以上使用,默认是ic_launcher    
   android:initialLayout="@layout/example_appwidget"//布局文件
   android:configure="com.example.android.ExampleAppWidgetConfigure"//添加widget之前,先跳转到配置的activity进行相关参数配置,这个我们暂时用不到       
   android:resizeMode="horizontal|vertical"//widget可以被拉伸的方向。horizontal表示可以水平拉伸,vertical表示可以竖直拉伸
android:widgetCategory="home_screen|keyguard"//分别在屏幕主页和锁屏状态也能显示(4.2+系统才支持)
    android:initialKeyguardLayout="@layout/example_keyguard"//锁屏状态显示的样式(4.2+系统才支持)
>
</appwidget-provider>


4. 精简example_appwidget_info.xml文件,最终结果:


<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
   android:minWidth="294dp"    
    android:minHeight="72dp"
   android:updatePeriodMillis="1800000"
   android:initialLayout="@layout/appwidget"
  >
</appwidget-provider>


5. widget布局文件:appwidget.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="wrap_content"
       android:layout_height="wrap_content"
       android:background="#f00"
       android:text="我是widget,哈哈哈"
       android:textSize="30sp" />
</LinearLayout>


- 简单演示,高低版本对比
- 仿照金山widget效果, apktool反编译,抄金山布局文件(业内抄袭成风)


1. 反编译金山apk


使用apktool,可以查看xml文件内容
apktool d xxx.apk
2. 在金山清单文件中查找 APPWIDGET_UPDATE, 找到widget注册的代码
3. 拷贝金山widget的布局文件process_widget_provider.xml到自己的项目中
4. 从金山项目中拷贝相关资源文件,解决报错 
5. 运行,查看效果

- widget生命周期


/**
* 窗口小部件widget

* @author Kevin

*/
public class MyWidget extends AppWidgetProvider {

/**
* widget的每次变化都会调用onReceive
*/
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
System.out.println("MyWidget: onReceive");
}

/**
* 当widget第一次被添加时,调用onEnable
*/
@Override
public void onEnabled(Context context) {
super.onEnabled(context);
System.out.println("MyWidget: onEnabled");
}

/**
* 当widget完全从桌面移除时,调用onDisabled
*/
@Override
public void onDisabled(Context context) {
super.onDisabled(context);
System.out.println("MyWidget: onDisabled");
}

/**
* 新增widget时,或者widget更新时,调用onUpdate
* 更新时间取决于xml中配置的时间,最短为半小时
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
System.out.println("MyWidget: onUpdate");
}

/**
* 删除widget时,调onDeleted
*/
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
System.out.println("MyWidget: onDeleted");
}

/**
* 当widget大小发生变化时,调用此方法
*/
@Override
public void onAppWidgetOptionsChanged(Context context,
AppWidgetManager appWidgetManager, int appWidgetId,
Bundle newOptions) {
System.out.println("MyWidget: onAppWidgetOptionsChanged");
}

}



- 定时更新widget


问题: 我们需要通过widget实时显示当前进程数和可用内存,但widget最短也得半个小时才会更新一次, 如何才能间隔比较短的时间来及时更新?

查看金山日志:

当桌面有金山widget时, 金山会在后台启动service:ProcessService,并定时输出如下日志:
03-29 08:43:03.070: D/MoSecurity.ProcessService(275): updateWidget

该日志在锁屏状态下也一直输出.


解决办法: 后台启动service,UpdateWidgetService, 并在service中启动定时器来控制widget的更新


- 更新widget方法


/**
* 定时更新widget的service

* @author Kevin

*/
public class UpdateWidgetService extends Service {

private Timer mTimer;
private AppWidgetManager mAWM;

@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
super.onCreate();

mAWM = AppWidgetManager.getInstance(this);

// 启动定时器,每个5秒一更新
mTimer = new Timer();
mTimer.schedule(new TimerTask() {

@Override
public void run() {
System.out.println("更新widget啦!");
updateWidget();
}
}, 0, 5000);
}

/**
* 更新widget
*/
private void updateWidget() {
// 初始化远程的view对象
RemoteViews views = new RemoteViews(getPackageName(),
R.layout.process_widget);

views.setTextViewText(R.id.tv_running_processes, "正在运行的软件:"
+ ProcessInfoProvider.getRunningProcessNum(this));
views.setTextViewText(
R.id.tv_memory_left,
"可用内存:"
+ Formatter.formatFileSize(this,
ProcessInfoProvider.getAvailMemory(this)));

// 初始化组件
ComponentName provider = new ComponentName(this, MyWidget.class);

// 更新widget
mAWM.updateAppWidget(provider, views);
}

@Override
public void onDestroy() {
super.onDestroy();
mTimer.cancel();
mTimer = null;
}
}


-----------------------------


启动和销毁service的时机


分析widget的声明周期,在onEnabled和onUpdate中启动服务, 在onDisabled中结束服务


- 注意: APK安装在sd卡上,widget在窗口小部件列表里无法显示。   android:installLocation="preferExternal", 修改过来后,需要卸载,再去安装widget才生效;


- 点击事件处理


// 初始化延迟意图,pending是等待的意思
Intent intent = new Intent(this, HomeActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);

// 当点击widget布局时,跳转到主页面
views.setOnClickPendingIntent(R.id.ll_root, pendingIntent);

//当一键清理被点击是,发送广播,清理内存
Intent btnIntent = new Intent();
btnIntent.setAction("com.itheima.mobilesafeteach.KILL_ALL");
PendingIntent btnPendingIntent = PendingIntent.getBroadcast(this, 0,
btnIntent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.btn_clear, btnPendingIntent);


---------------------------  


/**
* 杀死后台进程的广播接受者
* 清单文件中配置action="com.itheima.mobilesafeteach.KILL_ALL"

* @author Kevin

*/
public class KillAllReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
System.out.println("kill all...");
// 杀死后台所有运行的进程
ProcessInfoProvider.killAll(context);
}
}


---------------------------


<receiver android:name=".receiver.KillAllReceiver" >
           <intent-filter>
               <action android:name="com.itheima.mobilesafeteach.KILL_ALL" />
           </intent-filter>
        </receiver>


- 做一个有情怀的程序员, 拒绝耗电!


当锁屏关闭时,停止widget定时器的更新


UpdateWidgetService:


// 注册屏幕开启和关闭的广播接受者
mReceiver = new InnerScreenReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
registerReceiver(mReceiver, filter);


/**
* 屏幕关闭和开启的广播接收者

* @author Kevin

*/
class InnerScreenReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_SCREEN_OFF.equals(action)) {// 屏幕关闭
if (mTimer != null) {
// 停止定时器
mTimer.cancel();
mTimer = null;
}
} else {// 屏幕开启
startTimer();
}
}
}
- 程序锁
- 高级工具中添加程序锁入口
- 新建程序锁页面 AppLockActivity
- 程序锁页面布局文件实现


activity_app_lock.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" >

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:gravity="center"
       android:orientation="horizontal" >

       <TextView
           android:id="@+id/tv_unlock"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:background="@drawable/tab_left_pressed"
           android:gravity="center"
           android:text="未加锁"
           android:textColor="#fff" />

       <TextView
           android:id="@+id/tv_locked"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:background="@drawable/tab_right_default"
           android:gravity="center"
           android:text="已加锁"
           android:textColor="#fff" />
   </LinearLayout>

   <LinearLayout
       android:id="@+id/ll_unlock"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical" >

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="未加锁软件:x个"
           android:textColor="#000" />

       <ListView
           android:id="@+id/lv_unlock"
           android:layout_width="match_parent"
           android:layout_height="match_parent" />
   </LinearLayout>

   <LinearLayout
       android:id="@+id/ll_locked"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical"
       android:visibility="gone" >

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="已加锁软件:x个"
           android:textColor="#000" />

       <ListView
           android:id="@+id/lv_locked"
           android:layout_width="match_parent"
           android:layout_height="match_parent" />
   </LinearLayout>

</LinearLayout>


- 点击标签切换页面


@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_unlock:// 展示未加锁页面,隐藏已加锁页面
llLocked.setVisibility(View.GONE);
llUnlock.setVisibility(View.VISIBLE);
tvUnlock.setBackgroundResource(R.drawable.tab_left_pressed);
tvLocked.setBackgroundResource(R.drawable.tab_right_default);
break;
case R.id.tv_locked:// 展示已加锁页面,隐藏未加锁页面
llUnlock.setVisibility(View.GONE);
llLocked.setVisibility(View.VISIBLE);
tvUnlock.setBackgroundResource(R.drawable.tab_left_default);
tvLocked.setBackgroundResource(R.drawable.tab_right_pressed);
break;
default:
break;
}
}


- 应用列表信息展现(展现全部应用列表数据)

- 使用数据库保存已加锁的软件


AppLockOpenHelper.java

// 创建表, 两个字段,_id, packagename(应用包名)
db.execSQL("create table applock (_id integer primary key autoincrement, packagename varchar(50))");

----------------------------------


AppLockDao.java(逻辑和黑名单列表类似)


/**
* 增加程序锁应用
*/
public void add(String packageName) {
SQLiteDatabase db = mHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("packagename", packageName);
db.insert("applock", null, values);
db.close();
}

/**
* 删除程序锁应用

* @param number
*/
public void delete(String packageName) {
SQLiteDatabase db = mHelper.getWritableDatabase();
db.delete("applock", "packagename=?", new String[] { packageName });
db.close();
}

/**
* 查找程序锁应用

* @param number
* @return
*/
public boolean find(String packageName) {
SQLiteDatabase db = mHelper.getWritableDatabase();
Cursor cursor = db.query("applock", null, "packagename=?",
new String[] { packageName }, null, null, null);

boolean result = false;
if (cursor.moveToFirst()) {
result = true;
}

cursor.close();
db.close();
return result;
}

/**
* 查找已加锁列表

* @return
*/
public ArrayList<String> findAll() {
SQLiteDatabase db = mHelper.getWritableDatabase();
Cursor cursor = db.query("applock", new String[] { "packagename" },
null, null, null, null, null);

ArrayList<String> list = new ArrayList<String>();
while (cursor.moveToNext()) {
String packageName = cursor.getString(0);
list.add(packageName);
}

cursor.close();
db.close();
return list;
}


- 监听list item点击事件,向数据库添加一些数据

lvUnLock.setOnItemClickListener(new OnItemClickListener() {

@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
AppInfo info = mUnlockList.get(position);
mDao.add(info.packageName);
}
});


- 已加锁和未加锁数据设置


private ArrayList<AppInfo> mLockedList;// 已加锁列表集合
private ArrayList<AppInfo> mUnlockList;// 未加锁列表集合


private Handler mHandler = new Handler() {


public void handleMessage(android.os.Message msg) {
// 设置未加锁数据
mUnlockAdapter = new AppLockAdapter(false);
lvUnLock.setAdapter(mUnlockAdapter);

// 设置已加锁数据
mLockedAdapter = new AppLockAdapter(true);
lvLocked.setAdapter(mLockedAdapter);
};
};


/**
* 初始化应用列表数据
*/
private void initData() {
new Thread() {
@Override
public void run() {
mList = AppInfoProvider.getAppInfos(AppLockActivity.this);

mLockedList = new ArrayList<AppInfo>();
mUnlockList = new ArrayList<AppInfo>();

for (AppInfo info : mList) {
boolean isLocked = mDao.find(info.packageName);
if (isLocked) {
mLockedList.add(info);
} else {
mUnlockList.add(info);
}
}

mHandler.sendEmptyMessage(0);
}
}.start();
}


- 界面效果完善


点击锁子图标后, 实现加锁和去加锁的逻辑, 界面跟着更新


class AppLockAdapter extends BaseAdapter {


private boolean isLocked;//true表示已加锁数据

public AppLockAdapter(boolean isLocked) {
this.isLocked = isLocked;
}

@Override
public int getCount() {
if (isLocked) {
return mLockedList.size();
} else {
return mUnlockList.size();
}
}

@Override
public AppInfo getItem(int position) {
if (isLocked) {
return mLockedList.get(position);
} else {
return mUnlockList.get(position);
}
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(final int position, View convertView,
ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = View.inflate(AppLockActivity.this,
R.layout.list_applock_item, null);
holder = new ViewHolder();
holder.ivIcon = (ImageView) convertView
.findViewById(R.id.iv_icon);
holder.tvName = (TextView) convertView
.findViewById(R.id.tv_name);
holder.ivLock = (ImageView) convertView
.findViewById(R.id.iv_lock);

convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

final AppInfo info = getItem(position);
holder.ivIcon.setImageDrawable(info.icon);
holder.tvName.setText(info.name);


if(isLocked) {
holder.ivLock.setImageResource(R.drawable.unlock);
}else {
holder.ivLock.setImageResource(R.drawable.lock);
}

holder.ivLock.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
if (isLocked) {
mDao.delete(info.packageName);// 从数据库删除记录
mLockedList.remove(info);// 从已加锁集合删除元素
mUnlockList.add(info);// 给未加锁集合添加元素
} else {
mDao.add(info.packageName);// 向数据库添加记录
mLockedList.add(info);// 给已加锁集合添加元素
mUnlockList.remove(info);// 从未加锁集合删除元素
}

// 刷新listview
mLockedAdapter.notifyDataSetChanged();
mUnlockAdapter.notifyDataSetChanged();
}
});

return convertView;
}
}


- 更新已加锁/未加锁数量


/**
* 更新已加锁和未加锁数量
*/
private void updateAppNum() {
tvUnLockNum.setText("未加锁软件:" + mUnlockList.size() + "个");
tvLockedNum.setText("已加锁软件:" + mLockedList.size() + "个");
}


// 每次刷新listview前都会调用getCount方法,可以在这里更新数量
@Override
public int getCount() {
updateAppNum();

if (isLocked) {
return mLockedList.size();
} else {
return mUnlockList.size();
}
}

- 动画实现


- 解决动画移动问题


导致的原因,动画没有开始播放,界面就刷新了。
动画播放需要时间的,动画没有播就变成了新的View对象。就播了新的View对象,
让动画播放完后,再去更新页面;


public AppLockAdapter(boolean isLocked) {
this.isLocked = isLocked;

// 右移
mLockAnim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0f,
Animation.RELATIVE_TO_SELF, 1f, Animation.RELATIVE_TO_SELF,
0, Animation.RELATIVE_TO_SELF, 0);
mLockAnim.setDuration(500);

// 左移
mUnLockAnim = new TranslateAnimation(Animation.RELATIVE_TO_SELF,
0f, Animation.RELATIVE_TO_SELF, -1f,
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF,
0);
mUnLockAnim.setDuration(500);
}


holder.ivLock.setOnClickListener(new OnClickListener() {


@Override
public void onClick(View v) {
if (isLocked) {
view.startAnimation(mUnLockAnim);
mUnLockAnim
.setAnimationListener(new AnimationListener() {


@Override
public void onAnimationStart(
Animation animation) {
}


@Override
public void onAnimationRepeat(
Animation animation) {
}
//监听动画结束事件
@Override
public void onAnimationEnd(
Animation animation) {
mDao.delete(info.packageName);// 从数据库删除记录
mLockedList.remove(info);// 从已加锁集合删除元素
mUnlockList.add(info);// 给未加锁集合添加元素


// 刷新listview
mLockedAdapter.notifyDataSetChanged();
mUnlockAdapter.notifyDataSetChanged();
}
});
} else {
view.startAnimation(mLockAnim);
mLockAnim.setAnimationListener(new AnimationListener() {


@Override
public void onAnimationStart(Animation animation) {
}


@Override
public void onAnimationRepeat(Animation animation) {
}


//监听动画结束事件
@Override
public void onAnimationEnd(Animation animation) {
mDao.add(info.packageName);// 向数据库添加记录
mLockedList.add(info);// 给已加锁集合添加元素
mUnlockList.remove(info);// 从未加锁集合删除元素
// 刷新listview
mLockedAdapter.notifyDataSetChanged();
mUnlockAdapter.notifyDataSetChanged();
}
});
}
}
});

你可能感兴趣的:(第十一天 手机卫士)