0%

最近在思考做一个Android架构设计的分享,主要目的是通过回顾架构设计的演进过程,帮助我们审查和改进当前工程设计中的一些缺陷。

说起Android架构设计,马上就会有熟悉的几个词蹦出来,MVC, MVP, MVVM, 这些架构设计的内涵是什么?有优劣之分吗?除了它们,还有哪些架构设计模式?

为了回答上面的问题,我从自己的项目架构演进来分别说明一下各种架构设计的特点。我们今天讨论的都是构建用户交互应用程序的架构设计,是细粒度功能模块代码组织与划分,组件化,插件化等工程模块的拆分不在讨论范围之内。

Read more »

在分析Android Architecture Component这个框架之前,先想想这个框架能为我们做什么,我们为什么要按照这套框架模式去构建app?它和MVP架构有什么区别?

MVP, Clean, AAC架构的特点:
MVP架构的特点是面向接口编程。在View, Presenter, Model之间分别用接口做衔接,当有新的底层实现时,能够无缝替换。

此外,MVP的View和Model并不产生依赖,因此可以说是对View和Model做了解耦。

但MVP架构有其局限性。MVP需要创建太多的类和接口,并且每次通信都需要繁琐的通过接口传递信息

google sample提供了一个todo-mvp sample, 里面有MVP+RxJava实现,Clean架构实现。这两种架构方式我都实践过,MVP+RxJava所有业务,UI逻辑都包含在Presenter里面, Presenter直接干预了UI在拿到数据后做什么,使得逻辑上没有发生解耦,正常来说,解耦意味着Presenter的只能边界仅限返回结果数据,由UI来根据数据处理UI逻辑。

Read more »

SparseArray映射<Integer, Object>键值对。区别于对象数组,索引可以是非连续的。与使用HashMap映射Integer类型的key相比,使用SpaseArray存储效率更高,一方面它避免了自动对int型键自动装箱,另一方面它的数据结构不依赖额外的Map.Entry对象。

注意到这个容器使用数组保持映射关系,使用二分搜索寻找键。这种实现不适合存储大量数据项。与传统的HashMap比起来要慢,因为使用二分搜索查找,增加和移除操作需要在数组中插入和删除元素。存储上百条数据,性能差距不明显,不到50%.

Read more »

一个在文件系统上使用确定数量空间的缓存,每个cache entry包含一个字符串key和一个固定数字值。每个key必须匹配正则表达式[a-z0-9_-]{1,64}. 值表示为字节序列,可以通过流或者文件访问。每个值的长度在0Integer.MAX_VALUE之间。

缓存将数据存储在文件系统的一个文件夹中。缓存对这个文件夹必须是独占的,缓存可以删除或者覆盖文件夹中的文件。多个进程在同一时间使用同一个缓存会导致错误。

Read more »

1. Volley如何做内存优化的?

有两个很有意思的类ByteArrayPoolPoolingByteArrayOutputStream, 当从网络取得响应数据流后,将响应数据转换为字节数组存储到NetworkResponse中。Volley适合数据量小,通信频繁的网络操作,如果按传统方式申请内存则分配内存,然后等待垃圾收集器回收,必然会造成频繁GC, 导致内存抖动,性能下降。

Read more »

为什么使用轻量级的库?

通常我会选择一些成熟的、功能丰富的框架,而这些框架通常是一些重量级的库,也许很多功能你根本用不到,而这些需求之外的功能直接导致了代码体积的增长。也许使用Proguard能移除没有使用的代码,但是类之间如果存在复杂的依赖耦合关系,Proguard还能起到多大作用,Who knows. 有人担心以后可能需要用到那些功能,现在的库没法满足需求. OK, 在使用这个库的时候了解是如何设计和实现的,优秀的库都是对扩展开放的,面向接口编程,那么需要新功能的话就扩展它吧,学习其他人造轮子也会提高自己的编程能力。

曾经一个朋友拿着他们公司大约200kb的类似小米游戏下载软件给我看,我简直无法相信,现在一个功能非常简单的apk动不动就是几兆,能做到这个程度,几乎让人怀疑这个软件的是否专业,然而它在低端机器上表现得都非常流畅。我们费劲心思琢磨如何给apk瘦身,结果增加几个大的jar包就打回了原形,我们要增加第三方统计,友盟的百度的都加上,再加上第三方登录,推送,这些sdk再引用了大量的库比如http组件,json解析,安装包一下子膨胀了几兆,难受但是无奈。

一个图片加载框架的加载流程

1. CubeImageView会检查图片是否已经加载过,图片已经加载完成并已经在显示的重复请求直接忽略。
2. 检查图片是否在内存缓存中,如果再内存中,显示内存中的图片。否则,创建一个ImageTask, 传递给 ImageLoader, ImageTaskExecutor会处理这个任务。
3. ImageTaskExecutor 会用后台线程处理这个ImageTask.
4. ImageProvider负责获取图片,如果图片在本地有文件缓存,那么直接从本地文件加载;否则从网络下载,存文件缓存。
5. 获取到图片之后,存内存缓存,通知加载完成。收到加载完成通知后,ImageView就可以显示图片了。

根据这个加载流程,从核心类入手,逐个分析实现这样一个图片加载框架,我们关注的一些细节问题。

Read more »

什么是REST?

