JingBin's Home

《Android开发艺术探索》读书笔记

《Android开发艺术探索》 - 一本Android进阶类书籍,采用理论、源码和实践相结合的方式来阐述高水准的Android应用开发要点。

《Android开发艺术探索》从三个方面来组织内容。第一,介绍Android开发者不容易掌握的一些知识点;第二,结合Android源代码和应用层开发过程,融会贯通,介绍一些比较深入的知识点;第三,介绍一些核心技术和Android的性能优化思想。

《Android开发艺术探索》侧重于Android知识的体系化和系统工作机制的分析,通过《Android开发艺术探索》的学习可以极大地提高开发者的Android技术水平,从而更加高效地成为高级开发者。而对于高级开发者来说,仍然可以从《Android开发艺术探索》的知识体系中获益。

完善中,借鉴了他人的读书笔记。

Activity的生命周期和启动模式

用户正常使用情况下的生命周期 & 由于Activity被系统回收或者设备配置改变导致Activity被销毁重建情况下的生命周期。

Activity的生命周期全面分析

典型情况下的生命周期分析

Activity生命周期.png

  • 1.Activity第一次启动:onCreate->onStart->onResume。
  • 2.Activity切换到后台( 用户打开新的Activity或者切换到桌面),onPause->onStop。
  • 3.Activity从后台到前台,重新可见,onRestart->onStart->onResume。
  • 4.用户退出Activity,onPause->onStop->onDestroy。
  • 5.onStart开始到onStop之前,Activity可见。onResume到onPause之前,Activity可以接受用户交互。
  • 6.在新Activity启动之前,栈顶的Activity需要先onPause后,新Activity才能启动。所以不能在onPause执行耗时操作。

异常情况下的生命周期分析

系统配置变化导致Activity销毁重建

例如Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,Activity就会被销毁并重新创建。

  • 在异常情况下系统会在onStop之前调用onSaveInstanceState来保存状态。Activity重新创建后,会在onStart之后调用onRestoreInstanceState来恢复之前保存的数据。
  • 保存数据的流程: Activity被意外终止,调用onSaveIntanceState保存数据-> Activity委托Window,Window委托它上面的顶级容器一个ViewGroup( 书上说很可能就是DecorView) 。然后顶层容器在通知所有子元素来保存数据。 每个View都有 onSaveInstanceState 和 onRestoreInstanceState 方法。查看TextView 源码可以发现保存了文本选中状态和文本内容。
  • 系统只在Activity异常终止的时候才会调用 onSaveInstanceState 和onRestoreInstanceState 方法。其他情况不会触发。
资源内存不足导致低优先级的Activity被回收
  • 1.前台- 可见非前台( 被对话框遮挡的Activity) -后台,这三种Activity优先级从高到低。
  • 2.android:configChanges=”orientation” 在manifest中指定 configChanges 在系统配置变化后不重新创建Activity,也不会执行onSaveInstanceState 和 onRestoreInstanceState 方法,而是调用 onConfigurationChnaged 方法。
  • 3.configChanges 一般常用三个选项:
    • locale 系统语言变化
    • keyborardHidden 键盘的可访问性发生了变化,比如用户调出了键盘
    • orientation 屏幕方向变化

Activity的启动模式

Activity的LaunchMode

Android使用栈来管理Activity。

standard
  • 每次启动都会重新创建一个实例,不管这个Activity在栈中是否已经存在。
  • 谁启动了这个Activity,那么Activity就运行在启动它的那个Activity所在的栈中。
  • 用Application去启动Activity时会报错,提示非Activity的Context没有所谓的任务栈。解决
    办法是为待启动Activity制定FLAG_ACTIVITY_NEW_TASH标志位,这样就会为它创建
    一个新的任务栈。
singleTop
  • 如果新Activity位于任务栈的栈顶,那么此Activity不会被重新创建,同时回调 onNewIntent 方法。
  • 如果新Activity已经存在但不是位于栈顶,那么新Activity仍然会被创建。
singleTask
  • 这是一种单实例模式
  • 只要Activity在栈中存在,那么多次启动这个Activity都不会重新创建实例,同时也会回调 onNewIntent 方法。
  • 同时会导致在Activity之上的栈内Activity出栈。
