Google Develop for Android 系列七 - Framework最佳实践

原文链接


避免选择应用组件搭建架构

应用组件(Activities, Services, Providers, Receivers)是你与操作系统交互的接口但是不要将它们看作是搭建整个应用架构的基础工具。每一个组件在系统中有自己特定语境,通常也只应该有需要的时候才使用:

  • Activity 应用顶级的UI实体。它相当于传统操作系统中的main函数 (当用户点击应用icon的时候运行)。当你希望其它应用启动你应用的特定部分的时候才应该使用Activity来实现它。比如执行一个分享的操作或者打 开应用中的一些内容时。如果你只是从自己的应用中去获取一个UI的时候,没有必要通过Activity来实现它,你也可以只转变当前UI的状态(比如通过 fragments)。在fragments出现之前一个应用的架构可能通过多个activities来实现是比较常见的,但是现在已经是不必要的了,除 非你需要特定的设计。
  • Service 一个用于在Activity UI 之外执行一个长时间的操作的服务。它可以自启动(通过Context.startService())或者运行在另一个进程中 (Context.bindService()),如果这些行为你都不需要,那么你就不应该使用Service。

比如你需要后台工作但是不需要自启动(下载UI需要的内容,可以在用户重新回到UI的时候resume),你应该使用本地线程的原始类,比如: AsyncTask, Loader, HandlerThread, 等等。service是资源敏感的(需要作为全局状态的一部分被一直追踪),而且当你的应用不需要service的时候它还在后台运行,可能导致一些 bug(这是Android中常见的问题,而且对系统是有害的)。如果你需要将同一进程的代码关联起来,不要使用bindSerice(),使用简单的 callbacks和其它工具就可以。因为它们更容易编码也更容易理解,并且资源耗费更少。当然你要理解AsyncTask的异步特性,当 Activity finish掉之后结果才返回的情况也是有可能的。在使用结果时请check Activity的状态。

-Broadcast Receiver 关注感兴趣的特定事件,在事件发生的时候会自动唤起 。

Services要么被绑定要么自启动

Google Develop for Android 系列六 - Storage最佳实践

作者 : lightsky
原文链接


避免文件路径的硬编码

尽量从上context或者Environment中获取

  • 不要硬编码全局的路径”/sdcard”,使用Environment.getExternalStorageDirectory() 或者相关的方法替代
  • 不要硬编码应用路径: “/data/data/myapp/databases”, 使用 Context.getDatabasePath(), Context.getFilesDir()或者相关的方法替代

只持久化相对路径

当你需要持久化某个路径的时候,为了防止路径的变化,你应该使用相对路径。比如你的应用的备份需要恢复到一个新的设备上,数据路径就可能有些不同。

比如通过Context.getFilesDir() 方法返回的路径在不同的设备,用户或者配置时发生变化。因此在运行期间只通过相对路径构造绝对路径是最安全的。

避免过重的标准化以免出现特定的安全情况。

对临时文件使用缓存存储

Google Develop for Android 系列五 - Language and Libraries

作者 : lightsky
原文链接


使用Android自身适当的数据结构

出于对内存分配的考虑,传统的集合类在Android上可能不是最优选。Android特地提供的一些集合类型在很多情况下更合适,比如 ArrayMap(好过HashMap),SparseArray。当集合非常大的时候,那些一般的集合仍然是合适的。而较小的集合则受益于自动装箱和内 存分配的减少。

Serialization

Parcelable

Parcelable是Andorid的IPC序列化格式,或者说它是一种可以通过Binder传递数据的接口,它有以下限制

  • 将Parcels写入到硬盘中是不安全的
  • 你可以实现自己的Parcelables,但是如果在unparceling(Parcel反序列化)时不能访问到相同到类,那么就会unparcel就失败(对于向framework传递Parcels也适用)。
  • 一些对象被存入到Parcels而不是共享内存中的情况,比如文件描述器,也许是很好的性能优化,但是隐藏了该Parcel对象真实的内存耗费(直到该对象被unparceling反序列化后才会占用真实的内存)。

持久化的Bundles