含状态传输(英文:Representational State Transfer,简称REST)是一种软件架构风格。需要注意的是,REST是设计风格而不是标准。REST通常基于使用HTTP,URI,和XML以及HTML这些现有的广泛流行的协议和标准。

  • 资源是由URI来指定。
  • 对资源的操作包括获取、创建、修改和删除资源,这些操作正好对应HTTP协议提供的GET、POST、PUT和DELETE方法。
  • 通过操作资源的表现形式来操作资源。
  • 资源的表现形式则是XML或者HTML,取决于读者是机器还是人,是消费web服务的客户软件还是web浏览器。当然也可以是任何其他的格式

REST绝不是只和HTTP相联系.你可以使用任意一种传输协议,但HTTP在REST风格架构中无疑是使用最广泛的。如果我要连接的服务已经有一个适配手机端的web网站,为何我还要开发一个应用呢?换句话说,为什么我们不使用浏览器,一步到位?

为什么使用REST?

好吧,除非浏览器技术发展起来了。我要列举5大原因。

  • 第一个原因是android应用是集成在android platform中,可以使用intents, content provider等等,浏览器做不到这些。
  • 正是android平台为android应用提供了一些新功能,你的应用可以为其他应用提供intents.
  • 你的应用可以在后台运行。这意味着如果要从服务器更新数据,当从服务器取得数据后,应用可以通知用户,让他们知道有数据可用。
  • 应用和网络的连接稳定性有限。当网络较差时,请求可能失败。而android应用可以在后台进行多次重试,让用户在使用浏览器多次按刷按钮的痛苦中解放出来。
  • 应用可以将从网络中取得资源,比如JSON, 或者二进制,或者XML格式的内容解析并存储在数据库中。因此,当你想取得新数据时,你就可以选择获取比数据库中数据更新的或者更老旧的数据,而不是和数据库中缓存相同的数据。这样你就不用获得所有html,就不用花长时间去下载javascript.
  • 最后,你的应用可以和android操作系统在使用上保持一致性。
    Read more »

#Memory Leak

考虑下面的代码块:

1
2
3
4
5
6
7
8
9
public class SampleActivity extends Activity {

private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
}

也许不是那么明显,这块代码能造成大量的内存溢出。Android Lint 会提示如下警告

In Android, Handler classes should be static or leaks might occur.

但是内存是在哪儿溢出的,是如何发生的?首先通过列出我们已经知道的事实来找问题的根源:

1.当Android应用程序第一次启动时,framework在应用程序的主线程里创建了一个Looper对象。Looper实现了一个简单的消息队列,依次处理队列里的消息对象。所有的应用程序框架里的事件(比如Activity的生命周期函数调用,按钮点击事件等等)都封装为消息对象,被加入到Looper的消息队列中并依次处理。主线程的Looper在整个应用的生命周期里会一直存在。

2.当在主线程里实例化一个Handler时,就和Looper的消息队列关联在一起了。发送到消息队列的消息会持有Handler的引用,使得当Looper最后处理消息时候,framework可以调用Handler#handleMessage(Message).

3.在Java中,非静态匿名内部类会持有它所在外部类的隐式引用。而静态内部类不会。

Read more »

Loader Framework

如果在应用里通过ContentProvider取得数据,最糟的情况可能有一下几种:

  1. 你完全不知道android应用该怎么写,在主线程里去执行耗时操作
  2. 使用AsyncTask:
    你的app在屏幕方向改变的时候崩溃了,于是你google到stackoverflow上的答案并锁定了屏幕方向,接着你加上了容易出错的代码,在Activity中detach,attach AsyncTask.
  3. 使用CursorLoader
    从android HoneyComb到Kitkat,Loader Framework中的Loader子类都只有孤零零的CusorLoader.很可能你会使用其他的异步操作,而不仅仅是通过ContentProvider.你也许想要访问SharedPreferences,读文件或者请求访问web API.那样的话,你需要Loader,但正确实现它会有点复杂。
    我将带你完整过一遍整个流程,理解Loaders是如何工作的,为你的Loaders实现一个正确的基类,实现一个修复所有问题的CursorLoader,并扩展使其具备通知多个Uri的能力。这将是一篇长博客,所以拿一杯你喜欢的饮料边看边喝吧。
    Read more »

使用 Github+Octopress 搭建博客

1.对git不陌生

2.创建github页面。注意:

Github Pages for users and organizations uses the master branch like the public directory on a web server, serving up the files at your Pages url http://username.github.io. As a result, you’ll want to work on the source for your blog in the source branch and commit the generated content to the master branch. Octopress has a configuration task that helps you set all this up.

比如我的github用户名是taoliuh,那么创建一个github page 一定要设置仓库名称为taoliuh.github.io

3.绑定独立域名

我在godaddy上注册的域名。在域名管理页面添加ANAME: @204.232.175.78 CNAME: http your-custom-domain

4.发表博客

rake new_post["Hello World: The First of Many New Blog Posts"]

5.预览

rake preview

6.发布

当完成写作时,你需要生成并发布到github.

rake generate
rake deploy

不要忘了提交改动的文件到source分支

git add . 
git commit -am 'Add my first blog post' 
git push origin source

现在你就能在这个页面user-name.github.io看到你的博客文章了~LOL

Read more »