singleIntance
  • 具有singleTask模式的所有特性,同时具有此模式的Activity只能单独的位于一个任务栈中
TaskAffinity属性

TaskAffinity参数标识了一个Activity所需要的任务栈的名字。为字符串,且中间必须包含包名分隔符“.”。默认情况下,所有Activity所需的任务栈名字为应用包名。TashAffinity属性主要和singleTask启动模式或者 allowTaskReparenting 属性配对使用,其他情况下没有意义。 应用A启动了应用B的某个Activity后,如果Activity的allowTaskReparenting属性为true的话,那么当应用B被启动后,此Activity会直接从应用A的任务栈转移到应用B的任务栈中。 打个比方就是,应用A启动了应用B的ActivityX,然后按Home回到桌面,单击应用B的图标,这时并不会启动B的主Activity,而是重新显示已经被应用A启动的ActivityX。这是因为ActivityX的TaskAffinity值肯定不和应用A的任务栈相同( 因为包名不同) 。所以当应用B被启动以后,发现ActivityX原本所需的任务栈已经被创建了,所以把ActivityX从A的任务栈中转移过来了。

设置启动模式:

  • 1.manifest中 设置下的 android:launchMode 属性。
  • 2.启动Activity的 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 。
  • 3.两种同时存在时,以第二种为准。第一种方式无法直接为Activity添加FLAG_ACTIVITY_CLEAR_TOP标识,第二种方式无法指定singleInstance模式。
  • 4.可以通过命令行 adb shell dumpsys activity 命令查看栈中的Activity信息。

Activity的Flags

这些FLAG可以设定启动模式、可以影响Activity的运行状态。

  • FLAG_ACTIVITY_CLEAR_TOP 具有此标记位的Activity启动时,同一个任务栈中位于它上面的Activity都要出栈,一般和FLAG_ACTIVITY_NEW_TASK配合使用。效果和singleTask一样。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 如果设置,新的Activity不会在最近启动的Activity的列表(就是安卓手机里显示最近打开的Activity那个系统级的UI)中保存。

IntentFilter的匹配规则

启动Activity分为两种:

  • 1.显示调用 明确指定被启动对象的组件信息,包括包名和类名
  • 2.隐式调用 不需要明确指定组件信息,需要Intent能够匹配目标组件中的IntentFilter中所设置的过滤信息。
  • 3.IntentFilter中的过滤信息有action、category、data。
  • 4.只有一个Intent同时匹配action类别、category类别、data类别才能成功启动目标Activity。
  • 5.一个Activity可以有多个intent-filter,一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity。

action

  • 1.action是一个字符串。
  • 2.一个intent-filter可以有多个aciton,只要Intent中的action能够和任何一个action相同即可成功匹配。匹配是指与action的字符串完全一样。
  • 3.Intent中如果没有指定action,那么匹配失败。

category

  • category是一个字符串。
  • 2.Intent可以没有category,但是如果你一旦有category,不管有几个,每个都能够与
    intent-filter中的其中一个category相同。
  • 3.系统在startActivitystartActivityForResult的时候,会默认为Intent加上 android.intent.category.DEFAULT 这个category,所以为了我们的activity能够接收隐式调用,就必须在intent-filter中加上 android.intent.category.DEFAULT 这个category。

data

  • 1.data的匹配规则与action一样,如果intent-filter中定义了data,那么Intent中必须要定义可匹配的data。
  • 2.intent-filter中data的语法:

    <data android:scheme="string"
        android:host="string"
        android:port="string"
        android:path="string"
        android:pathPattern="string"
        android:pathPrefix="string"
        android:mimeType="string"/>
    
  • 3.Intent中的data有两部分组成:mimeType和URI。mimeType是指媒体类型,比如
    image/jpeg、audio/mpeg4-generic和video/等,可以表示图片、文本、视频等不同的媒
    体格式。

    • URI的结构:<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
      //实际例子
      content://com.example.project:200/folder/subfolder/etc
      http://www.baidu.com:80/search/info
      • scheme:URI的模式,比如http、file、content等,默认值是 file 。
      • host:URI的主机名
      • port:URI的端口号
      • path、pathPattern和pathPrefix:这三个参数描述路径信息。
        • path、pathPattern可以表示完整的路径信息,其中pathPattern可以包含通配符 * ,表示0个或者多个任意字符。
        • pathPrefix只表示路径的前缀信息。
    • Intent指定data时,必须调用 setDataAndType 方法, setData 和 setType 会清除另一方的值。