从API21开始,有一个新的PersistableBundle类,该类型的Bundle持有一个数据表支持XML格式数据的序列化。它接收的数据类型只能是Bundle所支持的子类。特别的,它不支持Parcelable对象。

当处理一些需要通过Binder IPC传递的数据时,PersistableBundle类非常有用。

避免Java序列化

Google Develop for Android 系列四 - Network最佳实践

作者 : lightsky
原文链接


不要过度同步

向云端发送数据和获取数据是非常耗电的行为之一。不是网络传输会将设备搞挂,而是一定量的后台应用向服务发起的这些请求会导致设备不能进入睡眠状态 (或者对于收音机的低电量模式),一定时间后会导致严重的电量的流失。如果你不需要立即获取数据,就不要获取。如果将来需要获取数据,那么可以使用JobScheduler或者GCM Network Manager将它们和一些系统的请求一起处理。
下面是一些避免过度请求的一些建议:

  • 使用 GoogleCloudMessaging(GCM)。而不是使用新的持久化连接。
  • 使用JobScheduler(API 21以及之后)或者GCM NetrowManager将一些异步请求绑在一起进行批处理,这些API可以保证操作只在设备恰当的闲暇状态下进行
  • Do not poll.Ever.(不太理解啥意思)
  • 只同步你需要的数据。数据同步已经被认为是电池和总体系统健康的罪魁祸首之一。因此开发者需要谨慎的选择哪些是真正需要同步的数据,以及多久同步一次,这些都可以让用户有更好的体验。

避免过度加载服务

当服务请求失败,应该使用备用的技术避免请求一直,重复的请求。Also, never synchronize against wall-clock time to avoid problems with the server being hit by large loads at these absolute times. 不是太明白啥意思, “当然,从不同步以至于一次性进行大量加载的方式去避免这些问题也是不好的?”

不要对网络想当然

进行网络调用前,确保使用NetworkInfo.isConnected()进行检测。

网络请求不确定性很大,比较耗时,因此另一个建议是不要在UI 线程或者其它需要立即同步的行为中进行网络的请求。

Google Develop for Android 系列三 - Performance最佳实践

作者 : lightsky
原文链接


在Android中,性能和内存的关系很密切,因为系统的整体内存大小会影响所有进程的性能,因为垃圾回收器会对运行期间的性能产生很大的影响。下面的重点是运行期间的性能问题而不是内存。

避免在动画和交互期间繁重的操作

正如在第一篇文章中提到过的,在UI Thread做繁重的操作会影响到渲染的处理。同样会导致动画的问题,因为它依赖于每一帧的渲染。这就意味着在动画期间避免在UI进行繁重的操作就更加重要。以下是一些可以避免的常见情况:

  • Layout
    Measurement 和 layout是比较繁重的操作,view的层级越复杂,操作就会越繁重。Measurement和layout是在UI Thred发发生的。因此当系统需要运行一个动画的时候紧接着还需进行layout,而它们都是在同一个线程,因此动画的流畅度可能就会受到影响。
    假设你的动画在13ms内就可以完成所有的渲染,在16帧率之内。然后某一请求导致了layout,花费了5ms的时间。该layout在下一帧绘制前会 发生,那么总的绘制时间就会达到18ms,最终你的动画就明显的跳过一帧。当动画过程中需要进行layout的时候,为了避免这种情况,可以在动画启动前 进行layout或者延迟layout到动画完成。当然,尽量为那些不会触发layout的属性添加动画。比如,View的translationXtanshlationY属性影响到post-layout属性。LayoutParams 属性也会需要请求layout操作,因此对这些属性进行动画的时候在相对复杂的UI上会导致卡顿。
  • Inflation
    View 填充也只会发生在UI Thread,也是比较繁重的操作(View的层级越大,工作越繁重)。填充工作会在手动填充一个View 或者启动一个activity的时候发生。这些都是在相同的UI线程进行的,当新的activity被填充的时候将会导致动画暂停。为了避免这种情况,可 以在动画完成的时候再启动Activity。或者避免滚动列表时填充不同类型的View导致的卡顿,可以考虑预填充。比如,RecyclerView支持 使用RectcledViewPool来装载不同的View类型。

