SharedPreference是Android上一种非常易用的轻量级存储方式,由于其API及其友好,得到了很多很多开发者的青睐。但是,SharedPreference并不是万能的,如果把它用在不合适的使用场景,那么将会带来灾难性的后果;本文将讲述一些SharedPreference的使用误区。

存储超大的value

第一次看到下面这个sp的时候,我的内心是崩溃的:

一个默认的sp有90K,当我打开它的时候,我都快哭了:除了零零星星的几个很小的key之外,存储了一个炒鸡大的key,这一个key至少占了其中的89K。知道这是什么概念吗?

在小米1S这种手机上,就算获取这个sp里面一个很小的key,会花费120+ms!!那个毫不相干的key拖慢了其他所有key的读取速度!当然,在性能稍好的手机上,这个问题不是特别严重。但是要知道,120ms这个是完全不能忍的!

之所以说SharedPreference(下文简称sp)是一种轻量级的存储方式,是它的设计所决定的:sp在创建的时候会把整个文件全部加载进内存,如果你的sp文件比较大,那么会带来两个严重问题:

  1. 第一次从sp中获取值的时候,有可能阻塞主线程,使界面卡顿、掉帧。
  2. 解析sp的时候会产生大量的临时对象,导致频繁GC,引起界面卡顿。
  3. 这些key和value会永远存在于内存之中,占用大量内存。
阅读全文 »

目前为止我们已经完成了Android四大组件中Activity,Service以及BroadcastReceiver的插件化,这几个组件各不相同,我们根据它们的特点定制了不同的插件化方案;那么对于ContentProvider,它又有什么特点?应该如何实现它的插件化?

与Activity,BroadcastReceiver等频繁被使用的组件不同,我们接触和使用ContentProvider的机会要少得多;但是,ContentProvider这个组件对于Android系统有着特别重要的作用——作为一种极其方便的数据共享的手段,ContentProvider使得广大第三方App能够在壁垒森严的系统中自由呼吸。

在Android系统中,每一个应用程序都有自己的用户ID,而每一个应用程序所创建的文件的读写权限都是只赋予给自己所属的用户,这就限制了应用程序之间相互读写数据的操作。应用程序之间如果希望能够进行交互,只能采取跨进程通信的方式;Binder机制能够满足一般的IPC需求,但是如果应用程序之间需要共享大量数据,单纯使用Binder是很难办到的——我相信大家对于Binder 1M缓冲区以及TransactionTooLargeException一定不陌生;ContentProvider使用了匿名共享内存(Ashmem)机制完成数据共享,因此它可以很方便地完成大量数据的传输。Android系统的短信,联系人,相册,媒体库等等一系列的基础功能都依赖与ContentProvider,它的重要性可见一斑。

既然ContentProvider的核心特性是数据共享,那么要实现它的插件化,必须能让插件能够把它的ContentProvider共享给系统——如果不能「provide content」那还叫什么ContentProvider?

但是,如果回想一下Activity等组件的插件化方式,在涉及到「共享」这个问题上,一直没有较好的解决方案:

阅读全文 »

Linus有一句名言广为人知:Read the fucking source code. 但其实,要深入理解某个软件、框架或者系统的工作原理,仅仅「看」代码是远远不够的。就拿Android Framework来说,整个代码量非常大不说,那些个动辄几万行的类如何去理解?所以我今天要说的就是:

Debug the fucking source code!!

之前分享过一个答案:大家遇到过什么 Android 兼容性问题?,这里面的有一些非常诡异的问题,我相信光靠看代码你是永远定位不出来的。还有我写的一系列Android插件框架原理的文章,这里面涉及到大量Android Framework层的知识,有小伙伴会问,这些Framework层的原理,你是如何学习的呢,有诀窍吗?有!那就是调试。

Debug是一项非常非常重要的技能,毋庸多言。今天我就给大家分享一下「调试Android Framework」的经验,一旦掌握这项技能,那么Java层的任何问题都拦不住你了。

阅读全文 »

Activity生命周期管理 以及 广播的管理 中我们详细探讨了Android系统中的Activity、BroadcastReceiver组件的工作原理以及它们的插件化方案,相信读者已经对Android Framework和插件化技术有了一定的了解;本文将探讨Android四大组件之一——Service组件的插件化方式。

与Activity, BroadcastReceiver相比,Service组件的不同点在哪里呢?我们能否用与之相同的方式实现Service的插件化?如果不行,它们的差别在哪里,应该如何实现Service的插件化?

我们接下来将围绕这几个问题展开,最终给出Service组件的插件化方式;阅读本文之前,可以先clone一份 understand-plugin-framework,参考此项目的 service-management 模块。另外,插件框架原理解析系列文章见索引

阅读全文 »

Activity生命周期管理 以及 插件加载机制 中我们详细讲述了插件化过程中对于Activity组件的处理方式,为了实现Activity的插件化我们付出了相当多的努力;那么Android系统的其他组件,比如BroadcastReceiver,Service还有ContentProvider,它们又该如何处理呢?

相比Activity,BroadcastReceiver要简单很多——广播的生命周期相当简单;如果希望插件能够支持广播,这意味着什么?

回想一下我们日常开发的时候是如何使用BroadcastReceiver的:注册, 发送接收;因此,要实现BroadcastReceiver的插件化就这三种操作提供支持;接下来我们将一步步完成这个过程。