隐式调用需注意:

  • 1.当通过隐式调用启动Activity时,没找到对应的Activity系统就会抛出 android.content.ActivityNotFoundException 异常,所以需要判断是否有Activity能够匹配我们的隐式Intent。

    • i. 采用 PackageManager 的 resloveActivity 方法
      public abstract List queryIntentActivityies(Intent intent,int flags);
      public abstract ResolveInfo resloveActivity(Intent intent,int flags);
      以上的第二个参数使用 MATCH_DEFAULT_ONLY ,这个标志位的含义是仅仅匹配那些在intent-filter中声明了 android.intent.category.DEFAULT 这个category的Activity。因为如果把不含这个category的Activity匹配出来了,由于不含DEFAULT这个category的Activity是无法接受隐式Intent的从而导致startActivity失败。
    • ii. 采用 Intent 的 resloveActivity 方法
  • 2.下面的action和category用来表明这是一个入口Activity并且会出现在系统的应用列表中,二者缺一不可。


理解RemoteViews

RemoteViews提供了一组基础的操作,用于跨进程更新它的界面。RemoteViews在Andriod中的使用场景有两种:通知栏和桌面小部件(都运行在SystemServer进程)。

RemoteViews的应用

通知栏主要通过NotificationManager的notify方法来实现,除了默认效果外还可以自定义布局。
桌面小工具主要通过AppWidgetProvider来实现,AppWidgetProvider本质上是一个广播。
两者都会用到RemoteViews,两者都运行在其他进程中,准确的说是系统的SystemServer进程。

RemoteViews在通知栏上的应用

关于PendingIntent,它表示的是一种特定的Intent,这个Intent中所包含的意图必须由用户来触发。

RemoteViews在桌面小部件上的应用

AppWidgetProvider实现桌面小工具的类,本质是一个广播即BroadcastReceiver。
具体使用看系统自动生成的桌面小工具。
桌面小部件上不管是初始化界面还是后续的更新界面都必须使用RemoteViews来完成。

PendingIntent概述

PendingIntent和Intent的区别在于,PendingIntent是在将来的某个环节的不确定的时刻发生,而Intent是立刻发生。

RemoteViews的内部机制

  • 大部分的set方法的确是通过发射来完成的。
  • NotificationManager和AppWidgetProvider通过Binder分别和SystemServer进程中的NotificationManagerService以及AppWidgetServer进行通信。
  • RemoteViews会通过Binder传递到SystemServer进程中,这是因为RemoteViews实现了Parcelable接口,因此它可以跨进程传输,系统会根据RemoteViews中的包名等信息去得到该应用的资源。

RemoteViews的意义

模拟通知栏效果实现跨进程跨进程的UI更新。
我们可以像系统一样使用Binder来实现,但是为了简单起见就采用广播。
实际:

  • 现在用两个应用,一个应用需要能够更新另一个应用的某个界面,这个时候我们当然可以选择AIDL去实现(跨应用更新UI),但是如果对界面的更新比较频繁,这个时候就会有效率的问题,如果采用RemoteViews来实现就没有这个问题了。(RemoteViews只支持一些常用的View,对于自定义的View是不支持的。)

Android动画深入分析

Android动画分为三种:

  • 1.View动画(平移、缩放、旋转、透明度)
  • 2.帧动画(图片切换动画)
  • 3.属性动画(动态的改变对象的属性从而达到动画的效果)

View动画

View动画的作用对象是View,支持四种动画效果:

  • 1.平移
  • 2.缩放
  • 3.旋转
  • 4.透明

View动画的种类