加快启动速度

Google Develop for Android 系列二 - Memory 最佳实践

作者 : lightsky
原文链接


在决定应用的行为,是否有好的用户体验以及整体的设备体验来说,内存的使用可能是独立因素中最重要的。内存因素包括应用的内存占用,以及内存搅动(导致的垃圾回收会对运行期间的性能有影响)。

避免在循环中分配内存

内存分配虽然不可避免,但是应尽可能的避免,特别是在平凡的调用的代码块中。比如在绘制代码中,因为每一帧的渲染都会执行该方法。

避免在自定义View的onDraw方法中分配内存,因为动画也许会调用它。使用缓存对象替换临时对象可以避免新的内存开销。典型的例子就是在 onDraw方法中分配了一个新的Paint对象,因为Canvas需要一个Paint对象。对于这种自定义View的实例只分配一个独立的Paint对 象而后在onDraw方法中临时使用更好。

尽可能的避免内存分配

避免常量,临时变量的内存分配。下面有一些可参考的策略,也许并不适用于传统的java编码,但是对于Android开发是推荐的。通常可以使用工 具帮助我们去决定是否某一块代码需要优化。如果代码的某一部分很少执行(比如用户改变一些设置的操作),更简洁和传统的抽象层是不错的选择。但是如果分析 表明某些代码频繁执行并导致了大量的内存搅动,考虑以下策略:

  • 对象缓存
    重用对象在一些常量内存的再分配中很有用,比如在内部循环中避免内存分配。比如,有些频繁调用的方法中可能需要一个Rect对象存储一些中间值,最好在把 Rect作为类级别的常量,只分配一次内存,甚至是静态的,避免每次方法调用的时候都分配。关于单例的一些警示对于这种方式也是适用的,在Android 上,静态的常见缺陷就是它们对于某一个进程是静态的,但是可能有多个活动在同一进程中。小心应用,这种技术在避免内存的再分配中是通用的

Google Develop for Android 系列一 - 相关上下文介绍

作者 : lightsky
原文链接

前几天在G+上看到Google Developers站点,有一个Android系列的文章,分享到个人微博,周末闲来没事就学写了下,把它们简单的翻译了下,没想到一发不可收拾,六篇 文章全部都翻译完了,有些地方省略了部分示例的描述或者换了另一种表述,如果有理解的不准确的地方,还望指正


原文:Developing for Android, I:Understanding the Mobile Context

context或者这些建议为何如此重要

对于理解这些最佳实践的相关上下文是非常重要的。特别是明白移动端存在一些严重的限制,和台式机以及服务器端的计算是完全不同的。因此,在开发应用 的时候,如果没有将这些限制考虑进去,将会导致很大的性能问题和内存消耗,这不是针对于某一个应用而言,而是针对整个设备,因为很多拥有相同性能问题的 app共同导致了一个性能很差的设备。
以下使一些重要的约束,限制和现实,在过去以及将来的移动设备中都很重要。

移动设备的内存是很有限的。当然不是指所有的设备,不过对于大多数的移动系统是这样的。对于开发者来说,我们使用的手机可能是比大多数用户现在或者 将来所使用的设备更快更好,也更新。比如,拥有一个带有 1GB-2GB内存Nexus 5对于我们来说是比较合理的,512M的内存配置的手机在美国以及一些新兴的低端市场种很常见的。因此,以2GB或者更大内存的标准去衡量一个App是不 现实的。
明白Android会运行多个Activity和多个并行的Service也是很重要的。在最近App列表中切换而不是重新启动一个App的方式对于创造 出一种很好的体验是非常重要的。但是这样意味着如果这些app消耗了比它们本应该消耗的更多内存,那么留给其它的应用的系统内存就很少了。如果这种低内存 情况发生,应用就不能保留在后台,系统会干掉app的进程,用户就被迫以重新启动的方式去开启一个App,显然这样的体验就比较差。

Android AsyncTask 技巧

AsyncTask的原理

其实AsyncTask的原理简单来说,就是:

  • 一个任务队:用于存放自定义的(WorkerRunnable)。
  • 一个线程池:初始化好任务队列,放入该线程池中。
  • 一个内部Handler:用于提供线程池执行线程时与主线程之间的交互(刷新控件各种)。

