手機衛士-11

手機衛士-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的啓動模式重溫

  1. standard 標準啓動模式

  2. singletop 單一頂部模式

    如果棧頂已經存在了activity,就不會重複的創建,而是複用棧頂已經存在的activity(瀏覽器的書籤)

  3. singletask 單一任務棧模式

    在任務棧裏面只能存在一個實例,這個實例是存在在手機衛士默認的任務棧裏面的

  4. 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卡是否有一樣的文件,以此來找到並刪除。 金山的痕跡清理


沒什麼好說的,呵呵

資料下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章