上述四种变换效果对应着Animation四个子类: TranslateAnimation 、 ScaleAnimation 、 RotateAnimation 和 AlphaAnimation 。这四种动画皆可以通过XML定义,也可以通过代码来动态创建。

xml定义动画:

  • 1. 标签表示动画集合,对应AnimationSet类,可以包含一个或若干个动画,内部还可以嵌套其他动画集合。两个属性:

    • i. android:interpolator 表示动画集合所采用的插值器,插值器影响动画速度,比如非匀速动画就需要通过插值器来控制动画的播放过程。
    • ii. android:shareInterpolator 表示集合中的动画是否和集合共享同一个插值器,如果集合不指定插值器,那么子动画就需要单独指定所需的插值器或默认值。
  • 2.<translate><scale><rotate><alpha>这几个子标签分别代表四种变换效果。

  • 3.定义完View动画的xml后,通过以下代码应用动画:

    1
    2
    Aniamation anim = AnimationUtils.loadAnimation(context,R.anim.animation_test);
    view.startAnimation(anim);

代码动态创建动画:

1
2
3
AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
alphaAnimation.setDuration(1500);
view.startAnimation(alphaAnimation);

自定义View动画

需要继承 Animation 这个抽象类,重写它的 initialize 和 applyTransformation 方法。在 initialize 方法中做一些初始化工作,在 applyTransformation 中进行相应的矩阵变换即可,很多时候需要采用 Camera 来简化矩阵变换的过程。自定义View动画的过程主要是矩阵变换的过程。

帧动画

帧动画是顺序播放一组预先定义好的图片,使用简单,但容易引起OOM,所以在使用帧动画时应尽量避免使用过多尺寸较大的图片。

View动画的特殊使用场景

LayoutAnimation

作用于ViewGroup,为ViewGroup指定一个动画,当它的子元素出场时都会具有这种动画效果,一般用在ListView上。

Activity的切换效果

我们可以自定义Activity的切换效果,主要通过在 startActivity 或者 finish 的后面增加overridePendingTransition(int enterAnim , int exitAnim)方法

属性动画

API 11后加入,可以在一个时间间隔内完成对象从一个属性值到另一个属性值的改变。因此与
View动画相比,属性动画几乎无所不能,只要对象有这个属性,它都能实现动画效果。API11
以下可以通过 nineoldandroids 库来兼容以前版本。

属性动画有以下三种使用方法:

  • ObjectAnimator:
    • ObjectAnimator.ofFloat(view,"translationY",values).start();
  • ValueAnimator

    • 1
      2
      3
      4
      5
      6
      ValueAnimator colorAnim = ObjectAnimator.ofInt(view,"backgroundColor",/*red*/0xffff8080,/*blue*/0xff8080ff);
      colorAnim.setDuration(2000);
      colorAnim.setEvaluator(new ArgbEvaluator());
      colorAnim.setRepeatCount(ValueAnimator.INFINITE);
      colorAnim.setRepeatMode(ValueAnimator.REVERSE);
      colorAnim.start();
  • AnimatorSet

    • 1
      2
      3
      AnimatorSet set = new AnimatorSet();
      set.playTogether(animator1,animator2,animator3);
      set.setDuration(3*1000).start();
    • 也可以通过在xml中定义在 res/animator/ 目录下。具体如下:

      • 1
        2
        3
        4
        5
        6
        7
        <?xml version="1.0" encoding="utf-8"?>
        <set xmlns:android="http://schemas.android.com/apk/res/android">
        <objectAnimator
        ....../>
        <animator
        ....../>
        </set>
      • 1
        2
        3
        4
        5
        AnimatorSet set = (AnimatorSet)AnimatorInflater.loadAnimator(context , R.animator.anim);
        set.setTarget(view);
        set.start();
        <set> 标签对应 AnimatorSet,<animator>对应ValueAnimator,
        而<objectAnimator>则对应 ObjectAnimator。

