本文梳理自:https://lrh1993.gitbooks.io/android_interview_guide/content/

一、Activity全方位解析

1、横竖屏切换生命周期(意外中止生命周期)

onPause()->onSaveInstanceState()-> onStop()->onDestroy()->onCreate()->onStart()->onRestoreInstanceState->onResume()

image

  • onCreateonRestoreInstanceState方法来恢复Activity的状态的区别:

    • onRestoreInstanceState回调则表明其中Bundle对象非空,不用加非空判断
  • onSaveInstanceState方法调用时机

    • 在onStop之前,和onPause没有既定的时序关系
    • 该方法只在Activity被异常终止的情况下调用

2、Activity A 启动Activity B的生命周期方法调用的问题

  • A的onPause() -> B的onCreate() -> B的onStart() -> B的onResume() -> B显示,同时A在屏幕上不可见 -> A的onStop()

  • onPause中不能进行耗时操作,会影响到新Activity的显示。因为onPause必须执行完,新的Activity的onResume才会执行

3、onNewIntent(Intent intent)

  • 参数Intent是启动复写onNewIntent的Activity时传入的Intent

  • 在SingleTask/SingleInstance/SingleTop中都会回调

  • 重新启动自身:onPause -> onNewIntent -> onResume

    • onRestart()不会执行

4、SingleTask()模式

  • 可以在AndroidManifest中通过TaskAffinity指定任务栈
    • taskAffinity属性的值为字符串,且中间必须有包名分隔符

    • 只能搭配SingleTask()启动模式和allowTaskReparenting属性配对使用

二、Service全方位解析

1、Service种类

  • (1)按运行进程分

    • 本地服务(Local Service):依附于主进程

      • 主进程被Kill后,服务就会中止
      • 应用:音乐播放器
    • 远程服务(Remote Service):独立进程,不依附于主进程

      • 配置:AndroidManifest中对应的Service标签下添加android:process="remote"
      • 应用:一些提供系统服务的Service
  • (2)按运行类型分

    • 前台服务:
      • 会在通知栏显式onGoing的Notification
    • 后台服务:
      • 默认为后台服务
      • 不会在通知栏显式
  • (3)按使用方式分类

类别区别
startService启动的服务主要用于启动一个服务执行后台任务,不进行通信。停止服务使用stopService
bindService启动的服务方法启动的服务要进行通信。停止服务使用unbindService
同时使用startServicebindService启动的服务停止服务应该同时使用stopServiceunbindService

2、Service的生命周期

image

