Android之教你如何搬运系统源码

项目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的时候缓存就会清零。这不正是我们要实现的功能么?

app详情页.png

导入实现功能的项目

之后这个功能在哪里实现过之后,应该怎样找到实现这个功能的地方呢?

别着急,我们打开刚才下载的《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找到在哪里引用。

搜索.png
  • 首先在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这个布局页面使用了。
layout.png

打开布局页面,直接查看布局预览,这次我们终于找到了App Info的布局页面了!如下图

layout1.png
  • 找到布局之后,查看布局中的id名,发现TextView中android:id="@+id/cache_size_text"这一行就是我们所需要的放置缓存大小的文字,我们自己通过这个id寻找在哪里引用

  • 再次打开搜索框,Containing text填入:cache_size_text,File name patterns中填入*.java,显示以下结果

cache-size.png

在打开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项目以获取全部缓存和清理全部缓存为主

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,263评论 25 707
  • 喧嚣的城市,匆忙的行人,每条街道上都有赶时间的上班族,朝着城市另一端的某座建筑,迈着急促的脚步,就如郝云在歌曲中所...
    白企阅读 356评论 0 2
  • 在中国古代神话传说中,哪吒是一个地位崇高的神,在道教中,拥有很高的称号,诸如中坛元帅、通天太师、威灵显赫大将军、三...
    虫子天下阅读 6,149评论 0 3
  • 春分已经过了两天,这个春天已经走了一半。依然过半的春天里,似乎和往常的春天不太一样。 因为疏散非首都功能...
    郑子衿阅读 529评论 3 11
  • 不久前在翻阅空间动态时,一个动态让我的心久久不能平静。又一个小生命被河水带走了。 偶然想起自己曾在假期的时候,做了...
    木东水阅读 718评论 0 0