理解差值器和估值器

  • 时间插值器( TimeInterpolator) 的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有LinearInterpolator(线性插值器:匀速动画),AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快),DecelerateInterpolator(减速插值器:动画越来越慢)。

  • 估值器( TypeEvaluator) 的作用是根据当前属性改变的百分比来计算改变后的属性值。
    系统预置有IntEvaluator 、FloatEvaluator 、ArgbEvaluator。

  • 具体来说 对于一个作用在view上改变其宽度属性、持续40ms的属性动画来说,就是当时间t=20ms时,时间流逝了50%,那么view的宽度属性应该改变了多少呢?这个就由Interpolator和Evaluator的算法来决定。

属性动画的监听器

1
2
3
4
5
6
public static interface AnimatorListener {
void onAnimationStart(Animator animation); //动画开始
void onAnimationEnd(Animator animation); //动画结束
void onAnimationCancel(Animator animation); //动画取消
void onAnimationRepeat(Animator animation); //动画重复播放
}

为了方便开发,系统提供了AnimatorListenerAdapter类,它是AnimatorListener的适配器类,可以有选择的实现以上4个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Implementors of this interface can add themselves as update listeners
* to an <code>ValueAnimator</code> instance to receive callbacks on every animation
* frame, after the current frame's values have been calculated for that
* <code>ValueAnimator</code>.
*/
public static interface AnimatorUpdateListener {
/**
* <p>Notifies the occurrence of another frame of the animation.</p>
* *
@param animation The animation which was repeated.
*/
void onAnimationUpdate(ValueAnimator animation);
}

AnimatorUpdateListener会监听整个动画的过程,动画由许多帧组成的,每播放一帧,onAnimationUpdate就会调用一次。

对任意属性做动画

  • 1.属性动画要求作用的对象提供该属性的get和set方法,属性动画根据外界传递的该属性的初始值和最终值,通过多次调用set方法来实现动画效果。

  • 2.如果被作用的对象没有set/get方法,可以:

    • i.请给你的对象加上get和set方法,如果你有权限的话( 对于SDK或者其他第三方类库
      的类无法加上的)
    • ii.用一个类来包装原始对象,间接为其提供get和set方法
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      //包装View类 用于给属性动画调用 从而包装了set get
      public class ViewWrapper {
      private View target;
      public ViewWrapper(View target) {
      this.target = target;
      }
      public int getWidth() {
      return target.getLayoutParams().width;
      }
      public void setWidth(int width) {
      target.getLayoutParams().width = width;
      target.requestLayout();
      }
      }
      //使用:
      ViewWrapper wrapper = new ViewWrapper(mButton);
      ObjectAnimator.ofInt(mButton,"width",500).setDuration(3000).start();
    • iii.采用ValueAnimator,监听动画过程,自己实现属性的改变;

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        private void performAnimate(final View target, final int start, final int end) {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
        valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
        // 持有一个IntEvaluator对象,方便下面估值的时候使用
        private IntEvaluator mEvaluator = new IntEvaluator();
        @Override
        public void onAnimationUpdate(ValueAnimator animator) {
        // 获得当前动画的进度值,整型,1-100之间
        int currentValue = (Integer) animator.getAnimatedValue();
        Log.d(TAG, "current value: " + currentValue);
        // 获得当前进度占整个动画过程的比例,浮点型,0-1之间
        float fraction = animator.getAnimatedFraction();
        // 直接调用整型估值器通过比例计算出宽度,然后再设给Button
        target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
        target.requestLayout();
        }
        });
        valueAnimator.setDuration(5000).start();
        }

属性动画的工作原理

属性动画需要运行在有Looper的线程中,系统通过反射调用被作用对象get/set方法。

使用动画的注意事项

  • 1.使用帧动画时,当图片数量较多且图片分辨率较大的时候容易出现OOM,需注意,尽量
    避免使用帧动画。
  • 2.使用无限循环的属性动画时,在Activity退出时即使停止,否则将导致Activity无法释放从而造成内存泄露。
  • 3.动画在3.0以下的系统存在兼容性问题,特殊场景可能无法正常工作,需做好适配工作。
  • 4.View动画是对View的影像做动画,并不是真正的改变了View的状态,因此有时候会出现动画完成后View无法隐藏( setVisibility(View.GONE) 失效),这时候调用 view.clearAnimation() 清理View动画即可解决。
  • 5.不要使用px,使用px会导致不同设备上有不同的效果。
  • 6.View动画是对View的影像做动画,View的真实位置没有变动,动画完成后的新位置是无法触发点击事件的。属性动画是真实改变了View的属性,所以动画完成后的位置可以接受触摸事件。
  • 7.使用动画的过程中,使用硬件加速可以提高动画的流畅度。