3、三种不同的Service启动方式

  • (1)startService / stopService主要用于不可交互的后台服务

    • 生命周期顺序:onCreate->onStartCommand->onDestroy

    • 方法触发次数

      • onCreate只会在第一次startService时被触发

      • 每次 startService 都会触发onStartCommand

      • 不论 startService 多少次,stopService 一次就会停止服务

    • Service结束时机:

      • 如果系统资源不足,android系统也可能结束服务

      • 调用stopService,或自身的stopSelf方法

      • 在设置中,通过应用->找到自己应用->停止

    • 使用场景:

      • 如果你只是想要启动一个后台服务长期进行某项任务
  • (2)bindService / unbindService主要用于可交互的后台服务

    • 生命周期顺序:onCreate->onBind->onUnBind->onDestroy

    • 方法触发次数:

      • onCreateonBind方法只会在第一次bindService时执行

      • 之后每次 bindService 都不会触发任何回调

      • onStartCommand方法始终不会调用

    • Service结束时机:

      • 调用unbindService来接触绑定、断开连接

      • 调用该Service的Context不存在了(如Activity被Finish——即通过bindService启动的Service的生命周期依附于启动它的Context

    • 使用场景

      • 如果你想要与正在运行的 Service 取得联系
  • (3)混合型(两种方式一起用)

    • 调用unBindService将不会停止Service,必须调用stopService或Service自身的stopSelf来停止服务

4、前台服务

  • 创建与销毁

    • 启动:startForeground()
    • 销毁:首先使用stopForeground()将前台服务降为后台服务,然后通过stopService()将前台服务中止
  • TaskStackBuilder在Notification通知栏中的使用

    • 应用:指定点击通知跳转页面后回退到主界面
    mBuilder = new NotificationCompat.Builder(this)
        .setContent(view)
        .setSmallIcon(R.drawable.icon).setTicker("新资讯")
        .setWhen(System.currentTimeMillis())
        .setOngoing(false)
        .setAutoCancel(true);
    Intent intent = new Intent(this, NotificationShow.class);
    TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
    stackBuilder.addParentStack(NotificationShow.class);
    stackBuilder.addNextIntent(intent);
    PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
    //PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    mBuilder.setContentIntent(pendingIntent);
    
    • (1)用TaskStackBuilder.create(this)创建一个stackBuilder实例
    • (2)接下来addParentStack()为跳转后的Activity添加一个父Activity(在activity中的manifest中添加parentActivityName即可)
      <activity
          android:name="com.lvr.service.NotificationShow"  
          android:parentActivityName=".MainActivity" >
      </activity>
      

三、BroadCastReceiver全方位解析

1、应用场景

  • 不同组件之间通信(包括应用内 / 不同应用之间)

  • 与 Android 系统在特定情况下的通信+

    • 如当电话呼入时、网络可用时
  • 多线程通信

2、实现原理

  • 广播使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型

    • 模型中有3个角色:
      • (1)消息订阅者(广播接收者)
      • (2)消息发布者(广播发布者)
      • (3)消息中心(AMS,即Activity Manager Service)
  • 广播发送者 和 广播接收者的执行 是 异步的

    • 广播接收者通过 消息循环 拿到此广播,并回调 onReceive()

image

3、具体使用

  • (1)定义广播接收者实体类

    • 继承BroadcastReceiver
    • 实现onReceive(Context context, Intent intent)
  • (2)注册广播接收器
    image

    • 静态注册(XML中的receiver标签)

      • android:exported=["true" | "false"]:决定此broadcastReceiver能否接收其他App的发出的广播。如果有intent-filter,默认值为true,否则为false

      • <action android:name="">:匹配intent-filter中的action,用于指定广播接收器将接收的广播类型

    • 动态注册(通过调用Context的registerReceiver()方法)

      • 动态广播最好在Activity的onResume()注册、onPause()注销

      • 有注册就必须有注销,否则会内存泄漏

  • (3)广播发送者向AMS发送广播(五类广播类型)

    • 类型一:普通广播(Normal Broadcast)

      • 开发者自身定义的Intent广播
      Intent intent = new Intent();
      //对应BroadcastReceiver中intentFilter的action
      intent.setAction(BROADCAST_ACTION);
      //发送广播
      sendBroadcast(intent);
      
    • 类型二:系统广播(System Broadcast)

      • 根据系统相关条件变化而自动发送广播

      • 每个广播都有特定的Intent - Filter(包括具体的action)

      • 只需要定义Reciver接受即可

    • 类型三:有序广播(Ordered Broadcast)

      • sendOrderedBroadcast(intent)

      • 定义:发送出去的广播被广播接收者按照先后顺序接收

      • 特点1:先接收的广播接收者可以对广播进行截断,即后接收的广播接收者不再接收到此广播;

      • 特点2:先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播

    • 类型四:粘性广播(Sticky Broadcast)

      • 在Android5.0 & API 21中已经失效
    • 类型五:App应用内广播(Local Broadcast)

      • 局部广播

      • 解决安全性&效率性问题

      • 将全局广播设为局部广播:1⃣
        注册时将exported设为false 2⃣ 在广播发送和接收时,增设相应权限permission,用于权限验证 3⃣ 发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中(intent.setPackage(packageName)

      • 使用封装好的LocalBroadcastManager类,使用方式上与全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将参数的context变成了LocalBroadcastManager的单一实例

四、ContentProvider全方位解析

  • 作用:跨进程通信
    • 也可用于进程内通信

image

  • 原理:ContentProvider的底层是采用 Android中的Binder机制

  • 理解:相当于各个进程与自身数据部分的一个中间件,各进程通过暴露自己的ContentProvider对数据进行统一、标准化的操作

  • 优点:

    • (1)安全:

      • 允许把自己的应用数据根据需求开放给 其他应用 进行 增、删、改、查,而不用担心因为直接开放数据库权限而带来的安全问题
    • (2)解耦了底层数据的存储方式:

      • 使得无论底层数据存储采用何种方式,外界对数据的访问方式都是统一的,这使得访问简单 & 高效

1、统一资源标识符(URI)

  • 定义:Uniform Resource Identifier,即统一资源标识符

  • 作用:唯一标识 ContentProvider & 其中的数据

image

  • 设置URI
    Uri uri = Uri.parse("content://com.carson.provider/User/1") 
    

2、MIME数据类型

  • 作用:指定某个扩展名的文件用某种应用程序来打开

    • eg:指定.html文件采用text应用程序打开
  • 格式:类型组成 = 类型 + 子类型

    • eg:text / html
  • ContentProvider根据 URI 返回MIME类型

    • ContentProvider.geType(uri);

3、ContentProvider类(对内操作)

  • 组织数据方式:表格

  • 主要方法(增删改查)

    • 供外部进程调用,运行在ContentProvider进程的Binder线程池中(不是主线程)

    • 得到数据类型,即返回当前 Url 所代表数据的MIME类型:public String getType(Uri uri)

    • (1)增:public Uri insert(Uri uri, ContentValues values)

    • (2)删:public int delete(Uri uri, String selection, String[] selectionArgs)

    • (3)改:public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)

    • (4)查:public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

  • ContentProvider类并不会直接与外部进程交互,而是通过ContentResolver类

4、ContentResolver类(对外交互)

  • 作用:统一管理不同 ContentProvider间的操作

    • 通过 URI 即可操作 不同的ContentProvider 中的数据

    • 外部进程通过 ContentResolver类 从而与ContentProvider类进行交互

  • 出现原因:一款应用可能存在多个Content Provider

  • 具体使用:

    • ContentProvider的增删改查调用相同

5、三个辅助ContentProvide的工具类

  • (1)ContentUris类

    • 作用:操作 URI
    • 核心方法有
      • withAppendedId():向URI追加一个id

        Uri uri = Uri.parse("content://cn.scu.myprovider/user") 
        Uri resultUri = ContentUris.withAppendedId(uri, 7);  
        // 最终生成后的Uri为:content://cn.scu.myprovider/user/7
        
      • parseId():从URL中获取ID

        Uri uri = Uri.parse("content://cn.scu.myprovider/user/7") 
        long personid = ContentUris.parseId(uri); 
        //获取的结果为:7
        
  • (2)UriMatcher类

    • 作用:

      • 在ContentProvider 中注册URI
      • 根据 URI 匹配 ContentProvider 中对应的数据表
    • 使用:

      // 步骤1:初始化UriMatcher对象
      UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
      //常量UriMatcher.NO_MATCH  = 不匹配任何路径的返回码
      // 即初始化时不匹配任何东西
      
      // 步骤2:在ContentProvider 中注册URI(addURI())
      int URI_CODE_a = 1;
      int URI_CODE_b = 2;
      matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a);
      matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b);
      // 若URI资源路径 = content://cn.scu.myprovider/user1 ,则返回注册码URI_CODE_a
      // 若URI资源路径 = content://cn.scu.myprovider/user2 ,则返回注册码URI_CODE_b
      
      // 步骤3:根据URI 匹配 URI_CODE,从而匹配ContentProvider中相应的资源(match())
      
      @Override
      public String getType (Uri uri){
          Uri uri = Uri.parse(" content://cn.scu.myprovider/user1");
      
          switch (matcher.match(uri)) {
              // 根据URI匹配的返回码是URI_CODE_a
              // 即matcher.match(uri) == URI_CODE_a
              case URI_CODE_a:
                  return tableNameUser1;
              // 如果根据URI匹配的返回码是URI_CODE_a,则返回ContentProvider中的名为tableNameUser1的表
              case URI_CODE_b:
                  return tableNameUser2;
              // 如果根据URI匹配的返回码是URI_CODE_b,则返回ContentProvider中的名为tableNameUser2的表
          }
      }
      
  • (3)ContentObserver类

    • 作用:观察 Uri引起ContentProvider 中的数据变化(增删改),并 通知外界(即访问该数据访问者)

    • 具体使用:

      // 步骤1:注册内容观察者ContentObserver
      getContentResolver().registerContentObserver(uri);
      // 通过ContentResolver类进行注册,并指定需要观察的URI
      
      // 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
      public class UserContentProvider extends ContentProvider {
          public Uri insert(Uri uri, ContentValues values) {
              db.insert("user", "userid", values);
              getContext().getContentResolver().notifyChange(uri, null);
            // 通知访问者
          }
      }
      
      // 步骤3:解除观察者
      getContentResolver().unregisterContentObserver(uri);
      // 同样需要通过ContentResolver类进行解除
      

五、Fragment详解

1、添加任务到回退栈

FragmentTransaction.addToBackStack(String tag)

  • 虽然实例不会被销毁,但是视图层次依然会被销毁。当再次返回该界面时,视图层仍旧是重新按照代码绘制视图

    • 会调用onDestoryViewonCreateView,只是未执行onDestroy

      • eg:即使加入了回退栈,其EditText中原先输入的还是会丢失
    • 如果不希望视图重绘(保持数据),就要使用hide/show,而不要用replace

2、transaction.replace()

  • 此方法是transaction.remove()然后transaction.add()的组合
    • transaction.remove(): 从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁

    • transaction.add(): 向Activity中添加一个Fragment

    • 直接使用add/replace/hide/show的话,都要commit其效果才会在屏幕上显示出来(ransatcion.commit() 提交事务)

3、Fragment与Activity通信

  • 三个方法:

    • (1)如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法

    • (2)如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作

    • (3)Fragment中可以通过getActivity()得到当前绑定的Activity的实例,然后进行操作

  • 优化:

    • 目标:降低Fragment与Activity的耦合,而且Fragment更不应该直接操作别的Fragment

    • 解决方法:通过在Fragment中定义回调接口,在Activity中实现接口来实现堆Fragment的统一管理

4、避免因Acvtivity发生重新启动时,Fragment也跟着不断重新创建大量耗费资源

  • 通过检查onCreate的参数Bundle savedInstanceState就可以判断,当前是否发生Activity的重新创建

    • 默认的savedInstanceState会存储一些数据,包括Fragment的实例
    • 判断只有在savedInstanceState==null时,才进行创建Fragment实例
  • Fragment也有onSaveInstanceState的方法,在此方法中进行保存数据,然后在onCreate或者onCreateView或者onActivityCreated进行恢复都可以

六、Android消息机制

image

  • 经典实现:
