Comments

《Listview 子控件重复加载》中谈到了onMeasure()方法,谈到了layout,但更细致的内容没有提到。这里找了一下相关资料来补充:

  • 绘制过程是一个控件树遍历的过程,从根控件开始
  • ViewGroup负责通知它的子视图,View负责自绘,按照顺序,父视图先于子视图绘制
  • 绘制实际上是2个自顶向下的过程:measure 和layout。
  • 经过measure 之后,每个View都会保存自己的尺寸,而在layout 过程中父视图会使用这些尺寸来摆放子视图
  • View对象的高宽会受到父视图的限制,以保证整个视图的正常显示
  • 父视图会多次调用子视图的measure方法。比如:父视图会先计算不受约束的情况下,子视图的大小;如果子视图过大或者过小,父视图都会指定一个具体的值
  • Measure 过程还会涉及ViewGroup.LayoutParams以及MeasureSpec的设置
  • ViewGroup.LayoutParams被View 对象用来告诉其父控件,自己想如何被计算以及摆放,最基本的是指定长宽:
    1. MATCH_PARENT, which means the View wants to be as big as its parent (minus padding)
    2. WRAP_CONTENT, which means that the View wants to be just big enough to enclose its content (plus padding).
    3. 具体数值
  • MeasureSpec 指定一种计算模式:
    1. UNSPECIFIED: This is used by a parent to determine the desired dimension of a child View.
    2. EXACTLY: This is used by the parent to impose an exact size on the child.
    3. AT MOST: This is used by the parent to impose a maximum size on the child.

参考资料

Comments

这是一个常见的listview 资源文件的写法。

listview资源文件
1
2
3
4
5
    <ListView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/listView"
    />

注意到android:layout_height 被设置为wrap_content,这似乎很合理。但如果我们留心的话可以发现,与这个listview 关联的adapter对象的getView()方法被重复调用了好几遍!这对于那些依赖listview 展示大量数据的应用来说,绝对是性能打击。

解决方案:

将这个属性设置为match_parent 或者固定的数值

原因:

通过分析AbsListView 以及Listview的代码,发现答案应该在onMeasure()方法里:由于wrap_content 并没有指定一个固定值,系统需要通过尝试layout来满足wrap_content。每次尝试都会调用onMeasure(),layoutChildren(),使得getView()被重复调用

Comments

Service 作为一个“看不见”的android 组件,天然的就应该为Android 程序去处理那些费事费力的苦活。但即使这样,作为四大组件之一的它依然运行在主线程上。也就是说,那些理所当然的“负担”如果直接运行在Service 上必然会影响程序的UI 性能。因此,我们通常需要与之对应的后台线程来完成工作。

与其他组件一样,要想Service 正常运行,就必须在AndroidManifest.xml 中对其进行声明。如果Service 作为一个开放组件(允许外部程序调用),则还需要设置Intent Filter 来支持隐式调用;反之,如果它只在程序内运行,则可省去Filter 设置,更妥当的做法是将export属性设置为false。

总的来说,Service 有两种形式:命令启动类(Started)绑定类(Bound)。两种形式之间并不总是非此既彼的关系,同一个应用的同一个Service 可以同时满足这两种形式,而这一点完全取决于应用逻辑。

命令启动类(started)的Service

  • 一旦被启动,如果没有其他组件调用stopService()而且自己也没有调用stopSelf(int),那么它将会一直运行下去(除非系统内存吃紧)
  • onStartCommand()有三类返回值,分别代表Service 在执行完该函数之后,如遭遇系统Kill 的不同策略:
    1. START_NOT_STICKY,除非有新的命令,不然不会重新构建Service
    2. START_STICKY,不死Service。如果系统资源允许,会重新构建Service并调用startCommand(),但并不会重新传递最后一次发过来的intent。(适用于不care 命令,而care 启没启动的操作,比如恢复音乐播放)
    3. START_REDELIVER_INTENT, 对intent 负责到底。在系统资源允许的情况下,重新构建service并将最后一次发送的intent 通过参数传递给startCommand()。(适用于care 命令,比如恢复下载某个文件)
  • 和绑定类服务不同,如果希望得到交互结果,则必须使用Broardcast传递
  • startCommand() 会有并发的情况,但stopService() 只会执行一次。所以在具体应用场景中,我们需要考虑命令Service们能不能、该不该被中途停止。如果得到的答案是否定的,那么我们应该尝试使用stopSelf(int)来管理service,其参数为希望停止的service ID。

