项目Demo地址:
https://github.com/liaozhoubei/EndCallAndClearCacheDemo
Demo项目以获取全部缓存和清理全部缓存为主
代码界中有句话:不要重复造轮子!
意思就是如果你发现你想要实现的功能已经有人做出来了,而且是开源的,那就不要浪费时间了,直接拿过来用吧!
当然这句话对于想要提高技术的人来说不适应。但是对于赶进度的码农来说确实是适用的。
可惜对于很多Android新手来说想要把轮子拿过来改一改都不会,现在我们就来学习如何在Android源码这个轮子之中找到自己想要的功能并且搬过来吧。
所需文件:Android上层源码(直接用搜索引擎搜,能找到一堆)
源码下载:http://pan.baidu.com/s/1eRKpOOy
Android上层源码,即packages的源码并不大,也就30多m。
工具准备好之后,我们就开始本次的任务吧。
实现清理手机缓存的功能
现在我们想要获取手机缓存,然后清理手机缓存。
但是我们不知道怎样获取手机缓存呀,也不知道怎么清理缓存,甚至没怎么接触过,这可如何是好!
找到功能在哪里实现过
且慢,我们先想想曾经在哪接触过它。没错,对于手机达人的我们来说是经常会接触到这个选项的,当我们打开手机设置-apps,随意选择一个app进入app详细页面的时候我们就会发现这么一个Cache的文字以及Clear cache的按钮,Cache的右边显示的是当前这个app的缓存大小,而我们点击Clear cache的时候缓存就会清零。这不正是我们要实现的功能么?
导入实现功能的项目
之后这个功能在哪里实现过之后,应该怎样找到实现这个功能的地方呢?
别着急,我们打开刚才下载的《Android上层源码》,在里面搜索,发现里面有个Settings的项目文件夹!那么Settings是不是代表着手机中的setting这个选项?我们的清理缓存的功能是不是就在里面?
话不多说,直接在开发工具中导入Settings项目。
找到具体实现的代码(重点)
现在问题又来了,工程已经导入了,源代码也可以查看了,可是一个package动不动就七八个类文件,直接一个一个文件查看太浪费时间和精力了,那么我们怎么找到我们需要的代码呢?
这时我们就要充分利用好开发工具了,非常感谢制作出开发工具的大神们,让我们开发省了多少时间。
我们再次查看刚才的App Info页面,仔细查看清理缓存这一部分。看到Cache文字和Clear cache按键,似乎并没什么特别的。
且慢,再仔细想想。Android之中如果想调用文字,不是要用android:text="@string/xxxx"这样的形式么。那么我们就能够先找到Cache单词的引用,然后顺藤摸瓜的直接找到使用文字的代码,既然引用了文字,一定与清理缓存有关系的。
注:Android是不建议使用硬编码即android:text="我是硬编码"直接使用文字的。
顺藤摸瓜找代码
在eclipse中直接使用Ctrl+H快捷键,打开搜索框选择File Search,然后在其中的Containing text中填写要搜索的文字,然后在File name patterns中填入要搜索哪些文件。
我们首先要找到这个文字的资源文件id,然后通过资源文件id找到在哪里引用。
- 首先在Containing text填入在布局页面中展现的文字Cache,然后这个文字是出现在xml布局页面中,所以File name patterns中填入*.xml,意思是我们要搜索所有的xml页面中包含Cache文字的地方
然后在结果页面中打开res-value-strings,发现将所有包含Cache文字的行都列了出来。我们发现有两个地方直接使用了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,翻译成中文不就是缓存标题栏和缓存大小栏么。
- 重复上面的方法,直接搜索cache_size_label,这次在搜索结果中显示installed_app_details.xml这个布局页面使用了。
打开布局页面,直接查看布局预览,这次我们终于找到了App Info的布局页面了!如下图
找到布局之后,查看布局中的id名,发现TextView中android:id="@+id/cache_size_text"这一行就是我们所需要的放置缓存大小的文字,我们自己通过这个id寻找在哪里引用
再次打开搜索框,Containing text填入:cache_size_text,File name patterns中填入*.java,显示以下结果
在打开InstalledAppDetails.java之后,发现其中的322行
mCacheSize = (TextView) findViewById(R.id.cache_size_text);
这毫无疑问就是设置缓存大小的地方了。
那么mCacheSize的文字又是从哪里来的呢?
继续在InstalledAppDetails.java寻找,发现:
mCacheSize.setText(getSizeStr(mAppEntry.cacheSize));
设置了缓存的大小,那么它是怎么获得缓存大小的呢?
分析getSizeStr(mAppEntry.cacheSize)),发现getSizeStr()这个方法只是纯粹用来格式化缓存的,也就是将b转换为kb,kb转换为mb而已,在InstalledAppDetails可以找到它的源码:
private String getSizeStr(long size) {
if (size == SIZE_INVALID) {
return mInvalidSizeStr.toString();
}
return Formatter.formatFileSize(this, size);
}
这样一来就更清晰明了了,mAppEntry.cacheSize就是缓存的大小,我们继续探索它的数值是如何获取出来的。
Ctrl + 左键点击cacheSize,我们就进入了ApplicationsState的源码,然后又惊奇的发现mAppEntry是AppEntry的实例,而AppEntry则是ApplicationsState的静态内部类。现在看来一切的答案都能在ApplicationsState这个类中寻找。
在ApplicationsState中仔细搜索cacheSize,我们发现在以下方法中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;
}
}
·······
}
};
细细研究entry.cacheSize,发现entry也是AppEntry的变量,那么无疑mAppEntry.cacheSize就是entry.cacheSize,毫无疑问我们得出了结论,这个就是内存大小了。
但是在进一步研究entry.cacheSize = stats.cacheSize,我们无法从stats.cacheSize这个方法中直接得到内存大小,因为在跟踪stats.cacheSize方法进入PackageStats类,继续跟踪的时候,发现它是依靠于底层用C语言实现的。
既然我们无法直接获得PackageStats这个类的方法,那么我们回过头来研究
final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub()
它创建了一个实例,并且被赋予final,一般情况下被赋予final的值都会在内部被使用,一旦能够被正常使用,那么就能够直接获得PackageStats的默认变量,然后使用stats.cacheSize获得缓存大小了。
通过搜索mStatsObserver的使用情况,发现了这两行相关代码:
mCurComputingSizePkg = entry.info.packageName;
mPm.getPackageSizeInfo(mCurComputingSizePkg, mStatsObserver);
到了这一步一切都差不多了,其中的entry.info.packageName所获得的是包名,而mPm则是PackageManager的变量。
创建工程获取缓存
我们直接创建一个工程项目来获取缓存,在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;//应用程序的大小
long datasize = stats.dataSize;//数据大小
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包,无法导入。这是因为IPackageStatsObserver是一个aidl类,所以我们从外部直接获取到了IPackageStatsObserver.aidl,然后再src目录下创建IPackageStatsObserver的包名,将IPackageStatsObserver.aidl粘贴进去。这时IPackageStatsObserver类便不会报错了。
但是又有个问题
pm.getPackageSizeInfo("com.example.writecache", mStatsObserver);
这行代码报错,这是怎么回事呢?
进入PackageManager的源码,搜索getPackageSizeInfo()方法,却发现这是一个隐藏的方法!代码如下:
/**
* @hide
*/
public abstract void getPackageSizeInfo(String packageName, int userHandle,
IPackageStatsObserver observer);
而添加了hide注释的方法是无法直接别调用的,那我们该怎么办呢?这时我们只能采用反射来调用这个方法了!
将以下两行代码:
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是系统内置浏览器的包名。当我们要测试的时候只需打开系统浏览器,随意浏览几个网站就有缓存信息了。
现在可以正常获得缓存信息了!
我们通过反射的调用了IPackageStatsObserver,然后再IPackageStatsObserver的构造方法中获取了系统内置浏览器的缓存大小/应用程序大小/数据大小等信息。
当然了,还需要添加获取缓存的权限,如下:
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
清理缓存
当我们正式获取到了缓存之后我们就可以进行清理缓存的工作了,那么清理缓存要怎么做呢?Android也没有直接给出方法,大家可以根据我上面给出的方法自己查找怎样清理应用缓存。
不过我相信很多人都会找到deleteApplicationCacheFiles()这个清理缓存的方法,但是这是一个清理单个应用缓存的方法,如果想一次清理所有的缓存呢?
当然是有的,同样在PackageManager的源码中,在deleteApplicationCacheFiles()方法下面一个方法,freeStorageAndNotify()便是清理所有缓存的方法了。
freeStorageAndNotify()方法的工作原理是向系统申请最大的内存使用空间,而系统一旦发现需要用到最大的内存使用量的时候,就会自动清理所有的缓存了!
这里还有一个坑,那便是deleteApplicationCacheFiles()和freeStorageAndNotify()方法同样都是标注为hide的,需要使用反射来调用。
这里给出调用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{
//当缓存清理完成之后调用
@Override
public void onRemoveCompleted(String packageName, boolean succeeded)
throws RemoteException {
}
}
当然,清理缓存也是需要获得系统权限的:
<uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
总结
相信如果仔细看完这篇文章,你一定知道该如何寻找到系统源码中所使用的方法,然后搬到自己的项目中使用了吧!
这里再次给出概要总结:
1、先在系统中寻找到自己实现的功能,如果你想要的功能系统没有那就只好自己造轮子了。
2、先从xml入手,在eclipse中使用Ctrl + H 快捷键打开搜索栏。先在string.xml中到到文字资源名
3、使用资源名字继续搜索xml文件,找到相应布局,获得空间的资源ID
4、通过资源ID搜索java文档,找到在那个类中被引用
5、一步一步跟进,获得实现的代码
项目Demo地址:
https://github.com/liaozhoubei/EndCallAndClearCacheDemo
Demo项目以获取全部缓存和清理全部缓存为主