項(xiàng)目Demo地址:
https://github.com/liaozhoubei/EndCallAndClearCacheDemo
Demo項(xiàng)目以獲取全部緩存和清理全部緩存為主
代碼界中有句話:不要重復(fù)造輪子!
意思就是如果你發(fā)現(xiàn)你想要實(shí)現(xiàn)的功能已經(jīng)有人做出來了喻旷,而且是開源的抖拦,那就不要浪費(fèi)時間了,直接拿過來用吧闷供!
當(dāng)然這句話對于想要提高技術(shù)的人來說不適應(yīng)仍稀。但是對于趕進(jìn)度的碼農(nóng)來說確實(shí)是適用的拳芙。
可惜對于很多Android新手來說想要把輪子拿過來改一改都不會察藐,現(xiàn)在我們就來學(xué)習(xí)如何在Android源碼這個輪子之中找到自己想要的功能并且搬過來吧。
所需文件:Android上層源碼(直接用搜索引擎搜舟扎,能找到一堆)
源碼下載:http://pan.baidu.com/s/1eRKpOOy
Android上層源碼分飞,即packages的源碼并不大,也就30多m睹限。
工具準(zhǔn)備好之后凄敢,我們就開始本次的任務(wù)吧怨规。
實(shí)現(xiàn)清理手機(jī)緩存的功能
現(xiàn)在我們想要獲取手機(jī)緩存丽旅,然后清理手機(jī)緩存豪椿。
但是我們不知道怎樣獲取手機(jī)緩存呀,也不知道怎么清理緩存叨恨,甚至沒怎么接觸過柳刮,這可如何是好!
找到功能在哪里實(shí)現(xiàn)過
且慢,我們先想想曾經(jīng)在哪接觸過它秉颗。沒錯痢毒,對于手機(jī)達(dá)人的我們來說是經(jīng)常會接觸到這個選項(xiàng)的,當(dāng)我們打開手機(jī)設(shè)置-apps蚕甥,隨意選擇一個app進(jìn)入app詳細(xì)頁面的時候我們就會發(fā)現(xiàn)這么一個Cache的文字以及Clear cache的按鈕哪替,Cache的右邊顯示的是當(dāng)前這個app的緩存大小,而我們點(diǎn)擊Clear cache的時候緩存就會清零菇怀。這不正是我們要實(shí)現(xiàn)的功能么凭舶?
導(dǎo)入實(shí)現(xiàn)功能的項(xiàng)目
之后這個功能在哪里實(shí)現(xiàn)過之后,應(yīng)該怎樣找到實(shí)現(xiàn)這個功能的地方呢敏释?
別著急库快,我們打開剛才下載的《Android上層源碼》摸袁,在里面搜索钥顽,發(fā)現(xiàn)里面有個Settings的項(xiàng)目文件夾!那么Settings是不是代表著手機(jī)中的setting這個選項(xiàng)靠汁?我們的清理緩存的功能是不是就在里面蜂大?
話不多說,直接在開發(fā)工具中導(dǎo)入Settings項(xiàng)目蝶怔。
找到具體實(shí)現(xiàn)的代碼(重點(diǎn))
現(xiàn)在問題又來了奶浦,工程已經(jīng)導(dǎo)入了,源代碼也可以查看了踢星,可是一個package動不動就七八個類文件澳叉,直接一個一個文件查看太浪費(fèi)時間和精力了,那么我們怎么找到我們需要的代碼呢沐悦?
這時我們就要充分利用好開發(fā)工具了成洗,非常感謝制作出開發(fā)工具的大神們,讓我們開發(fā)省了多少時間藏否。
我們再次查看剛才的App Info頁面瓶殃,仔細(xì)查看清理緩存這一部分「鼻看到Cache文字和Clear cache按鍵遥椿,似乎并沒什么特別的。
且慢淆储,再仔細(xì)想想冠场。Android之中如果想調(diào)用文字,不是要用android:text="@string/xxxx"這樣的形式么本砰。那么我們就能夠先找到Cache單詞的引用碴裙,然后順藤摸瓜的直接找到使用文字的代碼,既然引用了文字,一定與清理緩存有關(guān)系的青团。
注:Android是不建議使用硬編碼即android:text="我是硬編碼"直接使用文字的譬巫。
順藤摸瓜找代碼
在eclipse中直接使用Ctrl+H快捷鍵,打開搜索框選擇File Search督笆,然后在其中的Containing text中填寫要搜索的文字芦昔,然后在File name patterns中填入要搜索哪些文件。
我們首先要找到這個文字的資源文件id娃肿,然后通過資源文件id找到在哪里引用咕缎。
- 首先在Containing text填入在布局頁面中展現(xiàn)的文字Cache,然后這個文字是出現(xiàn)在xml布局頁面中料扰,所以File name patterns中填入*.xml凭豪,意思是我們要搜索所有的xml頁面中包含Cache文字的地方
然后在結(jié)果頁面中打開res-value-strings,發(fā)現(xiàn)將所有包含Cache文字的行都列了出來晒杈。我們發(fā)現(xiàn)有兩個地方直接使用了Cache
<!-- Manage applications, Header name used for cache information -->
<string name="cache_header_label">Cache</string>
<!-- Manage applications, text label for button -->
<string name="clear_cache_btn_text">Clear cache</string>
<!-- Manage applications, label that appears next to the cache size -->
<string name="cache_size_label">Cache</string>
分別是cache_header_label和cache_size_label嫂伞,翻譯成中文不就是緩存標(biāo)題欄和緩存大小欄么。
- 重復(fù)上面的方法拯钻,直接搜索cache_size_label帖努,這次在搜索結(jié)果中顯示installed_app_details.xml這個布局頁面使用了。
打開布局頁面粪般,直接查看布局預(yù)覽拼余,這次我們終于找到了App Info的布局頁面了!如下圖
找到布局之后亩歹,查看布局中的id名匙监,發(fā)現(xiàn)TextView中android:id="@+id/cache_size_text"這一行就是我們所需要的放置緩存大小的文字,我們自己通過這個id尋找在哪里引用
再次打開搜索框小作,Containing text填入:cache_size_text亭姥,F(xiàn)ile name patterns中填入*.java,顯示以下結(jié)果
在打開InstalledAppDetails.java之后躲惰,發(fā)現(xiàn)其中的322行
mCacheSize = (TextView) findViewById(R.id.cache_size_text);
這毫無疑問就是設(shè)置緩存大小的地方了致份。
那么mCacheSize的文字又是從哪里來的呢?
繼續(xù)在InstalledAppDetails.java尋找础拨,發(fā)現(xiàn):
mCacheSize.setText(getSizeStr(mAppEntry.cacheSize));
設(shè)置了緩存的大小氮块,那么它是怎么獲得緩存大小的呢?
分析getSizeStr(mAppEntry.cacheSize))诡宗,發(fā)現(xiàn)getSizeStr()這個方法只是純粹用來格式化緩存的滔蝉,也就是將b轉(zhuǎn)換為kb,kb轉(zhuǎn)換為mb而已塔沃,在InstalledAppDetails可以找到它的源碼:
private String getSizeStr(long size) {
if (size == SIZE_INVALID) {
return mInvalidSizeStr.toString();
}
return Formatter.formatFileSize(this, size);
}
這樣一來就更清晰明了了蝠引,mAppEntry.cacheSize就是緩存的大小,我們繼續(xù)探索它的數(shù)值是如何獲取出來的。
Ctrl + 左鍵點(diǎn)擊cacheSize螃概,我們就進(jìn)入了ApplicationsState的源碼矫夯,然后又驚奇的發(fā)現(xiàn)mAppEntry是AppEntry的實(shí)例,而AppEntry則是ApplicationsState的靜態(tài)內(nèi)部類〉跬荩現(xiàn)在看來一切的答案都能在ApplicationsState這個類中尋找训貌。
在ApplicationsState中仔細(xì)搜索cacheSize,我們發(fā)現(xiàn)在以下方法中entry.cacheSize = stats.cacheSize冒窍,被賦值了:
final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
·······
if (entry.size != newSize ||
entry.cacheSize != stats.cacheSize ||
entry.codeSize != stats.codeSize ||
entry.dataSize != stats.dataSize) {
entry.size = newSize;
entry.cacheSize = stats.cacheSize;
entry.codeSize = stats.codeSize;
entry.dataSize = stats.dataSize;
entry.sizeStr = getSizeStr(entry.size);
if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry
+ ": " + entry.sizeStr);
sizeChanged = true;
}
}
·······
}
};
細(xì)細(xì)研究entry.cacheSize递沪,發(fā)現(xiàn)entry也是AppEntry的變量,那么無疑mAppEntry.cacheSize就是entry.cacheSize综液,毫無疑問我們得出了結(jié)論款慨,這個就是內(nèi)存大小了。
但是在進(jìn)一步研究entry.cacheSize = stats.cacheSize谬莹,我們無法從stats.cacheSize這個方法中直接得到內(nèi)存大小檩奠,因?yàn)樵诟檚tats.cacheSize方法進(jìn)入PackageStats類,繼續(xù)跟蹤的時候届良,發(fā)現(xiàn)它是依靠于底層用C語言實(shí)現(xiàn)的笆凌。
既然我們無法直接獲得PackageStats這個類的方法圣猎,那么我們回過頭來研究
final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub()
它創(chuàng)建了一個實(shí)例士葫,并且被賦予final,一般情況下被賦予final的值都會在內(nèi)部被使用送悔,一旦能夠被正常使用慢显,那么就能夠直接獲得PackageStats的默認(rèn)變量,然后使用stats.cacheSize獲得緩存大小了欠啤。
通過搜索mStatsObserver的使用情況荚藻,發(fā)現(xiàn)了這兩行相關(guān)代碼:
mCurComputingSizePkg = entry.info.packageName;
mPm.getPackageSizeInfo(mCurComputingSizePkg, mStatsObserver);
到了這一步一切都差不多了,其中的entry.info.packageName所獲得的是包名洁段,而mPm則是PackageManager的變量应狱。
創(chuàng)建工程獲取緩存
我們直接創(chuàng)建一個工程項(xiàng)目來獲取緩存,在MainActivity中增加以下:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//獲取緩存
PackageManager pm = getPackageManager();
pm.getPackageSizeInfo("com.example.writecache", mStatsObserver);
}
IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
long cachesize = stats.cacheSize;//緩存大小
long codesize = stats.codeSize;//應(yīng)用程序的大小
long datasize = stats.dataSize;//數(shù)據(jù)大小
String cache = Formatter.formatFileSize(getApplicationContext(), cachesize);
String code = Formatter.formatFileSize(getApplicationContext(), codesize);
String data = Formatter.formatFileSize(getApplicationContext(), datasize);
System.out.println("cachesize:"+cache +" codesize:"+code+" datasize:"+data);
}
};
}
但是顯示找不到IPackageStatsObserver包祠丝,無法導(dǎo)入疾呻。這是因?yàn)镮PackageStatsObserver是一個aidl類,所以我們從外部直接獲取到了IPackageStatsObserver.aidl,然后再src目錄下創(chuàng)建IPackageStatsObserver的包名写半,將IPackageStatsObserver.aidl粘貼進(jìn)去岸蜗。這時IPackageStatsObserver類便不會報錯了。
但是又有個問題
pm.getPackageSizeInfo("com.example.writecache", mStatsObserver);
這行代碼報錯叠蝇,這是怎么回事呢璃岳?
進(jìn)入PackageManager的源碼,搜索getPackageSizeInfo()方法,卻發(fā)現(xiàn)這是一個隱藏的方法铃慷!代碼如下:
/**
* @hide
*/
public abstract void getPackageSizeInfo(String packageName, int userHandle,
IPackageStatsObserver observer);
而添加了hide注釋的方法是無法直接別調(diào)用的单芜,那我們該怎么辦呢?這時我們只能采用反射來調(diào)用這個方法了犁柜!
將以下兩行代碼:
PackageManager pm = getPackageManager();
pm.getPackageSizeInfo("com.android.browser", mStatsObserver);
替換為
//反射獲取緩存
try {
Class<?> loadClass = MainActivity.class.getClassLoader().loadClass("android.content.pm.PackageManager");
Method method = loadClass.getDeclaredMethod("getPackageSizeInfo", String.class,IPackageStatsObserver.class);
method.invoke(pm, "com.android.browser",mStatsObserver);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
其中com.android.browser是系統(tǒng)內(nèi)置瀏覽器的包名缓溅。當(dāng)我們要測試的時候只需打開系統(tǒng)瀏覽器,隨意瀏覽幾個網(wǎng)站就有緩存信息了赁温。
現(xiàn)在可以正常獲得緩存信息了坛怪!
我們通過反射的調(diào)用了IPackageStatsObserver,然后再IPackageStatsObserver的構(gòu)造方法中獲取了系統(tǒng)內(nèi)置瀏覽器的緩存大小/應(yīng)用程序大小/數(shù)據(jù)大小等信息股囊。
當(dāng)然了袜匿,還需要添加獲取緩存的權(quán)限,如下:
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
清理緩存
當(dāng)我們正式獲取到了緩存之后我們就可以進(jìn)行清理緩存的工作了稚疹,那么清理緩存要怎么做呢居灯?Android也沒有直接給出方法,大家可以根據(jù)我上面給出的方法自己查找怎樣清理應(yīng)用緩存内狗。
不過我相信很多人都會找到deleteApplicationCacheFiles()這個清理緩存的方法怪嫌,但是這是一個清理單個應(yīng)用緩存的方法,如果想一次清理所有的緩存呢柳沙?
當(dāng)然是有的岩灭,同樣在PackageManager的源碼中,在deleteApplicationCacheFiles()方法下面一個方法赂鲤,freeStorageAndNotify()便是清理所有緩存的方法了噪径。
freeStorageAndNotify()方法的工作原理是向系統(tǒng)申請最大的內(nèi)存使用空間,而系統(tǒng)一旦發(fā)現(xiàn)需要用到最大的內(nèi)存使用量的時候数初,就會自動清理所有的緩存了找爱!
這里還有一個坑,那便是deleteApplicationCacheFiles()和freeStorageAndNotify()方法同樣都是標(biāo)注為hide的泡孩,需要使用反射來調(diào)用车摄。
這里給出調(diào)用freeStorageAndNotify()方法的例子,其中pm是PackageManager的變量
try {
Class<?> loadClass = getActivity().getClass().getClassLoader().loadClass("android.content.pm.PackageManager");
Method method = loadClass.getDeclaredMethod("freeStorageAndNotify", Long.TYPE,IPackageDataObserver.class);
method.invoke(pm, Long.MAX_VALUE,new MyIPackageDataObserver());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
private class MyIPackageDataObserver extends IPackageDataObserver.Stub{
//當(dāng)緩存清理完成之后調(diào)用
@Override
public void onRemoveCompleted(String packageName, boolean succeeded)
throws RemoteException {
}
}
當(dāng)然仑鸥,清理緩存也是需要獲得系統(tǒng)權(quán)限的:
<uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
總結(jié)
相信如果仔細(xì)看完這篇文章吮播,你一定知道該如何尋找到系統(tǒng)源碼中所使用的方法,然后搬到自己的項(xiàng)目中使用了吧锈候!
這里再次給出概要總結(jié):
1薄料、先在系統(tǒng)中尋找到自己實(shí)現(xiàn)的功能,如果你想要的功能系統(tǒng)沒有那就只好自己造輪子了泵琳。
2摄职、先從xml入手誊役,在eclipse中使用Ctrl + H 快捷鍵打開搜索欄。先在string.xml中到到文字資源名
3谷市、使用資源名字繼續(xù)搜索xml文件蛔垢,找到相應(yīng)布局,獲得空間的資源ID
4迫悠、通過資源ID搜索java文檔鹏漆,找到在那個類中被引用
5、一步一步跟進(jìn)创泄,獲得實(shí)現(xiàn)的代碼
項(xiàng)目Demo地址:
https://github.com/liaozhoubei/EndCallAndClearCacheDemo
Demo項(xiàng)目以獲取全部緩存和清理全部緩存為主