手机卫士-11
课1
看门狗WatchDogService程序优化
程序锁不断打开关闭打开关闭,有时还是有界面没及时切换过来 有一瞬间还看见程序的界面,隐私还是保护得不够好 原因是看门狗里WatchDogService.java里死循环,整个死循环的周期有一定的事件,所以会产生多次打开程序锁而界面没切换过来 那是因为应用程序还不够优化
//该标志符用来控制是否不断刷新
flag = true;
new Thread() {
//其实该service所做的事件就是创建一个死循环,不断查看tempStopPacknames集合里的数据和新打开的app,从而进行比较操作
public void run() {
//如果为true就进入死循环
while (flag) {
// 获取用户的正在运行的应用程序任务栈的列表,最近使用的在集合的最前面
List<RunningTaskInfo> taskinfos = am.getRunningTasks(100);
String packname = taskinfos.get(0).topActivity
.getPackageName();
System.out.println(packname);
//对比获得的最新加载的app的名字是否存在于加锁app数据库中:当在加锁数据库中找到该packname,那么再比较该packname的app有没有通过密码校验
if (dao.find(packname)) {
//有通过密码校验
// 检查主人是否发过临时停止保护的指令
if (tempStopPacknames.contains(packname)) {
// 什么事情都不做,即不再弹出程序锁界面
//没有通过密码校验
} else {
// 这个应用程序需要被锁定
// 跳出来看门狗,让用户输入密码。
Intent intent = new Intent(WatchDogService.this,
WatchDogEnterPwdActivity.class);
//给该WatchDogEnterPwdActivity.class设置一个开启任务的模式
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//用意图开启界面,并加上应用的信息
intent.putExtra("packname", packname);
startActivity(intent);
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}.start();
我们给WatchDogService.java里死循环里加个计算器来计算循环一个的时间。
这是在效率比较高的模拟器结果 换成一个arm的模拟器来部署一下应用
看看其效率如何
- 如何优化?
处理细节: 在查询数据库的过程中不断打开数据库然后close,效率低,我们改造下ApplockDao.java,
ApplockDao.java
/**
* 查询全部的要锁定的应用程序
* @return 全部锁定应用程序包名的集合
*/
public List<String> findAll(){
List<String> packnames = new ArrayList<String>();
SQLiteDatabase db = helper.getWritableDatabase();
Cursor cursor = db.query("lockinfo", null, null, null, null, null, null);
while(cursor.moveToNext()){
String packname = cursor.getString(cursor.getColumnIndex("packname"));
packnames.add(packname);
}
cursor.close();
db.close();
//返回一个叫packnames的集合
return packnames;
}
把所有的数据一次取出来,放在内存中,然后让看门狗去查看内存,这样效率大大改善。 经过以上的改善,
得到如下的改善
但是这样的死循环非常耗电,占据的cpu资源大多,怎么解决?
搜搜地图(相当费电,后来版本来个重大改变后,在锁屏后gps的定位会占时关闭掉,这样的改变可可以省不少的电量)--->腾讯地图
需求:那么我们可以考虑,我们能否锁屏后我们的看门狗是否可以停掉,打开锁后看门狗会打开。
把看门狗中死循环的代码抽取到子线程中去跑。 把线程封装成一个方法
startDogThread();
然后让死循环加入一个成员变量标记flag,在自定义广播接受者出接收开关屏幕的广播事件处理
else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
tempStopPacknames.clear();
// 临时停止看门狗
flag = false;
} else if (Intent.ACTION_SCREEN_ON.equals(action)) {
// 开启看门狗
if (!flag) {
startDogThread();
}
}
receiver = new InnerReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("com.itheima.doggoaway");
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
registerReceiver(receiver, filter);
这样又大大减低了电量的消耗
优化程序后的小bug,上锁效果消失了?
在提高看门狗效率的同时还有bug:我们改造了ApplockDao的findAll时,这时看门狗只看集合内存里保存的应用集合,这样如果当操作一个应用上锁之后,再去锁上别的应用时这个后被锁上的app的上锁效果就失效了,因为之前看门狗里的死循环是检查private List tempStopPacknames;集合,现在是看新定义的private List lockPacknames;集合,以实现缓存的效果,当在点击listview里的加锁和解锁的按钮时,它们操作的是数据库dao的add和delete的方法,而方法是继续调用数据库的操作,看门狗根本不知道数据库也发生了变化,那么如果我们怎么解决?
改造ApplockDao的add和delete,发送自定义广播,当我们进行上锁的动画操作时,会对数据库进行操作,那么我们让ApplockDao的add和delete加上自定义广播,让看门狗接收这自定义的广播,这就可以让看门狗知道需要对数据库进行增删的操作了。在看门狗里这样接收广播
ApplockDao.java
/**
* 添加一条锁定的应用程序
* @param packname 包名
*/
public void add(String packname){
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("packname", packname);
db.insert("lockinfo", null, values);
db.close();
//通知看门狗更新锁定的应用程序集合
Intent intent = new Intent();
intent.setAction("com.itheima.mobilesafe.notifydogadd");
intent.putExtra("packname", packname);
context.sendBroadcast(intent);
}
/**
* 删除一条锁定的应用程序
* @param packname 包名
*/
public void delete(String packname){
SQLiteDatabase db = helper.getWritableDatabase();
db.delete("lockinfo", "packname=?", new String[]{packname});
db.close();
//通知看门狗更新锁定的包名集合
Intent intent = new Intent();
intent.setAction("com.itheima.mobilesafe.notifydogdelete");
intent.putExtra("packname", packname);
context.sendBroadcast(intent);
}
WatchDogService.java
else if("com.itheima.mobilesafe.notifydogadd".equals(action)){
lockPacknames.add(intent.getStringExtra("packname"));
}else if("com.itheima.mobilesafe.notifydogdelete".equals(action)){
lockPacknames.remove(intent.getStringExtra("packname"));
}
receiver = new InnerReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("com.itheima.mobilesafe.notifydogdelete");
filter.addAction("com.itheima.mobilesafe.notifydogadd");
registerReceiver(receiver, filter);
总结:广播很灵活,这里使用了自定义广播让不同应用或组件之间发送消息接收消息实现通信!!! 改善后的看门狗WatchDogService和 ApplockDao的完整逻辑
WatchDogService.java
/**
* 监视当前用户操作的应用程序,如果这个应用程序需要保护,看门狗就蹦出来,让用户输入密码
* //其实该service所做的事件就是创建一个死循环,不断查看tempStopPacknames集合里的数据和新打开的app,从而进行比较操作
* @author Administrator
*
*/
public class WatchDogService extends Service {
private Intent intent;
private String packname;
//应用管理器
private ActivityManager am;
private boolean flag;
// 程序锁的数据库dao
private ApplockDao dao;
//内部广播类
private InnerReceiver receiver;
/**
* 临时停止保护的包名集合
*/
private List<String> tempStopPacknames;
private List<String> lockPacknames;
@Override
public IBinder onBind(Intent intent) {
return null;
}
//内部广播类:该广播类主要是操作tempStopPacknames集合,即操作从程序锁界面传来的应用的名字和操作锁屏的状态中
//集合的清空,把集合提供给service去控制应用的加锁效果
//专门获取程序界面中传来的广播信号,通知WatchDogService去解除程序锁界面
private class InnerReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
//广播一直接收所有的频道信息
String action = intent.getAction();
//获取WatchDogEnterPwdActivity传来的StringExtra
if("com.itheima.doggoaway".equals(action)){
//StringExtra的key
String packname = intent.getStringExtra("packname");
//看门狗把主人要求临时停止保护的包名给记录下来,即把WatchDogEnterPwdActicity传来的
//StringExtra:packname,把其app加入临时停止保护的集合中
tempStopPacknames.add(packname);
}else if(Intent.ACTION_SCREEN_OFF.equals(action)){
//当接收的广播频道是锁屏,就把临时停止保护的应用集合清零,即让下次打开要保护的应用再次输入密码
tempStopPacknames.clear();
//停止看门狗
flag = false;
}else if(Intent.ACTION_SCREEN_ON.equals(action)){
//当接收的广播频道是打开屏幕,
if(!flag){
startDogThread();
}
}else if("com.itheima.phonesafeguard.notifydogadd".endsWith(action)){
lockPacknames.add(intent.getStringExtra("packname"));
}else if("com.itheima.phonesafeguard.notifydogdelete".equals(action)){
lockPacknames.remove(intent.getStringExtra("packname"));
}
}
}
@Override
public void onCreate() {
//初始化临时取消保护的app的集合
tempStopPacknames = new ArrayList<String>();
//获取操作加锁app的数据库的接口
dao = new ApplockDao(this);
//一旦服务开启集合的内容就不会再发生变化
lockPacknames = dao.findAll();
intent = new Intent(WatchDogService.this,WatchDogEnterPwdActivity.class);
//给该WatchDogEnterPwdActivity.class设置一个开启任务的模式
intent.setFlags(intent.FLAG_ACTIVITY_NEW_TASK);
receiver = new InnerReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("com.itheima.doggoaway");
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction("com.itheima.phonesafeguard.notifydogadd");
filter.addAction("com.itheima.phonesafeguard.notifydogdelete");
registerReceiver(receiver, filter);
am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
startDogThread();
super.onCreate();
}
private void startDogThread(){
flag = true;
new Thread(){
//其实该service所做的事件就是创建一个死循环,不断查看
//tempStopPacknames集合里的数据和新打开的app,从而进行比较操作
public void run() {
//如果为true就进入死循环
while(flag){
//获取用户的正在运行的应用程序任务栈的列表,最近使用的在集合的最前面
List<RunningTaskInfo> taskinfos = am.getRunningTasks(1);
packname = taskinfos.get(0).topActivity.getPackageName();
System.out.println(packname);
//对比获得的最新加载的app的名字是否存在于加锁app数据库中;
//当在加锁数据库中找到该packname,那么再比较该packname的app有没有通过密码校验
// if(dao.find(packname)){
if(lockPacknames.contains(packname)){// 查询内存
//有通过密码校验
//检查主人是否发过临时停止保护的指令
if(tempStopPacknames.contains(packname)){
//什么事情都不做,即不在弹出程序锁界面
//没有通过密码校验
}else{
//用意图开启界面,并加上应用的消息
intent.putExtra("packname", packname);
startActivity(intent);
}
}
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}.start();
}
@Override
public void onDestroy() {
flag = false;
//动态注销广播
unregisterReceiver(receiver);
receiver = null;
super.onDestroy();
}
}
ApplockDao.java
/**
* 程序锁增删改查的业务类
*
*/
public class ApplockDao {
private ApplockDBHelper helper ;
private Context context;
public ApplockDao(Context context){
helper = new ApplockDBHelper(context);
this.context = context;
}
/**
* 添加一条锁定的应用程序
* @param packname 包名
*/
public void add(String packname){
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("packname", packname);
db.insert("lockinfo", null, values);
db.close();
//通知看门狗更新锁定的应用程序集合
Intent intent = new Intent();
intent.setAction("com.itheima.phonesafeguard.notifydogadd");
intent.putExtra("packname", packname);
context.sendBroadcast(intent);
}
/**
* 删除一条锁定的应用程序
* @param packname 包名
*/
public void delete(String packname){
SQLiteDatabase db = helper.getWritableDatabase();
db.delete("lockinfo", "packname=?", new String[]{packname});
db.close();
//通知看门狗更新锁定的包名集合
Intent intent = new Intent();
intent.setAction("com.itheima.phonesafeguard.notifydogdelete");
intent.putExtra("packname", packname);
context.sendBroadcast(intent);
}
/**
* 查询一条锁定的应用程序
* @param packname 包名
* @return true 被锁定false 没有锁定
*/
public boolean find(String packname){
boolean result = false;
SQLiteDatabase db = helper.getWritableDatabase();
Cursor cursor = db.query("lockinfo", null, "packname=?", new String[]{packname}, null, null, null);
if(cursor.moveToNext()){
result = true;
}
cursor.close();
db.close();
return result;
}
/**
* 查询全部的要锁定的应用程序
* @return 全部锁定应用程序包名的集合
*/
public List<String> findAll() {
List<String> packnames = new ArrayList<String>();
SQLiteDatabase db = helper.getWritableDatabase();
Cursor cursor = db.query("lockinfo", null, null , null, null, null, null);
while(cursor.moveToNext()){
String packname = cursor.getString(cursor.getColumnIndex("packname"));
packnames.add(packname);
}
cursor.close();
db.close();
//返回一个叫packnames的集合
return packnames;
}
}
课2
Activity的启动模式重温
- standard 标准启动模式
- singletop 单一顶部模式
如果栈顶已经存在了activity,就不会重复的创建,而是复用栈顶已经存在的activity(浏览器的书签)
- singletask 单一任务栈模式
在任务栈里面只能存在一个实例,这个实例是存在在手机卫士默认的任务栈里面的
- singleinstance 单例模式
在任务栈里面只能存在一个实例,这个实例是在自己单独新建的任务栈里面 6
修改成7 其实这些启动模式都是为了解决用户的体验感受的 程序锁开发结束
流量统计
开始流量统计模块 新建TrafficManagerActivity.java并配置清单文件
TrafficManagerActivity.java
public class TrafficManagerActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
<!-- 流量统计 -->
<activity android:name="com.itheima.mobile47.TrafficManagerActivity"></activity>
MainActivity加入节点case MainActivity.java
case 4:
//流量管理
intent = new Intent(MainActivity.this,TrafficManagerActivity.class);
startActivity(intent);
break;
安卓下如何获取系统的流量信息?
类比:Windows-->网络连接-->本地连接(网卡信息)可以统计出流量信息 安卓:TrafficStats类
Class that provides network traffic statistics. These statistics include bytes transmitted and received and network packets transmitted and received, over all interfaces, over the mobile interface, and on a per-UID basis. TrafficStats类的重要api
//获取手机所有的网络端口 , wifi , 2g/3g/4g(手机卡产生的流量)
TrafficStats.getTotalRxBytes(); //r -->receive接收,获取全部的接受的byte (下载)
TrafficStats.getTotalTxBytes(); //t -->translate发送,获取全部的发送的byte (上传)
TrafficStats.getMobileRxBytes();// 手机卡下载产生的流量 从开机开始到现在产出的流量
TrafficStats.getMobileTxBytes();// 手机卡上传产生的流量
流量管理app由于TrafficStats类api的特点:
TrafficStats.getMobileRxBytes();// 手机卡下载产生的流量 从开机开始到现在产出的流量
一般每5分钟计算一次流量,一天下来就相加,来计算一个总的流量消耗量。
其实流量管理的app的程序逻辑无非是计时器和数据库的操作
该方法就是获取某个应用程序的流量,每个应用程序都有一个对应的uid用户id,就因为有了内核uid,每个程序就有了不同的权限,换句话说,每个应用程序都有一个uid。
//在Android系统里面 ,给每一个应用程序都分配了一个用户id
//pid:进程id uid:用户id
TrafficStats.getUidRxBytes(10014);
TrafficStats.getUidTxBytes(10014);
获得3g/wifi产生了多少的流量
//分别列出来3g/wifi产生了多少的流量 //计时器。 ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); cm.getActiveNetworkInfo().getType();
//自动校验流量 , 偷偷后台给运营商发送短信。 //10010 10086 llcx
//联网禁用 //linux平台的防火墙软件。必须手机要有root权限,修改iptable
实现腾讯的抽屉效果
TrafficManagerActivity.java的布局 activitytrafficmanager.xml 抽屉控件 要使用必须给该控件定义一个id TrafficManagerActivity.java
<SlidingDrawer
android:orientation="horizontal"
android:id="@+id/my_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent">
</SlidingDrawer>
但是其实一个也不够(设计界面报错),还要定义很多个id:给把手handle也定义一个id,还有content内容也要定义一个id(还报错),
<SlidingDrawer
android:orientation="horizontal"
android:id="@+id/my_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:content="@+id/my_content"
android:handle="@+id/my_handle">
</SlidingDrawer>
还需定义一个ImageView,然后imageview的id要引用SlidingDrawer的handle的id,
<SlidingDrawer
android:orientation="horizontal"
android:id="@+id/my_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:content="@+id/my_content"
android:handle="@+id/my_handle" >
<ImageView
android:id="@id/my_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
</SlidingDrawer>
还有LinearLayout里要引用SlidingDrawer的content的id,
<SlidingDrawer
android:orientation="horizontal"
android:id="@+id/my_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:content="@+id/my_content"
android:handle="@+id/my_handle" >
<ImageView
android:id="@id/my_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<LinearLayout
android:background="#22000000"
android:id="@id/my_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是抽屉里面的内容,哈哈哈"
/>
</LinearLayout>
</SlidingDrawer>
注意要把该布局文件复制过来! 这是从下往上拉 我们可以改变里orientation的属性去改变拉的方向
课3
实现金山手机卫士小功能:常用手机号码的效果
金山手机卫士小功能:常用手机号码:
(listview嵌套这listview) 把金山apk里的数据库文件拷贝过来
观察其数据库 把数据库信息展现到我们想实现的效果里 新建CommonNumberActivity.java和配置清单文件
CommonNumberActivity.java
public class CommonNumberActivity extends Activity {
private static final String path = "/data/data/com.itheima.mobile47/files/commonnum.db";
private ExpandableListView elv;
private SQLiteDatabase db;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_common_num);
清单文件
<!-- 常用号码查询 -->
<activity android:name="com.itheima.mobile47.CommonNumberActivity"/>
布局怎么写呢? activitycommonnum.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
style="@style/textview_title_style"
android:text="常用号码查询" />
<ExpandableListView
android:id="@+id/elv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ExpandableListView>
</LinearLayout>
使用ExpandableListView控件,这样的控件可以实现我们想要的效果 在CommonNumberActivity.java加载ExpandableListView控件。 ExpandableListView控件是Listview的子类。 在实现该控件的adapter的实现类时,如图
这样实现了接口中太多的方法,换成继承BaseExpandableListAdapter 这靠谱 里面有方法我们需要的不多, 写个界面加入到高级工具里 activity_tools.xml+ToolsActivity.java修改,进入到CommonNumberActivity.java
的效果如下
把数据库的内容加载到ExpandableListView控件里
先把数据库放到assets里 我们改造SplashActivcity-->copyDB--->data\data目录下 SplashActivcity.java
try {
// 拷贝数据库到data/data目录下面
copyDB("address.db");
copyDB("commonnum.db");
新建CommonNumberDao.java:获取到数据库的路径,然后就是操作数据库获取数据库的信息
CommonNumberDao.java
/**
* 获取数据库里面一共有多少个分组
* @return
*/
public static int getGroupCount(SQLiteDatabase db){
Cursor cursor = db.rawQuery("select count(*) from classlist", null);
cursor.moveToNext();
int groupcount = cursor.getInt(0);
cursor.close();
return groupcount;
}
CommonNumberDao.java:
1、获取数据库里面一共有多少个分组
2、获取数据库里面某个分组有多少个孩子
3、获取数据库里面某个分组的名字
4、获取数据库里面某个分组里面某个孩子的名字和电话
改造完CommonNumberDao.java后,回到CommonNumberActivity.java里的adapter进行改造。
CommonNumberActivity.java
//BaseXXX SimpleXXX DefaultXXX BaiscXXXX
private class CommonNumAdapter extends BaseExpandableListAdapter{
//获取分组的数量
@Override
public int getGroupCount() {
return CommonNumberDao.getGroupCount(db);
}
//获取孩子的数量
@Override
public int getChildrenCount(int groupPosition) {
return CommonNumberDao.getChildrenCount(db,groupPosition);
}
@Override
public Object getGroup(int groupPosition) {
return null;
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return null;
}
@Override
public long getGroupId(int groupPosition) {
return 0;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return 0;
}
@Override
public boolean hasStableIds() {
return false;
}
//获取分组显示的view
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
TextView tv;
if(convertView!=null && convertView instanceof TextView){
tv = (TextView) convertView;
}else{
tv= new TextView(getApplicationContext());
}
tv.setTextSize(20);
tv.setTextColor(Color.RED);
tv.setText(" "+CommonNumberDao.getGroupNameByPosition(db,groupPosition));
return tv;
}
//获取某个位置的孩子的view对象
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
TextView tv;
if(convertView!=null && convertView instanceof TextView){
tv = (TextView) convertView;
}else{
tv= new TextView(getApplicationContext());
}
tv.setTextSize(16);
tv.setTextColor(Color.BLACK);
String info = CommonNumberDao.getChildInfoByPosition(db,groupPosition, childPosition);
tv.setText(info.split("#")[0]+"\n"+info.split("#")[1]);
return tv;
}
//指定孩子可以被点击
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}
运行:
但是不断滑动时的最后app还是挂了,报出了内存溢出 改善程序:
view复用 还有bug:展开所有,然后不断拖动,也不会挂掉。但是如果拖动个几千次上万次,有可能数据打不开了,原因也是程序不断打开关闭打开关闭的操作也让数据库最后垮了,
改造:数据库改成在onCreate里打开,
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_common_num);
db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
然后在onDestroy里关闭数据库,同时要在CommonNumberDao.java里的增删查改api把数据库对象传过来
@Override
protected void onDestroy() {
super.onDestroy();
db.close();
}
如果想让孩子条目可以被点击,首先要设置如下:
//指定孩子可以被点击
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
然后在控件对象上设置:
elv = (ExpandableListView) findViewById(R.id.elv);
elv.setAdapter(new CommonNumAdapter());
elv.setOnChildClickListener(new OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
System.out.println("被点击了。"+CommonNumberDao.getChildInfoByPosition(db, groupPosition, childPosition));
return false;
}
});
课4
缓存清理模块:获取缓存信息 (建议看多一次视频:实用!)
缓存清理模块介绍 什么是缓存?
data/data里的cache文件夹 每一个文件夹有可以创建一个cache的文件夹缓存文件
一个应用程序如何生成缓存?
新建工程:缓存测试
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getFilesDir();//得到应用程序在文件系统上的路径 /data/data/包名/files
getCacheDir();//应用程序有临时的数据需要保存 ,就可以存放在cache目录,/data/data/包名/cache
try {
File file = new File(getCacheDir(),"gaga.txt");
FileOutputStream fos = new FileOutputStream(file);
fos.write("sfdaf".getBytes());
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
新建了工程后就可以使用金山手机位置使用缓存清理功能,可以搜出该工程产生的缓存文件。
在手机设置里的应用栏目也会显示:
一般我们从网上下载了一些临时的图片,应用会自动放在cache里,当在手机设置里的应用栏目点击清空缓存,可以把cache删除。
新建工程:获取缓存测试
划红线的数据谷歌怎么赋值的? 我们看系统上层所有的源码中找到Setting 在工程里导入Setting,为了查找里面的源码实现。
查找技巧,先搜界面,然后再搜java代码,看源码是怎么给划红线的数据赋值的 ctr+h--->全局搜索 缓存+*.xml
然后根据线索一步一步的最后定位到了res--->cachesizetext--->InstalledAppDetails.java,获取缓存的方法很明显都在该java文件里
目标InstalledAppDetails.java,在该类里可以找到谷歌如何给划红线的数据赋值(缓存)
ctr+k快速搜索 下次尝试操作寻找下。
源码定位的逻辑:
我们的目标是划红线的缓存数据谷歌是怎么赋值
1、从布局出发:首先在Eclipse里使用ctr+h全局搜索:缓存 *.xml
2、从搜索到的信息
这是在setting源码中布局文件xml中出现过的字段,打开文件可以找到该字段的来源
<string name="cache_header_label" msgid="1877197634162461830">"缓存"</string>
<string name="clear_cache_btn_text" msgid="5756314834291116325">"清除缓存"</string>
<string name="cache_size_label" msgid="7505481393108282913">"缓存"</string>
3、根据这个来源再继续全局搜索:cachesizelabel
*.xml,这样的目的是继续搜索到设置中心的布局文件,并找到布局文件中cachesizelabel的id,搜索结果是installedappdetails.xml出现过cachesizelabel这个id
4、根据该布局文件可以分析出显示缓存的数字的变量是:
<TextView
android:id="@+id/cache_size_text"
android:textAppearance="?android:attr/textAppearanceMedium"
android:paddingTop="6dip"
android:paddingRight="6dip"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:maxLines="1" />
5、继续使用全局搜索:cachesizetext
*.java,这样可以搜到cachesizetext出自于哪个类文件:搜索结果是InstalledAppDetails.java里的一段代码
// Cache section
mCacheSize = (TextView) findViewById(R.id.cache_size_text);
6、继续顺藤摸瓜:搜索: mCacheSize:找到一段关键代码:
if (mLastCacheSize != mAppEntry.cacheSize) {
mLastCacheSize = mAppEntry.cacheSize;
mCacheSize.setText(getSizeStr(mAppEntry.cacheSize));
7、我们继续点进cacheSize观察其来自于哪里:然后定位到了目标类ApplocationState.java,并发现了一段关键代码:
public static class SizeInfo {
long cacheSize;
long codeSize;
long dataSize;
}
8、然后继续搜索,然后定位到了最关键的代码块:分析:
9、最后搜索远程服务接口的实现类对象:mStatsObserver,最后定位到以下这段代码:
10、然后我们在工程里使用PackageManager类来使用getPackageSizeInfo方法去获取缓存值,但是遗憾的是,谷歌早就把getPackageSizeInfo方法给隐藏起来了:因此我们可以考虑使用反射的方法来获取该方法
/**
* Retrieve the size information for a package.
* Since this may take a little while, the result will
* be posted back to the given observer. The calling context
* should have the {@link android.Manifest.permission#GET_PACKAGE_SIZE} permission.
*
* @param packageName The name of the package whose size information is to be retrieved
* @param userHandle The user whose size information should be retrieved.
* @param observer An observer callback to get notified when the operation
* is complete.
* {@link android.content.pm.IPackageStatsObserver#onGetStatsCompleted(PackageStats, boolean)}
* The observer‘s callback is invoked with a PackageStats object(containing the
* code, data and cache sizes of the package) and a boolean value representing
* the status of the operation. observer may be null to indicate that
* no callback is desired.
*
* @hide
*/
public abstract void getPackageSizeInfo(String packageName, int userHandle,
IPackageStatsObserver observer);
继续在工程里获取缓存
技巧:如何观看安卓源码的某一个类中的某一个对象
在程序中打一个断点。 我们使用debug窗口去查看我们要了解的一些比较难理解的类,配合搜索工具来找出其源码父类的实现类。
code.google.com
www.github.com
我们工作的好帮手
使用反射机制拿出PackAgeManager类里被隐藏的方法:getPackageSizeInfo
然后还需调用远程服务 实现远程服务的接口 一系列工程完成后就可以获取缓存。
还有权限要加上:
获取缓存的逻辑(结合上面如何观察源码的方法和结论)
1、布局文件:需求:就是在输入框里输入一个应用的包名,然后点击确定按钮,就可以在控制台返回缓存的size
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<EditText
android:hint="请输入要获取的应用程序的包名"
android:id="@+id/et_packname"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:onClick="click"
android:text="获取缓存信息" />
</RelativeLayout>
2、从之前源码分析中知道我们可以从PackageManager类里获取getPackageSizeInfo方法来获取缓存size,因此我们获取到PackageManager类对象pm,然后快捷键发现方法获取不出来,查看PackageManager发现该方法是个抽象方法,也就是被谷歌隐藏了(hide)
3、那么我们就应该想办法怎么获得那个方法,方法就是(重要!)使用debug的方法来观察其PackageManager的实现类是什么?我们随便在程序中输出些什么,然后在该输出的地方打上一个断点
public class MainActivity extends Activity {
private EditText et_packname;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_packname = (EditText) findViewById(R.id.et_packname);
}
public void click(View view){
//获取某个应用程序的缓存
String packname = et_packname.getText().toString().trim();
//使用该类可以获取到缓存的方法getPackageSizeInfo
PackageManager pm = getPackageManager();
//但是该方法getPackageSizeInfo被谷歌隐藏,其在PackageManager类里只是一个抽象的方法,被隐藏了,那么技巧就是我们随便在程序中输出些东西,并打上断点
//以此来获取PackageManager里的内容
System.out.println("----");
4、然后我们进入debug视图观察得到结果:其PackageManager的实现类就是:ApplicationPackageManager,找到其源码发现:的确是继承PackageManager并发现:
/*package*/
final class ApplicationPackageManager extends PackageManager {
private static final String TAG = "ApplicationPackageManager";
private final static boolean DEBUG = false;
private final static boolean DEBUG_ICONS = false;
并发现的确有方法
@Override
public void getPackageSizeInfo(String packageName, int userHandle,
IPackageStatsObserver observer) {
try {
mPM.getPackageSizeInfo(packageName, userHandle, observer);
} catch (RemoteException e) {
// Should never happen!
}
}
5、那么我们就可以使用反射的机制把其方法反射出来了:
//pm.getPackageSizeInfo();
//这样可以拿到PackageManager实现类的字节码
Method[] methods = PackageManager.class.getMethods();
for(Method method : methods){
if("getPackageSizeInfo".equals(method.getName())){
try {
method.invoke(pm, packname,new MyObserver());
} catch (Exception e) {
e.printStackTrace();
}
}
}
6、观察源码中的方法,其 mPM.getPackageSizeInfo(packageName, userHandle, observer);中的参数是一个远程服务实现类:因为在之前的源码分析中得到的结论,那么我们必须找到该远程服务的aidl文件复制过来放在我们的工程目录下(注意包名要和原文件里的包名一致)
7、把aidl远程服务接口文件设置好后,就可以把其远程服务的类定义出来并实现,并传到反射出来的getPackageSizeInfo()方法里了。
private class MyObserver extends android.content.pm.IPackageStatsObserver.Stub{
@Override
public void onGetStatsCompleted(PackageStats pStats, boolean succeeded)
throws RemoteException {
long size = pStats.cacheSize;
System.out.println("缓存大小为:"+Formatter.formatFileSize(getApplicationContext(), size));
}
}
8、最后使用该方法还需要获取权限:
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
课5
手机卫士开发:缓存清理 (重看视频如何引入源码中的progressBar样式然后如何自定义)
开发缓存清理 新建CleanCacheActivity.java+配置清单
在MainActivity.java增加点击事件case 6
需求UI:
布局:activitycleancache.xml 在布局里引用了一个progressBar的特殊样式。在源码那里复制过来的。 在CleanCacheActivity.java加载自定义的progressBar,测试其效果。 需求:就是在进度条进行完后就显示被进度条覆盖的信息。 需求:模仿腾讯:每扫描一条cache,即显示一行信息条目,动态的显示(难) 继续实现布局:activitycleancache.xml activitycleancache.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
style="@style/textview_title_style"
android:gravity="center"
android:text="清理缓存" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ProgressBar
android:id="@+id/pb_scan"
android:layout_width="fill_parent"
android:layout_height="15dip"
android:indeterminateOnly="false"
android:progressDrawable="@drawable/progress_horizontal" />
<TextView
android:id="@+id/tv_scan_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="adfadsfa" />
</FrameLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1" >
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</LinearLayout>
</ScrollView>
<Button
android:onClick="cleanAll"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="清理全部" />
</LinearLayout>
(观察源码后学习的进度条样式)progress_horizontal.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background" android:drawable="@drawable/security_progress_bg">
</item>
<item android:id="@android:id/secondaryProgress" android:drawable="@drawable/security_progress">
</item>
<item android:id="@android:id/progress" android:drawable="@drawable/security_progress">
</item>
</layer-list>
- CleanCacheActivity.java编程
- 初始化控件
- 扫描所有的应用程序,查看他们的缓存信息;
- 扫描应用方法:scan(),耗时,所以放在子线程里。
- 获取缓存getCacheInfo(String packname)
- 在扫描应用方法里去获取应用的cache信息。
public class CleanCacheActivity extends Activity { protected static final int SCANNING = 1; protected static final int FINISHED = 2; public static final int ADD_CACHE_VIEW = 3; private TextView tv_scan_result; private ProgressBar pb_scan; private LinearLayout ll_container; private PackageManager pm; private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case SCANNING: //这样可以让子线程中遍历的应用信息滚动 PackageInfo info = (PackageInfo) msg.obj; tv_scan_result.setText("正在扫描:" + info.applicationInfo.loadLabel(pm)); break; case ADD_CACHE_VIEW: break; case FINISHED: //遍历完应用接收到该信号就把UI中的TextView隐藏掉 tv_scan_result.setText("扫描完毕!"); pb_scan.setVisibility(View.INVISIBLE); break; } }; }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_clean_cache); //该类用来获取缓存信息的 pm = getPackageManager(); tv_scan_result = (TextView) findViewById(R.id.tv_scan_result); pb_scan = (ProgressBar) findViewById(R.id.pb_scan); ll_container = (LinearLayout) findViewById(R.id.ll_container); // 扫描所有的应用程序,查看他们的缓存信息。 scan(); } private void scan() { new Thread() { public void run() { //从包管理者中获取到所有已经安装了的应用的包名集合 List<PackageInfo> infos = pm.getInstalledPackages(0); //给进度条设置最大数 pb_scan.setMax(infos.size()); int progress = 0; for (PackageInfo info : infos) { //调用内部方法 getCacheInfo(info); progress++; pb_scan.setProgress(progress); try { //模拟进度条运行耗时 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //遍历已经安装的应用安装包,并让UI中的TextView显示遍历的效果,所以使用Handler Message msg = Message.obtain(); msg.what = SCANNING; //把应用的对象发过去 msg.obj = info; handler.sendMessage(msg); } //遍历完就给Handler发信号把UI中的TextView隐藏 Message msg = Message.obtain(); msg.what = FINISHED; handler.sendMessage(msg); }; }.start(); } //该方法里使用反射技术获取缓存 public void getCacheInfo(PackageInfo info) { try { Method method = PackageManager.class.getMethod( "getPackageSizeInfo", String.class, IPackageStatsObserver.class); method.invoke(pm, info.packageName, new MyObserver(info)); } catch (Exception e) { e.printStackTrace(); } } private class MyObserver extends android.content.pm.IPackageStatsObserver.Stub { PackageInfo packinfo; public MyObserver(PackageInfo packinfo) { this.packinfo = packinfo; } @Override public void onGetStatsCompleted(PackageStats pStats, boolean succeeded) throws RemoteException {// 这个方法不是运行在主线程中,所有不可以直接更新ui long size = pStats.cacheSize; System.out.println(packinfo.applicationInfo.loadLabel(pm) + ",缓存大小为:" + Formatter.formatFileSize(getApplicationContext(), size)); } } } class CacheInfo { Drawable icon; String packname; String appname; long size; }
能找到数据。那么就是在scan给界面更新。 功能完成后 技巧:动态在布局文件里,例如LinearLayout里加载类似于listView样式的条目显示
我们需要把扫描到的信息条目怎么列在LinearLayout里?
ll_container.addView(xxx);
原因:
使用Handler解决就没问题了。 但是使用Message发送缓存信息发现比较多元化,所以定义一个缓存信息的bean内部类CacheInfo,让MEssage发给handler去处理。 测试的结果样式比较简单,
我们做的出专业些,定义一个条目布局:item_cacheinfo.xml
item_cacheinfo.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="wrap_content" >
<ImageView
android:id="@+id/iv_cache_icon"
android:layout_width="40dip"
android:layout_height="40dip"
android:src="@drawable/ic_launcher" />
<TextView
android:layout_centerVertical="true"
android:layout_width="wrap_content"
android:id="@+id/tv_cache_name"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/iv_cache_icon"
android:text="应用程序名称"
android:textColor="#000000"
android:textSize="20sp" />
<TextView
android:layout_centerVertical="true"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:id="@+id/tv_cache_size"
android:layout_height="wrap_content"
android:text="缓存大小"
android:textColor="#ff0000"
android:textSize="14sp" />
</RelativeLayout>
然后在CleanCacheActivity.java的Handler里使用打气筒inflate把条目布局给加载进去,在为条目布局控件赋值,赋值之后让linearLayout的对象ll_container.addView(view)添加进来。
考虑到加载的条目比较多,我们在布局文件里再加上一个scrollBar控件,可以让批量的条目支持滚动
CleanCacheActivity.java
public class CleanCacheActivity extends Activity {
protected static final int SCANNING = 1;
protected static final int FINISHED = 2;
public static final int ADD_CACHE_VIEW = 3;
private TextView tv_scan_result;
private ProgressBar pb_scan;
private LinearLayout ll_container;
private PackageManager pm;
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case SCANNING:
//这样可以让子线程中遍历的应用信息滚动
PackageInfo info = (PackageInfo) msg.obj;
tv_scan_result.setText("正在扫描:"
+ info.applicationInfo.loadLabel(pm));
break;
case ADD_CACHE_VIEW:
final CacheInfo cacheinfo = (CacheInfo) msg.obj;
View view = View.inflate(getApplicationContext(),
R.layout.item_cacheinfo, null);
ImageView icon = (ImageView) view
.findViewById(R.id.iv_cache_icon);
TextView name = (TextView) view
.findViewById(R.id.tv_cache_name);
TextView size = (TextView) view
.findViewById(R.id.tv_cache_size);
icon.setImageDrawable(cacheinfo.icon);
name.setText(cacheinfo.appname);
size.setText(Formatter.formatFileSize(getApplicationContext(),
cacheinfo.size));
ll_container.addView(view);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// pm.deleteApplicationCacheFiles(packageName,
// mClearCacheObserver);
try {
Method method = PackageManager.class.getMethod(
"deleteApplicationCacheFiles",
String.class, IPackageDataObserver.class);
method.invoke(pm, cacheinfo.packname,new IPackageDataObserver.Stub() {
@Override
public void onRemoveCompleted(String packageName, boolean succeeded)
throws RemoteException {
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
});
break;
case FINISHED:
//遍历完应用接收到该信号就把UI中的TextView隐藏掉
tv_scan_result.setText("扫描完毕!");
pb_scan.setVisibility(View.INVISIBLE);
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_clean_cache);
//该类用来获取缓存信息的
pm = getPackageManager();
tv_scan_result = (TextView) findViewById(R.id.tv_scan_result);
pb_scan = (ProgressBar) findViewById(R.id.pb_scan);
ll_container = (LinearLayout) findViewById(R.id.ll_container);
// 扫描所有的应用程序,查看他们的缓存信息。
scan();
}
private void scan() {
new Thread() {
public void run() {
//从包管理者中获取到所有已经安装了的应用的包名集合
List<PackageInfo> infos = pm.getInstalledPackages(0);
//给进度条设置最大数
pb_scan.setMax(infos.size());
int progress = 0;
for (PackageInfo info : infos) {
//调用内部方法
getCacheInfo(info);
progress++;
pb_scan.setProgress(progress);
try {
//模拟进度条运行耗时
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//遍历已经安装的应用安装包,并让UI中的TextView显示遍历的效果,所以使用Handler
Message msg = Message.obtain();
msg.what = SCANNING;
//把应用的对象发过去
msg.obj = info;
handler.sendMessage(msg);
}
//遍历完就给Handler发信号把UI中的TextView隐藏
Message msg = Message.obtain();
msg.what = FINISHED;
handler.sendMessage(msg);
};
}.start();
}
//该方法里使用反射技术获取缓存
public void getCacheInfo(PackageInfo info) {
try {
Method method = PackageManager.class.getMethod(
"getPackageSizeInfo", String.class,
IPackageStatsObserver.class);
method.invoke(pm, info.packageName, new MyObserver(info));
} catch (Exception e) {
e.printStackTrace();
}
}
private class MyObserver extends
android.content.pm.IPackageStatsObserver.Stub {
PackageInfo packinfo;
public MyObserver(PackageInfo packinfo) {
this.packinfo = packinfo;
}
@Override
public void onGetStatsCompleted(PackageStats pStats, boolean succeeded)
throws RemoteException {// 这个方法不是运行在主线程中,所有不可以直接更新ui
long size = pStats.cacheSize;
System.out.println(packinfo.applicationInfo.loadLabel(pm)
+ ",缓存大小为:"
+ Formatter.formatFileSize(getApplicationContext(), size));
if (size > 0) {
//在该远程服务实现类里进行对缓存的数据处理,使用把数据通过Message发给Handler然后处理UI
Message msg = Message.obtain();
msg.what = ADD_CACHE_VIEW;
CacheInfo cacheinfo = new CacheInfo();
cacheinfo.icon = packinfo.applicationInfo.loadIcon(pm);
cacheinfo.packname = packinfo.packageName;
cacheinfo.appname = packinfo.applicationInfo.loadLabel(pm)
.toString();
cacheinfo.size = size;
msg.obj = cacheinfo;
handler.sendMessage(msg);
}
}
}
class CacheInfo {
Drawable icon;
String packname;
String appname;
long size;
}
/**
* 清理全部的缓存
* @param view
*/
public void cleanAll(View view){
Method[] methods= PackageManager.class.getMethods();
for(Method method : methods){
if("freeStorageAndNotify".equals(method.getName())){
try {
method.invoke(pm, Integer.MAX_VALUE,new IPackageDataObserver.Stub() {
@Override
public void onRemoveCompleted(String packageName, boolean succeeded)
throws RemoteException {
System.out.println("result:"+succeeded);
}
});
} catch (Exception e) {
e.printStackTrace();
}
return;
}
}
}
}
课6
实现清理缓存信息(建议下午的观察源码的流程视频看一遍)
接着清理缓存信息 需求:模仿金山:跳转到设置中心的app设置中心 需求2:模仿腾讯:
找源码,实现清除缓存的点击功能
我们给条目设计一个点击事件,把以上的源码设计进来。 然后发现方法有不能直接调用,被谷歌工程师给隐藏了,所以又需要反射出来:
CleanCacheActivity.java
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// pm.deleteApplicationCacheFiles(packageName,
// mClearCacheObserver);
try {
Method method = PackageManager.class.getMethod(
"deleteApplicationCacheFiles",
String.class, IPackageDataObserver.class);
method.invoke(pm, cacheinfo.packname,new IPackageDataObserver.Stub() {
@Override
public void onRemoveCompleted(String packageName, boolean succeeded)
throws RemoteException {
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
});
然后点击发现又报出异常信息:
但是在加载权限是发现清单文件也报错了
那么我们强行删除Problems中的信息,看是否可以运行。 发现不可行:那么按照之前的逻辑去反射谷歌隐藏的方法来实现功能不可行,那么有什么办法清理应用缓存呢?
但是清理缓存的功能还是能实现的,那就是一键清理的安卓漏洞。
主要是安卓的一个服务会检测系统的内存,如果发现内存不足,那么会自动把系统里的所有缓存给删除,我们可以抓住此漏洞,模仿该特点去发送内存不足的信息给系统,一次达到一键清理缓存的功能实现。 在CleanCacheActivity里加入一键清理的功能cleanAll PackageManager-->freeStorageAndNotify
CleanCacheActivity.java
/**
* 清理全部的缓存
* @param view
*/
public void cleanAll(View view){
Method[] methods= PackageManager.class.getMethods();
for(Method method : methods){
if("freeStorageAndNotify".equals(method.getName())){
try {
method.invoke(pm, Integer.MAX_VALUE,new IPackageDataObserver.Stub() {
@Override
public void onRemoveCompleted(String packageName, boolean succeeded)
throws RemoteException {
System.out.println("result:"+succeeded);
}
});
} catch (Exception e) {
e.printStackTrace();
}
return;
}
}
}
源码解释:
使用反射来调用该方法。一次给系统发送信息。 点击一键清理
虽然报出异常: 但是其实都已经删除了 金山的SD卡清理
怎么实现?
SD卡不安全,每个应用程序都会在SD卡里写一些内容,这些内容我们不可能去手工认知,但是金山已经把他们写成数据库文件了,我们就使用金山的数据库来遍历SD卡是否有一样的文件,以此来找到并删除。 金山的痕迹清理
没什么好说的,呵呵