public class Activity extends android.app.Activity {
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            System.out.println(msg.what);
        }
    };
    @Override
    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                ...............耗时操作
                Message message = Message.obtain();
                message.what = 1;
                mHandler.sendMessage(message);
            }
        }).start();
    }
}
  • Handler线程切换的原理

    • 线程2持有在线程1中创建的Handler的引用

    • Handler接收消息是在创建它的线程当中

  • MessageQueue,Handler和Looper三者之间的关系

    • Looper是保存在ThreadLocal中的

      • 每个线程只能有一个Looper
    • 每个线程可以有多个Handler

      • 一个Looper可以处理来自多个Handler的消息
    • Looper中维护一个MessageQueue

  • 使用消息机制:

    • 初始化Looper:Looper.prepare()

    • 开启Looper:Looper.loop()

      • loop()进入循环模式,直到消息为空时退出循环:

      • 当遇到Message 时,读取MessageQueue的下一条Message,把Message分发给相应的target(handler)。

      • 当next()取出下一条消息时,队列中已经没有消息时,next()会无限循环,产生阻塞。等待MessageQueue中加入消息,然后重新唤醒。

    • 创建handler

    • 发送消息

      • 最终都调用了sendMessageAtTime(),在该方法内调用enqueueMessage()
    • 接收消息:next()

      • 原理:next()轻易不会返回null,当消息队列为空时,next方法会阻塞,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回继续轮询

      • nativePollOnce()是阻塞操作
        , nextPollTimeoutMillis代表下一个消息到来前,还需要等待的时长

      • 当nextPollTimeoutMillis = -1时,表示消息队列中无消息,会一直等待下去。

      Message next() {
          final long ptr = mPtr;
          if (ptr == 0) { //当消息循环已经退出,则直接返回
              return null;
          }
          int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
          int nextPollTimeoutMillis = 0;
          for (;;) {
              if (nextPollTimeoutMillis != 0) {
                  Binder.flushPendingCommands();
              }
              //阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回继续执行下面的Synchronized代码块
              nativePollOnce(ptr, nextPollTimeoutMillis);
              
              synchronized (this) {
                  final long now = SystemClock.uptimeMillis();
                  Message prevMsg = null;
                  Message msg = mMessages;
                  if (msg != null && msg.target == null) {
                      //当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,为空则退出循环。
                      do {
                          prevMsg = msg;
                          msg = msg.next;
                      } while (msg != null && !msg.isAsynchronous());
                  }
                  if (msg != null) {
                      if (now < msg.when) {
                          //当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长
                          nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                      } else {
                          // 获取一条消息,并返回
                          mBlocked = false;
                          if (prevMsg != null) {
                              prevMsg.next = msg.next;
                          } else {
                              mMessages = msg.next;
                          }
                          msg.next = null;
                          //设置消息的使用状态,即flags |= FLAG_IN_USE
                          msg.markInUse();
                          return msg;   //成功地获取MessageQueue中的下一条即将要执行的消息
                      }
                  } else {
                      //没有消息
                      nextPollTimeoutMillis = -1;
                  }
              //消息正在退出,返回null
                  if (mQuitting) {
                      dispose();
                      return null;
                  }
                  ...............................
          }
      }
      
    • 分发消息:dispatchMessage()

      • 优先级最高:Message的回调方法:message.callback.run()

      • 优先级次之:Handler中Callback的回调方法:Handler.mCallback.handleMessage(msg)

      • 优先级最低:Handler的默认方法:Handler.handleMessage(msg)

七、Android事件分发机制

  • 主要发生的Touch事件有如下四种:

    • MotionEvent.ACTION_DOWN:按下View(所有事件的开始)
    • MotionEvent.ACTION_MOVE:滑动View
    • MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
    • MotionEvent.ACTION_UP:抬起View(与DOWN对应)
  • 三个重要方法的关系(此为Activity事件分发中的dispatchTouchEvent()源码

// 点击事件产生后,会直接调用dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {

    //代表是否消耗事件
    boolean consume = false;


    if (onInterceptTouchEvent(ev)) {
    //如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
    //则该点击事件则会交给当前View进行处理
    //即调用onTouchEvent ()方法去处理点击事件
      consume = onTouchEvent (ev) ;

    } else {
      //如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
      //则该点击事件则会继续传递给它的子元素
      //子元素的dispatchTouchEvent()就会被调用,重复上述过程
      //直到点击事件被最终处理为止
      consume = child.dispatchTouchEvent (ev) ;
    }

    return consume;
   }
  • 默认事件传递情况:(如图下所示)

    • 从Activity A---->ViewGroup B—>View C,从上往下调用dispatchTouchEvent()
    • 再由View C—>ViewGroup B —>Activity A,从下往上调用onTouchEvent()(子类的onTouchEvent会return super.onTouchEvent()
  • 假设View C希望处理这个点击事件

    • C被设置成可点击的(Clickable)

    • 覆写C的onTouchEvent方法返回true

  • 拦截DOWN的后续事件:假设ViewGroup B没有拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件。

    • DOWN事件传递到C的onTouchEvent方法,返回了true。

    • 在后续到来的第一个MOVE事件,B的onInterceptTouchEvent方法返回true拦截该MOVE事件,但该事件并没有传递给B;这个MOVE事件将会被系统变成一个CANCEL事件传递给C的onTouchEvent方法

    • 后续又来了一个MOVE事件,该MOVE事件才会直接传递给B的onTouchEvent()

      • 后续事件将直接传递给B的onTouchEvent()处理
      • 后续事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
    • C再也不会收到该事件列产生的后续事件。

  • 事件分发机制的三部分组成

    • Activity对点击事件的分发机制
    • ViewGroup对点击事件的分发机制
    • View对点击事件的分发机制

1、Activity对点击事件的分发机制

  • 当一个点击事件发生时,调用顺序如下(Activity向ViewGroup的传递
    • 事件最先传到Activity的dispatchTouchEvent()进行事件分发

    • 调用Window类实现类PhoneWindow的superDispatchTouchEvent()

    • 调用DecorView的superDispatchTouchEvent()

    • 最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的dispatchTouchEvent()

2、ViewGroup对点击事件的分发机制

1、判断ViewGroup是否对事件拦截
  • ViewGroup的dispatchTouchEvent()源码分析
// 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN,并且将mFirstTouchTarget赋值,才进入此区域,主要功能是拦截器
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
            //disallowIntercept:是否禁用事件拦截的功能(默认是false),即不禁用
            //可以在子View通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改,不让该View拦截事件
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //默认情况下会进入该方法
            if (!disallowIntercept) {
                //调用拦截方法
                intercepted = onInterceptTouchEvent(ev); 
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            // 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
            intercepted = true;
        }
  • 进入拦截判断的两个条件

    • (1)如果当前事件的MotionEvent.ACTION_DOWN,则进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截

    • (2)如果mFirstTouchTarget != null,即已经发生过MotionEvent.ACTION_DOWN,并且该事件已经有ViewGroup的子View进行处理了,那么也进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截

  • 如果不是以上两种情况,即已经是MOVE或UP事件了,并且之前的事件没有对象进行处理,则将intercepted设置成true,开始拦截接下来的所有事件

    • 解释了如果子View的onTouchEvent()方法返回false(ViewGroup中的mFirstTouchTarget为null),那么接下来的一些列事件都不会交给他处理
2、ViewGroup不拦截时继续向下分发的逻辑
  • dispatchTransformedTouchEvent()

    if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
    
    • 由于其中传递的child不为空,所以就会调用子元素的dispatchTouchEvent()

    • 如果子元素的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就会被赋值,同时跳出找寻newTouchTarget(即上面的mFirstTouchTarget)的for循环

  • mFirstTouchTarget赋值

    //添加TouchTarget,则mFirstTouchTarget != null。
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    //表示以及分发给NewTouchTarget
    alreadyDispatchedToNewTouchTarget = true;
    
    • 其中在addTouchTarget(child, idBitsToAssign);内部完成mFirstTouchTarget被赋值

    • 如果mFirstTouchTarget为空,将会让ViewGroup默认拦截所有操作

    • 如果遍历所有子View或ViewGroup,都没有消费事件。ViewGroup会自己处理事件

3、View对点击事件的分发机制

  • View的dispatchTouchEvent()源码分析
    public boolean dispatchTouchEvent(MotionEvent event) {  
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                mOnTouchListener.onTouch(this, event)) {  
            return true;  
        }  
        return onTouchEvent(event);  
    }
    
    • 只有三个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent(event)方法
3.1 三个判断条件分析
  • (1)mOnTouchListener!= null

    //mOnTouchListener是在View类下setOnTouchListener方法里赋值的
    public void setOnTouchListener(OnTouchListener l) { 
    
    //即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
        mOnTouchListener = l;  
    }
    
  • (2)(mViewFlags & ENABLED_MASK) == ENABLED

    • 该条件是判断当前点击的控件是否enable
    • 由于很多View默认是enable的,因此该条件恒定为true
  • (3)mOnTouchListener.onTouch(this, event)

    • 回调控件注册Touch事件时的onTouch方法
    //手动调用设置
    button.setOnTouchListener(new OnTouchListener() {  
    
        @Override  
        public boolean onTouch(View v, MotionEvent event) {  
    
            return false;  
        }  
        
    });
    
    • 如果在onTouch方法返回true,就会让上述三个条件全部成立,从而整个方法直接返回true。

    • 如果在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。

3.2 onTouchEvent(event)的源码
public boolean onTouchEvent(MotionEvent event) {  
    final int viewFlags = mViewFlags;  
    if ((viewFlags & ENABLED_MASK) == DISABLED) {  
        // A disabled view that is clickable still consumes the touch  
        // events, it just doesn't respond to them.  
        return (((viewFlags & CLICKABLE) == CLICKABLE ||  
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
    }  
    if (mTouchDelegate != null) {  
        if (mTouchDelegate.onTouchEvent(event)) {  
            return true;  
        }  
    }  
     //如果该控件是可以点击的就会进入到下两行的switch判断中去;

    if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
    //如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。

        switch (event.getAction()) {  
            case MotionEvent.ACTION_UP:  
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
               // 在经过种种判断之后,会执行到关注点1的performClick()方法。
               //请往下看关注点1
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
                    // take focus if we don't have it already and we should in  
                    // touch mode.  
                    boolean focusTaken = false;  
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
                        focusTaken = requestFocus();  
                    }  
                    if (!mHasPerformedLongPress) {  
                        // This is a tap, so remove the longpress check  
                        removeLongPressCallback();  
                        // Only perform take click actions if we were in the pressed state  
                        if (!focusTaken) {  
                            // Use a Runnable and post this rather than calling  
                            // performClick directly. This lets other visual state  
                            // of the view update before click actions start.  
                            if (mPerformClick == null) {  
                                mPerformClick = new PerformClick();  
                            }  
                            if (!post(mPerformClick)) {  
            //关注点1
            //请往下看performClick()的源码分析
                                performClick();  
                            }  
                        }  
                    }  
                    if (mUnsetPressedState == null) {  
                        mUnsetPressedState = new UnsetPressedState();  
                    }  
                    if (prepressed) {  
                        mPrivateFlags |= PRESSED;  
                        refreshDrawableState();  
                        postDelayed(mUnsetPressedState,  
                                ViewConfiguration.getPressedStateDuration());  
                    } else if (!post(mUnsetPressedState)) {  
                        // If the post failed, unpress right now  
                        mUnsetPressedState.run();  
                    }  
                    removeTapCallback();  
                }  
                break;  
            case MotionEvent.ACTION_DOWN:  
                if (mPendingCheckForTap == null) {  
                    mPendingCheckForTap = new CheckForTap();  
                }  
                mPrivateFlags |= PREPRESSED;  
                mHasPerformedLongPress = false;  
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                break;  
            case MotionEvent.ACTION_CANCEL:  
                mPrivateFlags &= ~PRESSED;  
                refreshDrawableState();  
                removeTapCallback();  
                break;  
            case MotionEvent.ACTION_MOVE:  
                final int x = (int) event.getX();  
                final int y = (int) event.getY();  
                // Be lenient about moving outside of buttons  
                int slop = mTouchSlop;  
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                    // Outside button  
                    removeTapCallback();  
                    if ((mPrivateFlags & PRESSED) != 0) {  
                        // Remove any future long press/tap checks  
                        removeLongPressCallback();  
                        // Need to switch from pressed to not pressed  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                    }  
                }  
                break;  
        }  
//如果该控件是可以点击的,就一定会返回true
        return true;  
    }  
//如果该控件是不可以点击的,就一定会返回false
    return false;  
}
  • performClick()
    public boolean performClick() {  
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    
        if (mOnClickListener != null) {  
            playSoundEffect(SoundEffectConstants.CLICK);  
            mOnClickListener.onClick(this);  
            return true;  
        }  
        return false;  
    }
    
    • 只要mOnClickListener不为null,就会去调用onClick方法

      public void setOnClickListener(OnClickListener l) {  
          if (!isClickable()) {  
              setClickable(true);  
          }  
          mOnClickListener = l;  
      }
      
3.3 onTouch()的执行高于onClick()
  • (1)如果在回调onTouch()里返回false,就会让dispatchTouchEvent方法返回false,那么就会执行onTouchEvent();如果回调了setOnClickListener()来给控件注册点击事件的话,最后会在performClick()方法里回调onClick()

    • onTouch()返回false(该事件没被onTouch()消费掉) = 执行onTouchEvent() = 执行OnClick()
  • (2)如果在回调onTouch()里返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),即onClick()也不会执行;

    • onTouch()返回true(该事件被onTouch()消费掉) = dispatchTouchEvent()返回true(不会再继续向下传递) = 不会执行onTouchEvent() = 不会执行OnClick()