简化的命令服务IntentServie

如果你不希望Service 处理并发请求,那么IntentService 是不二的选择。之所以这么说,是你几乎只需要三步就可以实现一个为你完成后台操作的单线程服务:1,继承IntetnService;2,覆盖onHandleIntent();3,在构造函数中初始化父类。那么IntentService 会帮你做到: – 创建一个后台线程 – 创建一个消息队列来执行任务 – 当没有任务的时候自动停止

如果希望自己覆盖某些Service 的方法,那么一定要记得调用super,否则会导致一些意想不到的结果出现。

绑定类(Bound)服务

  • IBinder 表示客户端与服务端交互的接口
  • 三种提供Binder 接口的方法:
    1. 直接继承Binder 类,提供API 给客户端调用。这些API 既可以由Binder 子类来实现,也可以通过API 返回该服务或其他服务的实例,由服务本身提供的方法去完成调用;这种方法适用与私有服务
    2. 使用Messenger返回binder。该方法的本质是AIDL,提供了一种线程安全的跨进程通讯方案。这种方法适用于服务与调用组件不在同一个进程的情况。详见参考代码
    3. 如果服务不但跨进程,而且还希望处理并发请求,则应该使用AIDL来实现
  • 多个客户端可以绑定同一个服务,但onBind() 只会在第一个客户端绑定的时候被调用。之后绑定的客户端虽然会获得同样的binder,但已经不会再调用onBind()
  • 四大组件中除了Broardcast Receiver之外都可以绑定服务
  • onServiceDisconnected()只会在服务崩溃的时候调用,而并不会在unbind 的时候被调用
  • bind 应与unbind 成对出现,服务与其绑定的客户端组件共存亡。如果交互工作完全结束的话,可以尽早的结束绑定,而不一定要等到组件消亡(比如某浪sso 接口那样)
  • bind 与unbind 不应该出现在onResume() 以及onPause()里,这样会使得服务的绑定与解绑操作过于频繁
  • 绑定类服务又恰巧被调用了startService(),则只有同时满足以下条件,服务才会终止:1,没有客户端组件绑定该服务;2,服务调用了stopService() 或者stopSelf(int)
  • 如下图所示,只有当unBind()返回true 的时候,下次绑定的时候,系统才会调用onRebind(),否则还会继续调用onBind()
  • 所有对象均为跨进程引用计数
  • 如果服务连接丢失的话,容易引发DeadObjectException

服务的生命周期

前台服务

  • 前台服务能够极大的提升不被杀掉的概率
  • 前台服务必须有与之对应ON-GONING通知显示
  • stopForeground()并不会停止服务;而停止服务后,通知自然消亡

AIDL

  • Stub 继承自Binder 还实现了aidl 中的接口,除此之外还提供类似asInterface(IBinder)的辅助方法
  • AIDL 接口调用是直接方法调用,不应该想当然的认为它们在哪个线程中执行。如果是本地进程调用,那么接口会和调用者使用同一个线程执行;如果是远程调用,会由服务所在的进程的线程池派发一个线程来执行;如果使用oneway修饰,在远程调用中是非阻塞的,而在本地调用中依然是同步的。
  • AIDL 支持java 的基础类型、StringCharsequenceListMap,除此之外的类型,都需要实现Parcebal接口,且必须使用import来声明,即使它们在同一个包
  • 调用不保证会执行,所以从一开始设计的时候就应该考虑线程安全
  • 为了避免ANR,应该将调用转移到单独的线程中执行
  • 抛出的异常不会被调用者捕获(跨进程异常是不可取的)

参考资料

参考代码