阅读本文之前,可以先clone一份 understand-plugin-framework,参考此项目的receiver-management 模块。另外,插件框架原理解析系列文章见索引

阅读全文 »

上文 Activity生命周期管理 中我们地完成了『启动没有在AndroidManifest.xml中显式声明的Activity』的任务;通过Hook AMS和拦截ActivityThread中H类对于组件调度我们成功地绕过了AndroidMAnifest.xml的限制。

但是我们启动的『没有在AndroidManifet.xml中显式声明』的Activity和宿主程序存在于同一个Apk中;通常情况下,插件均以独立的文件存在甚至通过网络获取,这时候插件中的Activity能否成功启动呢?

要启动Activity组件肯定先要创建对应的Activity类的对象,从上文 Activity生命周期管理 知道,创建Activity类对象的过程如下:

1
2
3
4
5
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);

也就是说,系统通过ClassLoader加载了需要的Activity类并通过反射调用构造函数创建出了Activity对象。如果Activity组件存在于独立于宿主程序的文件之中,系统的ClassLoader怎么知道去哪里加载呢?因此,如果不做额外的处理,插件中的Activity对象甚至都没有办法创建出来,谈何启动?

因此,要使存在于独立文件或者网络中的插件被成功启动,首先就需要解决这个插件类加载的问题。
下文将围绕此问题展开,完成『启动没有在AndroidManifest.xml中显示声明,并且存在于外部插件中的Activity』的任务。

阅读本文之前,可以先clone一份 understand-plugin-framework,参考此项目的classloader-hook 模块。另外,插件框架原理解析系列文章见索引

阅读全文 »

之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在Android系统上运行起来?

在Java平台要做到动态运行模块、热插拔可以使用ClassLoader技术进行动态类加载,比如广泛使用的OSGi技术。在Android上当然也可以使用动态加载技术,但是仅仅把类加载进来就足够了吗?ActivityService等组件是有生命周期的,它们统一由系统服务AMS管理;使用ClassLoader可以从插件中创建Activity对象,但是,一个没有生命周期的Activity对象有什么用?所以在Android系统上,仅仅完成动态类加载是不够的;我们需要想办法把我们加载进来的Activity等组件交给系统管理,让AMS赋予组件生命周期;这样才算是一个有血有肉的完善的插件化方案。

接下来的系列文章会讲述 DroidPlugin对于Android四大组件的处理方式,我们且看它如何采用Hook技术坑蒙拐骗把系统玩弄于股掌之中,最终赋予Activity,Service等组件生命周期,完成借尸还魂的。

阅读全文 »

在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式Binder Hook;插件框架通过AOP实现了插件使用和开发的透明性。在讲述DroidPlugin如何实现四大组件的插件化之前,有必要说明一下它对ActivityManagerServiche以及PackageManagerService的Hook方式(以下简称AMS,PMS)。

ActivityManagerService对于FrameWork层的重要性不言而喻,Android的四大组件无一不与它打交道:

  1. startActivity最终调用了AMS的startActivity系列方法,实现了Activity的启动;Activity的生命周期回调,也在AMS中完成;
  2. startService,bindService最终调用到AMS的startService和bindService方法;
  3. 动态广播的注册和接收在AMS中完成(静态广播在PMS中完成)
  4. getContentResolver最终从AMSgetContentProvider获取到ContentProvider

PMS则完成了诸如权限校捡(checkPermission,checkUidPermission),Apk meta信息获取(getApplicationInfo等),四大组件信息获取(query系列方法)等重要功能。

在上文Android插件化原理解析——Hook机制之Binder Hook中讲述了DroidPlugin的Binder Hook机制;我们知道AMSPMS就是以Binder方式提供给应用程序使用的系统服务,理论上我们也可以采用这种方式Hook掉它们。但是由于这两者使用得如此频繁,Framework给他们了一些“特别优待”,这也给了我们相对于Binder Hook更加稳定可靠的hook方式。

阅读全文 »

Android系统通过Binder机制给应用程序提供了一系列的系统服务,诸如ActivityManagerServiceClipboardManagerAudioManager等;这些广泛存在系统服务给应用程序提供了诸如任务管理,音频,视频等异常强大的功能。

插件框架作为各个插件的管理者,为了使得插件能够无缝地使用这些系统服务,自然会对这些系统服务做出一定的改造(Hook),使得插件的开发和使用更加方便,从而大大降低插件的开发和维护成本。比如,Hook住ActivityManagerService可以让插件无缝地使用startActivity方法而不是使用特定的方式(比如that语法)来启动插件或者主程序的任意界面。

我们把这种Hook系统服务的机制称之为Binder Hook,因为本质上这些服务提供者都是存在于系统各个进程的Binder对象。因此,要理解接下来的内容必须了解Android的Binder机制,可以参考我之前的文章Binder学习指南

阅读全文 »

Markdown这种格式的出现大大提升了写作的效率,但是它对于非英文的用户其实并不友好:每当我们需要使用#[-等标志符的时候,需要不断地切换输入法。

首先,切换输入法(就算是按shift键)让我们的思维不连贯;其次,一旦中间有一次切换出错,那么又有撤销的成本;我相信每一个非英文markdown的使用者都有这种困惑;实际想要达到的效果如下:

效果图

阅读全文 »