总结:

1、onTouch()onTouchEvent()的区别
  • 这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。
    • 如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。
2、Touch事件的后续事件(MOVE、UP)层级传递
  • 分析三个重要方法得知

    • dispatchTouchEvent()和 onTouchEvent()消费事件、终结事件传递(返回true)

    • 而onInterceptTouchEvent 并不能消费事件,它相当于是一个分叉口起到分流导流的作用,对后续的ACTION_MOVE和ACTION_UP事件接收起到非常大的作用

      • 接收了ACTION_DOWN事件的函数不一定能收到后续事件(ACTION_MOVE、ACTION_UP)(接收到Down后,在OnClick里返回false?)
  • ACTION_MOVE和ACTION_UP事件的传递结论

    • 如果在某个对象(Activity、ViewGroup、View)的dispatchTouchEvent()消费事件(返回true),那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP

    • 如果在某个对象(Activity、ViewGroup、View)的onTouchEvent()消费事件(返回true),那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent()并结束本次事件传递过程。

八、AsyncTask详解

1、概述

  • 是一个抽象类
    public abstract class AsyncTask<Params, Progress, Result>
    
    • Params:传入参数
    • Progress:任务进度参数类型
    • Result:结果类型

2、使用

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {  

    @Override  
    protected void onPreExecute() {  
        progressDialog.show();  
    }  

    @Override  
    protected Boolean doInBackground(Void... params) {  
        try {  
            while (true) {  
                int downloadPercent = doDownload();  
                publishProgress(downloadPercent);  
                if (downloadPercent >= 100) {  
                    break;  
                }  
            }  
        } catch (Exception e) {  
            return false;  
        }  
        return true;  
    }  

    @Override  
    protected void onProgressUpdate(Integer... values) {  
        progressDialog.setMessage("当前下载进度:" + values[0] + "%");  
    }  

    @Override  
    protected void onPostExecute(Boolean result) {  
        progressDialog.dismiss();  
        if (result) {  
            Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();  
        } else {  
            Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();  
        }  
    }  
}
  • 继承抽象类并实现四个方法后,通过new DownloadTask().execute();创建一个实例来开始任务

    • AsyncTask对象必须在UI线程中创建

      • InternalHandler是一个静态类,为了能够将执行环境切换到主线程,因此这个类必须在主线程中进行加载。所以变相要求AsyncTask的类必须在主线程中进行加载。
    • 一个任务实例只能执行一次,如果执行第二次将会抛出异常

  • 通过在doInBackground中显式调用publishProgress(downloadPercent);来回调onProgressUpdate