widget用法

September 23, 2015 8:21 PM

创建AppWidgetProvider的子类

public class MyAppWidgetProvider extends AppWidgetProvider {
@Override
public void onEnabled(Context context) {
    // 第一次创建执行
    // 服务监控进程状态
    Intent service = new Intent(context,TaskWidgetService.class);
    context.startService(service);
    super.onEnabled(context);
}

@Override
public void onDisabled(Context context) {
    //删除最后一个执行
    Intent service = new Intent(context,TaskWidgetService.class);
    context.stopService(service);
    super.onDisabled(context);
}
}

WebView入门

WebView控件可以实现一个浏览器的功能,直接在控件中显示指定的网页

第一种方式

第一种方式是不需要,在布局文件中,使用WebView控件的

步骤:

解析:TypedArray 为什么需要调用recycle()

在 Android 自定义 View 的时候,需要使用 TypedArray 来获取 XML layout 中的属性值,使用完之后,需要调用 recyle() 方法将 TypedArray 回收。

那么问题来了,这个TypedArray是个什么东西?为什么需要回收呢?TypedArray并没有占用IO,线程,它仅仅是一个变量而已,为什么需要 recycle?
为了解开这个谜,首先去找官网的 Documentation,到找 TypedArray 方法,得到下面一个简短的回答:

Android内核 ServiceManager

getSystemService很尽职,你知道吗?

那个…嗯…曾记否上篇的Android Binder中留下的疑问?就是在Context的实现类中使用
getSystemService(String serviceName)方法获取一个系统服务,那么这些系统服务的Binder
引用时如何传递给客户端的呢?其实这里有个关键点,就是系统服务并不是通过startService()启
动的。

Android内核 Binder

Binder,英文的意思是别针、回形针。平常我们经常用回形针把几张纸夹起来。而在Android
中,Binder用于完成进程间通信(IPC),就是把多个线程夹起来。比如,普通应用程序可以
调用音乐播放服务的播放、暂停、停止等功能。

异步线程的实现(Looper、MessageQueue、Handler)

什么是异步消息处理线程?

对于普通线程来说,执行完run()方法内的代码后线程就结束了。所谓异步消息处理线程
而言,线程启动后会进入一个无限循环体之中,每循环一次,就从其内部的消息队列中取出一
个消息,并回调该消息相应的消息处理函数,执行完一个消息后再继续回到循环体之中。除非
消息队列为空,线程会暂停,直到消息队列中有新的消息了,则继续无限循环。

 异步消息处理线程其本质上也是一个线程,只不过这种线程的执行代码被设计成如上所描述

的逻辑而已。一般来说,当同时处在以下两种需求时使用异步消息处理线程:

CountDownTimer(倒数计时器)

其实在很多时候,我们都需要一个倒计时的功能,这个功能我们自己可以根据java自带的TimerTask
去实现。这里,提到的是一个在基本Android开发书籍中都很少介绍到的一个Android原生自带倒数计
时器 - CountDownTimer 。

ViewHolder的作用

Adapter的getVIew()以得到旧视图(convertView):

/**
 * Get a View that displays the data at the specified position in the data set. You can either
 * create a View manually or inflate it from an XML layout file. When the View is inflated, the
 * parent View (GridView, ListView...) will apply default layout parameters unless you use
 * {@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)}
 * to specify a root view and to prevent attachment to the root.
 * 
 * @param position The position of the item within the adapter's data set of the item whose view
 *        we want.
 * @param convertView The old view to reuse, if possible. Note: You should check that this view
 *        is non-null and of an appropriate type before using. If it is not possible to convert
 *        this view to display the correct data, this method can create a new view.
 *        Heterogeneous lists can specify their number of view types, so that this View is
 *        always of the right type (see {@link #getViewTypeCount()} and
 *        {@link #getItemViewType(int)}).
 * @param parent The parent that this view will eventually be attached to
 * @return A View corresponding to the data at the specified position.
 */
View getView(int position, View convertView, ViewGroup parent);