Android的线程和线程池

  • 1.在Android系统,线程主要分为主线程和子线程,主线程处理和界面相关的事情,而子线程一般用于执行耗时操作。
  • 2.在Android中,线程的形态有很多种:
    • i.AsyncTask封装了线程池和Handler。
    • ii.HandlerThread是具有消息循环的线程,内部可以使用handler
    • iii.IntentService是一种Service,内部采用HandlerThread来执行任务,当任务执行完毕后IntentService会自动退出。由于它是一种Service,所以不容易被系统杀死
  • 3.操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,其创建和销毁都会有相应的开销。同时当系统存在大量线程时,系统会通过时间片轮转的方式调度每个线程,因此线程不可能做到绝对的并发,除非线程数量小于等于CPU的核心数。
  • 4.频繁创建销毁线程不明智,使用线程池是正确的做法。线程池会缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁线程所带来的系统开销。

主线程和子线程

  • 1.主线程主要处理界面交互逻辑,由于用户随时会和界面交互,所以主线程在任何时候都需要有较高响应速度,则不能执行耗时的任务;
  • 2.android3.0开始,网络访问将会失败并抛出NetworkOnMainThreadException这个异常,这样做是为了避免主线程由于被耗时操作所阻塞从而现ANR现象。

Android中的线程形态

AsyncTask

1.三个参数(都可为Void):

  • i. Params:参数
  • ii. Progress:执行进度
  • iii. Result:返回值

2.四个方法 :

  • i. onPreExecute() 主线程执行,异步方法执行前调用。
  • ii. doInBackground(Params…params) 线程池中执行,用于执行异步任务;在方法内部用publishProgress 来更新任务进度。
  • iii. onProgressUpdate(Progress…value) 主线程执行,后台任务进度状态改变时被调用。
  • iv. onPostExecute(Result result) 主线程执行,异步任务执行之后被调用执行顺序: onPreExecute->doInBackground->onPostExecute 如果取消了异步任务,会回调onCancelled(),onPostExecute则不会被调用。

AsyncTask的类必须在主线程加载,Android4.1及以上已经被系统自动完成了;AsyncTask对象必须在主线程创建;execute方法需要在UI线程调用;一个AsyncTask对象只能调用一次;Android1.6之前串行执行,Android1.6采用线程池并行处理任务,Android3.0开始,又采用一个线程来串行执行任务,但也可以通过 executeOnExecutor() 方法来并行执行任务。

AsyncTask的工作原理

  • 1.AsyncTask中有两个线程池( SerialExecutor 和 THREAD_POOL_EXECUTOR )和一个 InternalHandler ,其中线程池SerialExecutor用于任务排队,THREAD_POOL_EXECUTOR用于真正执行任务,InternalHandler用于将执行环境切换到主线程。
  • 2.AsyncTask的排队过程:系统首先会把AsyncTask的Params参数封装成FutureTask对象,它充当Runnable的作用,接下来这个FutureTask会交给SerialExecutor的 execute() 方法处理,execute()方法首先会把FutereTask对象插入到任务队列 mTasks 中去;如果没有正在活动的AsyncTask任务,就会执行下一个AsyncTask任务;同时当一个AsyncTask任务执行完成后,AsyncTask会继续执行其他任务直到所有任务都执行为止,可以看出默认情况,AsyncTask是串行执行的(Android3.0后)。

HandlerThread

  • 1.HandlerThread继承了Thread,是一种可以使用Handler的Thread
  • 2.在run方法中通过 looper.prepare() 来开启消息循环,这样就可以在HandlerThread中创建Handler了
  • 3.外界可以通过一个Handler的消息方式来通知HandlerThread来执行具体任务;确定不使用之后,可以通过 quit 或 quitSafely 方法来终止线程执行
  • 4.具体使用场景是IntentService