3、源码分析

(1)构造函数
public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return result;
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }
  • 仅创建了两个实例
    • mWorker中的call()方法执行了耗时操作,即result = doInBackground(mParams);
    • 然后把执行得到的结果通过postResult(result);,传递给内部的Handler跳转到主线程中
(2)将任务加载进线程池:execute()
  • execute()调用了executeOnExecutor()方法

  • executeOnExecutor()方法中,执行耗时任务是在exec.execute(mFuture)

    • exec是SerialExecutor类,
  • SerialExecutor 是个静态内部类

    • 是所有实例化的AsyncTask对象公有的

    • SerialExecutor 内部维持了一个队列,通过锁使得该队列保证AsyncTask中的任务是串行执行的,即多个任务需要一个个加到该队列中,然后执行完队列头部的再执行下一个

    • 调用 scheduleNext()方法,调用THREAD_POOL_EXECUTOR执行队列头部的任务

  • 在线程池中调用execute()方法执行具体的耗时任务(即构造函数Call方法中所定义的内容)

  • 实行完耗时任务后,postResult()发送消息给InternalHandler

    • 如果收到的消息是MESSAGE_POST_RESULT(即执行完了doInBackground()方法并传递结果),那么就调用finish()方法。
    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
    

4、AsyncTask使用不当的后果

(1)生命周期

AsyncTask不与任何组件绑定生命周期,所以在Activity/或者Fragment中创建执行AsyncTask时,最好在Activity/Fragment的onDestory()调用 cancel(boolean);

(2)内存泄漏

如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。

(3)结果丢失

屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask(非静态的内部类)会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

九、HandlerThread详解

一、概述

1、使用场景
  • 避免重复开启销毁线程解决方案:

    • 线程池
    • HandlerThread
  • HandlerThread可以用来执行多个耗时操作,而不需要多次开启线程

    • 里面是采用HandlerLooper实现的
2、理解:
  • 每一个HandlerThread对应一个Thread

  • 存在意义:是一个对Looper做了内部封装的Thread

    • 每次创建时,不需要再Looper.prepare()等操作
  • 一个继承了线程的类(HandlerThread),搭配Handler使用,将Thread的Looper绑定到Handler上,然后在Handler的handlerMessage方法中检查是否有耗时逻辑需要处理

3、基本使用
  • (1)创建Handler的实例对象

    HandlerThread handlerThread = new HandlerThread("myHandlerThread");
    
    • 参数为线程名
  • (2)启动创建的HandlerThread线程

    handlerThread.start();
    
  • (3)通过handlerThread将线程的looper与Handler绑定到一起

    mThreadHandler = new Handler(mHandlerThread.getLooper()) {
        @Override
        public void handleMessage(Message msg) {
            checkForUpdate();
            if(isUpdate){
                mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
            }
        }
    };
    
  • 例子:


public class MainActivity extends AppCompatActivity {
    private static final int MSG_UPDATE_INFO = 0x100;
    Handler mMainHandler = new Handler();
    private TextView mTv;
    private Handler mThreadHandler;
    private HandlerThread mHandlerThread;
    private boolean isUpdate = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTv = (TextView) findViewById(R.id.tv);
        initHandlerThread();
    }

    private void initHandlerThread() {
        mHandlerThread = new HandlerThread("xujun");
        mHandlerThread.start();
        mThreadHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                checkForUpdate();
                if (isUpdate) {
                    mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
                }
            }
        };
    }

    /**
     * 模拟从服务器解析数据
     */
    private void checkForUpdate() {
        try {
            //模拟耗时
            Thread.sleep(1200);
            mMainHandler.post(new Runnable() {
                @Override
                public void run() {
                    String result = "实时更新中,当前股票行情:<font color='red'>%d</font>";
                    result = String.format(result, (int) (Math.random() * 5000 + 1000));
                    mTv.setText(Html.fromHtml(result));
                }
            });
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onResume() {
        isUpdate = true;
        super.onResume();
        mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
    }

    @Override
    protected void onPause() {
        super.onPause();
        isUpdate = false;
        mThreadHandler.removeMessages(MSG_UPDATE_INFO);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandlerThread.quit();
        mMainHandler.removeCallbacksAndMessages(null);
    }
}

二、源码分析

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        //持有锁机制来获得当前线程的Looper对象
        synchronized (this) {
            mLooper = Looper.myLooper();
            //发出通知,当前线程已经创建mLooper对象成功,这里主要是通知getLooper方法中的wait
            notifyAll();
        }
        //设置线程的优先级别
        Process.setThreadPriority(mPriority);
        //这里默认是空方法的实现,我们可以重写这个方法来做一些线程开始之前的准备,方便扩展
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        // 直到线程创建完Looper之后才能获得Looper对象,Looper未创建成功,阻塞
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

    /**
     * Returns the identifier of this thread. See Process.myTid().
     */
    public int getThreadId() {
        return mTid;
    }
}
(1)使用HandlerThread时必须调用start()方法
  • 调用start()方法之后,才可以将HandlerThread和handler绑定在一起

  • 原因:就是我们是在run()方法才开始初始化我们的looper,而我们调用HandlerThread的start()方法的时候,线程会交给虚拟机调度,由虚拟机自动调用run方法

2、为什么要使用锁机制和notifyAll()
  • 原因:在获得mLooper对象的时候存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值。
    • 这里等待方法wait和run方法中的notifyAll方法共同完成同步问题
3、quit与quitSafe
  • 两个方法最终都会调用MessageQueue的quit(boolean safe)方法
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;
        //安全退出调用这个方法
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {//不安全退出调用这个方法
            removeAllMessagesLocked();
        }
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}
  • (1)不安全的退出调用removeAllMessagesLocked()

    • 会遍历Message链表,移除所有信息的回调,并重置为null
    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }
    
  • (2)安全的退出会调用removeAllFutureMessagesLocked()

    • 会根据Message.when这个属性,判断我们当前消息队列是否正在处理消息,没有正在处理消息的话,直接移除所有回调,正在处理的话,等待该消息处理处理完毕再退出该循环

    • 因此说quitSafe()是安全的,而quit()方法是不安全的

      • quit方法不管是否正在处理消息,直接移除所有回调
    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            //判断当前队列中的消息是否正在处理这个消息,没有的话,直接移除所有回调
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {//正在处理的话,等待该消息处理处理完毕再退出该循环
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }
    

十、IntentService详解

  • 定义:IntentService是Android里面的一个封装类,继承自四大组件之一的Service(是一种Service)。

1、概述

(1)与普通线程的区别
  • IntentService内部采用了HandlerThread实现,作用类似于后台线程

  • 与后台线程相比,IntentService是一种后台服务,优势是:优先级高(不容易被系统杀死),从而保证任务的执行

    • 对于后台线程,若进程中没有活动的四大组件,则该线程的优先级非常低,容易被系统杀死,无法保证任务的执行
(2)与普通Service区别
  • 不建议在Service中编写耗时的逻辑和操作,否则会引起ANR;

  • 从属性 & 作用上来说

类别特性
Service依赖于应用程序的主线程(不是独立的进程 or 线程)
IntentService创建一个工作线程来处理多线程任务
  • 从关闭方式来说
类别特性
Service需要主动调用stopSelf()来结束服务
IntentService不需要主动关闭,在所有intent被处理完后,系统会自动关闭服务
(3)使用场景
  • 作用:处理异步请求,实现多线程

  • 特征:线程任务需要按顺序、在后台执行的使用场景

    • eg:最常见的场景:离线下载
  • 不适合场景:由于所有的任务都在同一个Thread looper里面来做,所以不符合多个数据同时请求的场景。

(4)工作流程

image

  • 若启动IntentService多次,那么每个耗时操作则以队列的方式在IntentService的onHandleIntent回调方法中依次执行,执行完自动结束。

2、使用(三步)

(1)定义IntentService的子类:传入线程名称、复写onHandleIntent()方法(执行耗时逻辑)
package com.example.carson_ho.demoforintentservice;

import android.app.IntentService;
import android.content.Intent;
import android.util.Log;

/**
 * Created by Carson_Ho on 16/9/28.
 */
public class myIntentService extends IntentService {

    /*构造函数*/
    public myIntentService() {
        //调用父类的构造函数
        //构造函数参数=工作线程的名字
        super("myIntentService");

    }

    /*复写onHandleIntent()方法*/
    //实现耗时任务的操作
    @Override
    protected void onHandleIntent(Intent intent) {
        //根据Intent的不同进行不同的事务处理
        String taskName = intent.getExtras().getString("taskName");
        switch (taskName) {
            case "task1":
                Log.i("myIntentService", "do task1");
                break;
            case "task2":
                Log.i("myIntentService", "do task2");
                break;
            default:
                break;
        }
    }


    @Override
    public void onCreate() {
        Log.i("myIntentService", "onCreate");
        super.onCreate();
    }

    /*复写onStartCommand()方法*/
    //默认实现将请求的Intent添加到工作队列里
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("myIntentService", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i("myIntentService", "onDestroy");
        super.onDestroy();
    }
}
(2)在Manifest.xml中注册服务
<service android:name=".myIntentService">
    <intent-filter>
        <action android:name="cn.scu.finch"/>
    </intent-filter>
</service>
(3)在Activity中开启Service服务
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

        //同一服务只会开启一个工作线程
        //在onHandleIntent函数里依次处理intent请求。

        Intent i = new Intent("cn.scu.finch");
        Bundle bundle = new Bundle();
        bundle.putString("taskName", "task1");
        i.putExtras(bundle);
        startService(i);

        Intent i2 = new Intent("cn.scu.finch");
        Bundle bundle2 = new Bundle();
        bundle2.putString("taskName", "task2");
        i2.putExtras(bundle2);
        startService(i2);

        startService(i);  //多次启动
    }
}
  • 实现结果:
    image

三、源码分析

(1)IntentService如何单独开启一个新的工作线程
// IntentService源码中的 onCreate() 方法
@Override
public void onCreate() {
    super.onCreate();
    // HandlerThread继承自Thread,内部封装了 Looper
    //通过实例化HandlerThread新建线程并启动
    //所以使用IntentService时不需要额外新建线程
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

    //获得工作线程的 Looper,并维护自己的工作队列
    mServiceLooper = thread.getLooper();
    //将上述获得Looper与新建的mServiceHandler进行绑定
    //新建的Handler是属于工作线程的。
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

private final class ServiceHandler extends Handler {

    public ServiceHandler(Looper looper) {
        super(looper);
    }

    // IntentService的handleMessage方法把接收的消息交给onHandleIntent()处理
    // onHandleIntent()是一个抽象方法,使用时需要重写的方法
    @Override
    public void handleMessage(Message msg) {
        // onHandleIntent 方法在工作线程中执行,执行完调用 stopSelf() 结束服务。
        onHandleIntent((Intent) msg.obj);
        //onHandleIntent 处理完成后 IntentService会调用 stopSelf() 自动停止。
        stopSelf(msg.arg1);
    }
}

// onHandleIntent()是一个抽象方法,使用时需要重写的方法
@WorkerThread
protected abstract void onHandleIntent(Intent intent);
(2)IntentService如何通过onStartCommand()传递给服务intent被依次插入到工作队列中
public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

public void onStart(Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    //把 intent 参数包装到 message 的 obj 中,然后发送消息,即添加到消息队列里
    //这里的Intent 就是启动服务时startService(Intent) 里的 Intent。
    msg.obj = intent;
    
    //每次startService()的时候,就会最终发送消息给onCreate()中的handleMessage(),然后回调被复写的onHandleIntent处理耗时逻辑
    mServiceHandler.sendMessage(msg);
}

//清除消息队列中的消息
@Override
public void onDestroy() {
    mServiceLooper.quit();
}

四、总结

(1)实现原理
  • IntentService本质是采用Handler & HandlerThread方式:
    • 通过HandlerThread单独开启一个名为IntentService的线程

    • 创建一个名叫ServiceHandler的内部Handler

    • 把内部Handler与HandlerThread所对应的子线程进行绑定

    • 通过onStartCommand()传递给服务intent,依次插入到工作队列中,并逐个发送给onHandleIntent()

    • 通过onHandleIntent()来依次处理所有Intent请求对象所对应的任务

(2)使用逻辑
  • 调用时传入不同的Intent(比如用Action区分)

  • 复写方法onHandleIntent(),在里面根据Intent的不同进行不同的线程操作

(3)注意
  • 工作任务队列是顺序执行的

    • 如果一个任务正在IntentService中执行,此时你再发送一个新的任务请求,这个新的任务会一直等待直到前面一个任务执行完毕才开始执行。
  • 原因:

    • 由于onCreate() 方法只会调用一次,所以只会创建一个工作线程

    • 当多次调用 startService(Intent) 时(onStartCommand也会调用多次)其实并不会创建新的工作线程,只是把消息加入消息队列中等待执行,所以多次启动 IntentService 会按顺序执行事件

    • 如果服务停止,会清除消息队列中的消息,后续的事件得不到执行。

      • 因为源码中调用的是quit()而不是quitSafe()

十一、LruCache原理解析

1、概述

  • 关键点:使用了Lru(最近最少使用)算法

  • 基本理解

    • LruCache是个泛型类

    • 主要算法原理是把最近使用的对象用强引用(即我们平常使用的对象引用方式)存储在 LinkedHashMap

    • 当缓存满时,把最近最少使用的对象从内存中移除,并提供了get和put方法来完成缓存的获取和添加操作。

2、使用

eg:图片缓存

(1)设置LruCache缓存的大小,一般为当前进程可用容量的1/8
  • 缓存的总容量和每个缓存对象的大小所用单位要一致
(2)重写sizeOf方法,计算出要缓存的每张图片的大小
int maxMemory = (int) (Runtime.getRuntime().totalMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight() / 1024;
    }
};