IntentService

IntentSercie是一种特殊的Service,继承了Service并且是抽象类,任务执行完成后会自动停止,优先级远高于普通线程,适合执行一些高优先级的后台任务; IntentService封装了 HandlerThread 和 Handler。

  • 1.onCreate 方法自动创建一个HandlerThread
  • 2.然后用它的Looper构造了一个Handler对象 mServiceHandler ,这样通过mServiceHandlerAndroid的线程和线程池发送的消息都会在HandlerThread执行;
  • 3.IntentServiced的 onHandlerIntent 方法是一个抽象方法,需要在子类实现,onHandlerIntent方法执行后,stopSelt(int startId)就会停止服务,如果存在多个后台任务,执行完最后一个stopSelf(int startId)才会停止服务。

Android线程池

优点:

  1. 重用线程池的线程,减少线程创建和销毁带来的性能开销
  2. 控制线程池的最大并发数,避免大量线程互相抢系统资源导致阻塞
  3. 提供定时执行和间隔循环执行功能

ThreadPoolExecutor(熟悉后可自定义线程池)

Executor是一个接口,线程池的具体实现在ThreadPoolExecutor;它提供了一系列的参数来配置线程池;Android的线程池 大部分都是通 过Executor提供的工厂方法创建的。

ThreadPoolExecutor常见构造参数
  1. corePoolSize: 线程池的核心线程数,默认情况下,核心线程会一直存活(设置了超时机制除外, allowCoreThreadTimeOut属性为true时开启)
  2. maxinmumPoolSize: 线程池能容纳的最大线程数,当活动的线程达到这个数值之后,后续新任务会被阻塞
  3. keepAliveTime: 非核心线程闲置的超时时长,超过这个时长,非核心线程就会被回收,当allowCoreThreadTimeOut为true时,keepAliveTime同样作用于核心线程。
  4. unit:keepAliveTime的时间单位,这是一个枚举,常用TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分钟)
  5. workQueue: 线程池中的任务队列,通过execute方法提交的Runnable对象会存储在这个参数中
  6. threadFactory: 线程工厂,为线程池提供创建线程的功能,是个接口,提供ThreadnewThread(Runnable r)方法
  7. RejectedExecutionHandle:当线程池无法执行新任务时,可能由于线程队列已满或无法成功执行任务,这时候 ThreadPoolExecutor会调用handler的 rejectedExecution的方法,默认会抛出RejectedExecutionException
ThreadPoolExecutor执行任务大致遵循如下规则:
  1. 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务Android的线程和线程池
  2. 如果线程池中的线程数量已经达到或超过核心线程数量,那么任务会被插入到任务队列中排队等待执行
  3. 如果步骤2中无法将任务插入到任务队列中,往往是因为任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务
  4. 如果步骤3中线程数量达到线程池规定的最大值,线程池会拒绝执行任务,并会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者
AsyncTask的THREAD_POOL_EXECUTOR线程池配置:
  1. 核心线程数等于CPU核心数+1
  2. 线程池最大线程数为CPU核心数的2倍+1
  3. 核心线程无超时机制,非核心线程的闲置超时时间为1秒
  4. 任务队列容量是128

常见的4个线程池

  • 1、FixedThreadPool :线程数量固定的线程池,当所有线程都处于活动状态时,新任务会处于等待状态,只有核心线程并且不会回收(无超时机制),能快速的响应外界请求。
  • 2、CachedThreadPool :线程数量不定的线程池,最大线程数Integer.MAX_VALUE(相当于任意大),当所有线程都处于活动状态时,会创建新线程来处理任务;线程池的空闲进程超时时长为60秒,超过就会被回收;任何任务都会被立即执行,适合执行大量的耗时较少的任务。
  • 3、ScheduledThreadPool :核心线程数量固定,非核心线程数量无限制,非核心线程闲置时会被立刻回收,用于执行定时任务和具有固定周期的重复任务。
  • 4、SingleThreadExecutor :只有一个核心线程,所有任务都在这个线程中串行执行,不需要处理线程同步问题。

相关资料