3、原理解析

(1) 核心思想:
  • 维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的
    • 一直没访问的对象,将放在链表尾,即将被淘汰
    • 最近访问的对象将放在链表头,最后被淘汰

image

(2)使用LinkedHashMap(HashMap+双向链表)实现
  • LinkedHashMap 继承自 HashMap

    • HashMap实现Map接口
  • 双向链表的结构可以实现访问顺序和插入顺序,使得LinkedHashMap中的对按照一定顺序排列

    • LinkedHashMap的构造函数指定实现顺序
    //其中accessOrder设置为true则为访问顺序,为false,则为插入顺序
    
    public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }
    
    • 例子:设置为访问顺序时(accessOrder = true)
      • 访问顺序:链表头插入,链表尾删除,访问时从链表尾访问(类似于队列)
      public static final void main(String[] args) {
          LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(0, 0.75f, true);
          map.put(0, 0);
          map.put(1, 1);
          map.put(2, 2);
          map.put(3, 3);
          map.put(4, 4);
          map.put(5, 5);
          map.put(6, 6);
          map.get(1);
          map.get(2);
      
          for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
              System.out.println(entry.getKey() + ":" + entry.getValue());
      
          }
      }
      
      • 输出
        0:0  //链表尾
        3:3
        4:4
        5:5
        6:6
        1:1
        2:2 //链表头
        
  • 使用HashMap+双向链表好处:

    • 双向链表用于LRU中数据的存储和对使用时间新旧的维护

      • 在链表头的是最新使用的。
      • 在尾部的是最旧的。也是下次要清除的。
      • 如果加入的值是链表内存在的则要移动到头部
    • HashMap是来配合双向链表,用于减少时间复杂度

      • 它是可以快速的(O(1)的时间)定位,链表中某个值是否存在
      • 要不然需要遍历双向链表,时间复杂度为O(n) n为链表长度)
  • 不使用单链表原因:

    • 获取前驱时需要遍历,时间效率下降
      • 虽然市面上单链表使用较多,因为单链表相较于双向链表虽然牺牲了时间效率,但是因为没有前驱,所以节省了空间
(3)实现插入和获取的put()/get()与他们分别对应的trimToSize()与recordAccess()
  • 1⃣put()方法
    • 在添加过缓存对象后,调用 trimToSize()方法,来判断缓存是否已满,如果满了就要删除近期最少使用的算法
public final V put(K key, V value) {
         //不可为空,否则抛出异常
    if (key == null || value == null) {
        throw new NullPointerException("key == null || value == null");
    }
    V previous;
    synchronized (this) {
            //插入的缓存对象值加1
        putCount++;
            //增加已有缓存的大小
        size += safeSizeOf(key, value);
           //向map中加入缓存对象(put返回value)
        previous = map.put(key, value);
            //如果已有缓存对象,则缓存大小恢复到之前
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }
        //entryRemoved()是个空方法,可以自行实现
    if (previous != null) {
        entryRemoved(false, key, previous, value);
    }
        //调整缓存大小(关键方法)
    trimToSize(maxSize);
    return previous;
}
  • 2⃣trimToSize()方法
public void trimToSize(int maxSize) {
    //死循环
    while (true) {
        K key;
        V value;
        synchronized (this) {
            //如果map为空并且缓存size不等于0或者缓存size小于0,抛出异常
            if (size < 0 || (map.isEmpty() && size != 0)) {
                throw new IllegalStateException(getClass().getName()
                    + ".sizeOf() is reporting inconsistent results!");
            }
            //如果缓存大小size小于最大缓存,或者map为空,不需要再删除缓存对象,跳出循环
            if (size <= maxSize || map.isEmpty()) {
                break;
            }
            //迭代器获取第一个对象,即队尾的元素,近期最少访问的元素
            Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
            key = toEvict.getKey();
            value = toEvict.getValue();
            //删除该对象,并更新缓存大小
            map.remove(key);
            size -= safeSizeOf(key, value);
            evictionCount++;
        }
        entryRemoved(true, key, value, null);
    }
}
  • 3⃣get()方法与recordAccess()
    • LruCache的get() -> LinkedHashMap的get()-> LinkedHashMap的recordAccess()实现排序
void recordAccess(HashMap<K,V> m) {
    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
    //判断是否是访问排序
    if (lm.accessOrder) {
        lm.modCount++;
        //删除此元素
        remove();
        //将此元素移动到队列的头部
        addBefore(lm.header);
    }
}

十二、Activity、Window、DecorView及ViewRoot的关系

1、四个部分的职能介绍

(1)Activity
  • 不负责视图控制,只是控制生命周期和处理事件

  • 一个Activity包含了一个Window

    • Window才是真正代表一个窗口
(2)Window
  • Window是一个抽象类,实际在Activity中持有的是其子类PhoneWindow

    • PhoneWindow中以内部类的形式持有一个DecorView
    • DecorView是View的根布局
  • Window是视图的承载器

    • Window 通过WindowManager将DecorView加载其中

    • Window将DecorView交给ViewRoot,进行视图绘制以及其他交互

(3)DecorView
  • 是FrameLayout的子类

  • 是Android视图树的根结点视图

  • 内容:包含一个竖直方向的LinearLayout,里面有三个部分

    • 1⃣ViewStub:延迟加载的视图(应该是设置ActionBar,根据Theme设置)

    • 2⃣标题栏:根据Theme设置,有的布局没有

    • 3⃣内容栏:Activity中setContentView所设置的布局文件,是内容栏唯一的子View

      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:fitsSystemWindows="true"
          android:orientation="vertical">
          <!-- Popout bar for action modes -->
          <ViewStub
              android:id="@+id/action_mode_bar_stub"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:theme="?attr/actionBarTheme" />
      
          <FrameLayout
              style="?android:attr/windowTitleBackgroundStyle"
              android:layout_width="match_parent"
              android:layout_height="?android:attr/windowTitleSize">
      
              <TextView
                  android:id="@android:id/title"
                  style="?android:attr/windowTitleStyle"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:background="@null"
                  android:fadingEdge="horizontal"
                  android:gravity="center_vertical" />
          </FrameLayout>
      
          <FrameLayout
              android:id="@android:id/content"
              android:layout_width="match_parent"
              android:layout_height="0dip"
              android:layout_weight="1"
              android:foreground="?android:attr/windowContentOverlay"
              android:foregroundGravity="fill_horizontal|top" />
      </LinearLayout>
      
(4)ViewRoot
  • 所有View的绘制事件分发等交互都是通过它来执行或传递的

    • View的三大流程(测量(measure),布局(layout),绘制(draw))均通过ViewRoot来完成

    • Android的所有触屏事件、按键事件、界面刷新等事件都是通过ViewRoot进行分发的

  • ViewRoot对应ViewRootImpl

    • 它是连接WindowManagerService和DecorView的纽带
  • ViewRoot并不属于View树的一份子

image

2、四部分的关系

  • Activity持有一个PhoneWindow(继承Window抽象类的实现类)

  • PhoneWindow以内部类的形式持有一个DecorView

  • Window通过WindowManager将DecorView加载

  • Window将DecorView交给ViewRoot,进行视图绘制及其他交互

3、DecorView的创建

(1)引出:
  • Activity的setContentView()方法中通过Window的方法来装载视图
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    
(2)创建流程:
  • Activity通过attach()方法生成PhoneWindow实例(获取Window对象)

    • attach()方法在ActivityThread的handleLaunchActivity()方法中先于生命周期开始执行
  • 然后Window在setContentView()中通过installDecor()方法创建DecorView

  • insatallDecor()中,通过generateDecor()方法从主题中获取样式,然后根据样式,通过decor.addView()加载布局到DecorView中

  • 然后从DecorView中通过findViewById获取mContentParent

    • mContentParent即布局中@android:id/content所对应的FrameLayout。

4、DecorView的显示

(1)引出
  • 目前DecorView还只是创建,但还没有显示

  • 只有在onResume()的时候才能显示到前台

(2)流程
  • 在ActivityThread中的handleLaunchActivity()中通过handleResumeActivity()来回调Activity.onResume()

    • 此时界面仍不可见
  • 然后在handleResumeActivity()中通过windowManager.addView()方法将DecorView添加进WindowManager中,并创建ViewRootImpl对象

    • WindowManager接口 -> WindowManagerImpl实现类 -> WindowManagerGlobal的addView()
    public void addView(View view, ViewGroup.LayoutParams params,
                    Display display, Window parentWindow) {
    
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    
        ......
    
        synchronized (mLock) {
    
            ViewRootImpl root;
            //实例化一个ViewRootImpl对象
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
    
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }
    
        ......
    
        try {
            //将DecorView交给ViewRootImpl
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
    
        }
    }
    
    • addView()方法中将DecorView交给ViewRootImpl,执行一系列的setView()方法
    • setView()方法中最终调用到performTraversals()方法,开始View的三大绘制流程
  • 最后在handleResumeActivity()中通过makeViesable()mDecor.setVisibility(View.VISIBLE)可见来使界面可见

5、ViewRoot对事件的分发

(1)引出
  • ViewRootImpl是个连接器

  • 通过硬件的感知来通知视图,进行用户之间的交互

  • 负责WindowManagerService与DecorView之间的通信

(2)流程
  • 硬件 -> ViewRootImpl -> DecorView -> PhoneWindow -> Activity

  • 用户点击屏幕产生一个触摸行为,这个触摸行为则是通过底层硬件来传递捕获,然后交给ViewRootImpl,接着将事件传递给DecorView,而DecorView再交给PhoneWindow,PhoneWindow再交给Activity,然后接下来就是我们常见的View事件分发了

十三、View测量、布局及绘制原理

image

1、Measure流程

  • setMeasuredDimension():设定View的宽高信息,完成View的测量操作

  • MeasureSpec的确定
    image

  • View的测量流程
    image

    • 在Measure过程中,ViewGroup一般是先测量子View的大小,然后再确定自身的大小

2、Layout流程

  • View的布局流程
    image

    • ViewGroup先在layout()中确定自己的布局,然后在onLayout()方法中再调用子View的layout()方法,让子View布局

3、Draw过程

  • View的绘制流程
    image

总结:

方法名特性
onMeasure()方法单一View,一般重写此方法,针对wrap_content情况,规定View默认的大小值,避免于match_parent情况一致。ViewGroup,若不重写,就会执行和单子View中相同逻辑,不会测量子View。一般会重写onMeasure()方法,循环测量子View。
onLayout()方法单一View,不需要实现该方法。ViewGroup必须实现,该方法是个抽象方法,实现该方法,来对子View进行布局。
onDraw()方法无论单一View,或者ViewGroup都需要实现该方法,因其是个空方法

十六、高效加载Bitmap的方式

1、高效加载Bitmap的流程

(1)将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片。
(2)从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数。
(3)根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。
(4)将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。

2、使用示例

  • 方法定义:
 public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        //加载图片
        BitmapFactory.decodeResource(res,resId,options);
        //计算缩放比
        options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWidth);
        //重新加载图片
        options.inJustDecodeBounds =false;
        return BitmapFactory.decodeResource(res,resId,options);
    }

    private static int calculateInSampleSize(BitmapFactory.Options options, int reqHeight, int reqWidth) {
        int height = options.outHeight;
        int width = options.outWidth;
        int inSampleSize = 1;
        if(height>reqHeight||width>reqWidth){
            int halfHeight = height/2;
            int halfWidth = width/2;
            //计算缩放比,是2的指数
            while((halfHeight/inSampleSize)>=reqHeight&&(halfWidth/inSampleSize)>=reqWidth){
                inSampleSize*=2;
            }
        }


        return inSampleSize;
    }
  • 使用:
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.mipmap.ic_launcher,100,100);

十九、Android Context详解

1、Context的源码

  • 描述:Context提供了关于应用环境全局信息的接口

  • 是一个抽象类,它的执行被Android系统所提供

public abstract class Context {
    /**
     * File creation mode: the default mode, where the created file can only
     * be accessed by the calling application (or all applications sharing the
     * same user ID).
     * @see #MODE_WORLD_READABLE
     * @see #MODE_WORLD_WRITEABLE
     */
    public static final int MODE_PRIVATE = 0x0000;

    public static final int MODE_WORLD_WRITEABLE = 0x0002;

    public static final int MODE_APPEND = 0x8000;

    public static final int MODE_MULTI_PROCESS = 0x0004;

    .
    .
    .
    }

2、Context与其实现类的关系图

image

  • ContextWrapper类

    • 如其名所言,这只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用

    • 同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象

    • 调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。

  • ContextThemeWrapper类

    • 如其名所言,其内部包含了与主题(Theme)相关的接口,

    • 只有Activity会需要主题

  • ContextImpl类

    • 是Context的具体实现类
  • Context在应用中的具体实现

    • (1)Application

    • (2)Activity

    • (3)Service

3、Context的作用域(什么情况下该用哪种类型的Context)

  • 凡是跟UI相关的,都应该使用Activity做为Context来处理

image

4、如何获取Context

  • (1)View.getContext

    • 返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
  • (2)Activity.getApplicationContext

    • 获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。

    • getApplication()区别:结果都是获取到Application的Context,但getApplication()只有在Activity和Service中才能调用的到

  • (3)ContextWrapper.getBaseContext()

    • 用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
  • (3)Activity.this

    • 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。

5、Context引起的内存泄漏

  • (1)错误的单例
    • 原因:生命周期不一致,持有已结束生命周期对象的引用导致无法GC
public class Singleton {
    private static Singleton instance;
    private Context mContext;

    private Singleton(Context context) {
        this.mContext = context;
    }

    public static Singleton getInstance(Context context) {
        if (instance == null) {
            instance = new Singleton(context);
        }
        return instance;
    }
}
  • (2)View持有Activity的引用
    • 原因:被static修饰的是常驻内存,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉
public class MainActivity extends Activity {
    private static Drawable mDrawable;

    @Override
    protected void onCreate(Bundle saveInstanceState) {
        super.onCreate(saveInstanceState);
        setContentView(R.layout.activity_main);
        ImageView iv = new ImageView(this);
        mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
        iv.setImageDrawable(mDrawable);
    }
}

6、正确使用Context

  • (1)当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。

    • Application的Context对象可以理解为随着进程存在的
  • (2)不要让生命周期长于Activity的对象持有到Activity的引用。

  • (3)尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