Android开发艺术探索阅读笔记(持续完善更新中......)

Android开发艺术探索阅读笔记(持续完善更新中......)

1.Activity的生命周期和启动模式

1.1 启动模式

  • 有两种方式指定activity的启动方式:分别为在AndroidMenifest中指定,和在代码中设置Intent标志位指定,当两种方式都存在时,以第二种为准
  • 第一种只能设置4种启动模式,且无法设置为FLAG_ACTIVITY_CLEAR_TOP,第二张无法指定singleInstance
  • FLAG_ACTIVITY_CLEAR_TOP 如果Activity为默认standard模式,则连同它只上的Activity全部出战,创建新的Activity实例放入栈顶,如果Activity为singleTask在它之上的Activity全部出栈,如果被启动的Activity已经存在,系统则会调用它的onNewIntent.
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 相等于在分别为在AndroidMenifest中设置android:excludeFromRecents=“true”。用户点击“最近任务列表”,该Task不会出现在最近任务列表中,可达到隐藏应用的目的

1.2 intent-filter的匹配规则

  • 显式调用
Intent intent = new Intent(MainActivity.this, TestActivity.class);
startActivity(intent);
  • 隐式调用
Intent intent = new Intent();
intent.setAction("com.dev.test");
intent.addCategory("com.dev.testcat");
startActivity(intent);
  • 原则上一个Intent不应该显式与隐式并存,如果共存以显示为主。
  • 隐式调用为匹配目标组件的IntentFilter中设置过的过滤信息,IntentFilter中旅的过滤信息有action
    ,category,data。一个Activity可以有多个intent-filter,一个Intent只要能匹配任何一组intent-filter
    即可成功启动对应的Activity。
  • action的匹配规则为:action是一个字符串,一个过滤规则intent-filter中可以有多个action,只要一个Intent能和action的任何一个相同则匹配成功。需要注意的是如果Intent没有指定action则匹配失败。action区分大小写。
  • category的匹配规则:category是一个字符串,系统预定了一些,同时我们也可以在应用中自己定义。如果Intent中有category,不管有几个category,对于每个category来说,它必须是在过滤规则中定义过的。当然,Intent中也可以没有category,没有的话,也可以匹配成功。因为系统在startActivity或者startAvtivityForResult的时候会为Intent加上“android.intent.category.DEFAULT”这个category。同时为了我们的activity能够接受隐式调用,则必须在过滤规则intent-filter
    中加上 这个category
  • data 的匹配规则:与action规则相同,如果过滤规则intent-filter定义了data,那么Intent中也要定义可匹配的data.

2.IPC机制

2.1 Android的多进程模式

  • Android中使用多进程的唯一方式:给四大组件在AndroidMenifest中指定android:process。(有一种特殊情况开启多进程的方式为通过JNI在native层fork一个新的进程)
  • 进程命名方式
android:process = “:jincheng”
android:process = “www.wjb.jincheng”

假设包名为www.wjb则第一个进程名为www.wjb:jincheng第二个进程名为ww.wjb.jincheng,切以“:”开头的为当前应用的私有进程,其他应用的组件不能与它跑在同一进程中,不以“:”开头的进程为全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。

  • ShareUID Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。
  • 使用多进程出现的问题
    1.静态成员和单例模式完全失效
    2.线程同步机制完成失效
    3.SP的可靠性下降
    4.Application会多次重建
  • 序列化
    1.静态成员变量属于类,不属于对象,所以不会参与序列化
    2.用transient关键字标记的成员变量不参与序列化
    3.Serializable属于JAVA使用更简单但开销较大,需要大量的I/O操作,Parcelable属于Android使用略微繁琐,效率高,主要用于内存序列化。如果是存储设备的序列化,和网络传输的序列化推荐使用Serializable。

2.2 Binder

  • 直观的说,Binder是Android的一个类,实现了IBinder接口
  • 从IPC角度来说,Binder是Android的一种跨进程通信方式,还可以把Binder理解成 一种虚拟物理设置,它的设置驱动是/dev/binder
  • 从Android Framework来说Binder是ServiceManager连接各种Manager(ActivityManager,WindowManager…)和相应的ManagerService的桥梁。
  • 从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

2.3 Android的IPC方式

2.3.1 Bundle
  • Bundle实现了Parcelable接口,Antivity,Servce,Receiver可通过Intent传递Bundle中的数据。
2.3.2 文件共享
  • 两个进程通过读/写同一个文件来交换数据
  • SP属于文件共享的一种,一般目录位于/data/data/应用包名/shaerd_prefs目录下。SP有读写缓存策略,既内存中会有一份SP的文件缓存,因此在多进程模式下,系统对SP的读/写就变的不靠谱,面对高并发的读/写访问时SP有很大几率丢失数据,因此不建议进程通信中使用SP
2.3.3 Messenger
  • 轻量级的IPC通信方式,底层为AIDL
2.3.4 AIDL
  • 大量的并发请求使用AIDL
2.3.5 ContentProvider
  • ContentProvider是Android提供的专门用于不同应用间进行数据共享的方式。底层为Binder
2.3.6 Socket
  • 分为流式套接字和用户数据报套接字分别对应于网络的传输控制层中的TCP和UDP协议
  • TCP协议是面向连接的协议,提供双向通信功能,需要“三次握手”才能建立连接,为了提供稳定的数据传输宫功能,其本身提供了超时重传机制,因此具有很高的稳定性。
  • UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能 在性能上UDP有更好的效率,缺点是不一定保证数据的正确传输,尤其是在网络拥塞的情况。

3.View的事件体系

  • 一个view的左上角坐标为(left,top) 右下角坐标为(right,bottom)
  • width = right -left
  • hight = bottom - top
  • x,y为相对于父控件的左上角坐标(3.0增加的参数x,y,translationX,translationY)
  • x = left+translationX
  • y = fight+translationY
  • getX/getY为相对当前View左上角的x,y坐标
  • getRawX/getRawY为相对于手机屏幕的x,y坐标
  • TouchSlop为最小滑动距离,可通过ViewConfiguration.getTouchSlop()获得
  • VelocityTracker速度追踪
  • GrstureDetector手势检测
  • Scroller弹性滑动对象

3.1滑动

  • scrollTo,scrollBy
    1.scrollBy内部也是调用了scrollTo,scrollBy是基于当前位置滑动,scrollTo是基于传递参数的绝对滑动。
    2.scrollTo,scrollBy只能改变View内容的位置而不能改变View在布局中的位置
    3.单位为像素
  • 改变布局参数
val lp = btn_test.layoutParam
lp.width+=100lp.height+=100
btn_test.requestLayout()
//或者
//btn_test.layoutParams = lp

3.2View的事件分发机制

3.2.1 点击事件的传递规则
  • 点击事件的分发过程由三个主要方法完成:分发,
    拦截,处理
  1. dispatchTouchEvent(分发)
    如果事件能够传递给当前View,那么此方法一定会调用,返回结构受当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响,表示是否消耗当前事件。
  2. onInterceptTouchEvent(拦截)
    用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
  3. onTouchEvent(处理)
    在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件
  • onTouchListener的优先级高于OnTouchEvent 如果一个View设置了onTouchListener,则onTouchListener的onTouch会被回调。如果返回true,则OnTouchEvent将不会被调用,否则会调用。在OnTouchEvent中,如果当前设置的有onTouchListener则它的onClick会被调用,所以平常使用的OnClickListener的优先级最低,处于事件传递的尾端
  • 当一个点击事件产生后,传递顺序为:Activity>Window>View
  • 如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用,以此类推。如果所有元素都不处理,那么事件最终会传递给Activity处理,也就是说Activity的onTouchEvent会被调用
  • ViewGroup默认不拦截任何事件,因为源码中它的onInterceptTouchEvent方法默认返回为false
  • View没有onInterceptTouchEvent,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用
  • View的onTouchEvent默认都会消耗事件(返回true),除非他是不可点击的(clickable和longClickable同时为false)。View的longClickable默认都为false,clickable属性分情况。Button为true,TextView则为false
  • 事件的传递总是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子view,通过requestDisallowInterceptTouchEvent可以在子元素中干预父元素的事件分发过程,ACTION_DOWN事件除外
3.2.2 事件分发的源码解析
public boolean dispatchTouchEvent(MotionEvent ev) {    
       if (ev.getAction() == MotionEvent.ACTION_DOWN) {       
           onUserInteraction();   
           }   
      if (getWindow().superDispatchTouchEvent(ev)) { 
         return true;   
         }    
    return onTouchEvent(ev);
}

onUserInteraction 每当Key,Touch,Trackball事件分发到当前Activity就会被调用。如果你想当你的Activity在运行的时候,能够得知用户正在与你的设备交互,你可以override该方法。
这个回调方法和onUserLeaveHint是为了帮助Activities智能的管理状态栏Notification;特别是为了帮助Activities在恰当的时间取消Notification。所有Activity的onUserLeaveHint 回调都会伴随着onUserInteraction。这保证当用户相关的的操作都会被通知到,例如下拉下通知栏并点击其中的条目。注意在Touch事件分发过程中,只有Touch Down 即Touch事件的开始会触发该回调,不会在move 和 up 分发时触发(从Activity 源码中 dispatchTouchEvent 方法中确实是这么做的)。
onUserLeaveHint作为Activity的生命周期回调的部分,会在用户决定将Acitivity放到后台时被调用。例如:当用户按下Home键,onUserLeaveHint就会被调用。但是当来电话时,来电界面会自动弹出,onUserLeaveHint就不会被调用。当该方法被调用时,他会恰好在onPause调用之前。

  • Window的唯一实现是PhoneWindow,在PhoneWindow将事件传递到了DecorView
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {   
    return mDecor.superDispatchTouchEvent(event);
}

mDecor就是顶级View---->DecorView.也就是Activity中通过setContentView所设置的View,顶级View也叫根View,一般都是ViewGroup
((ViewGroup)getWindwo.getDecorView().findViewById(android.r.id.content)).getChildAt(0)可以获取到Activity设置的View
顶级View对点击事件的分发过程
点击事件到达顶级View,会调用ViewGroup的dispatchTouchEvent方法,其逻辑为:如果顶级ViewGroup拦截事件,也就是onInterceptTouchEvent返回true,则事件由ViewGroup处理,如果这个ViewGroup设置了OnTouchListener,则onTouchEvent会被调用,否则,onTouchEvent会被调用。也就是说如果都提供的话,onTouch会屏蔽掉onTouchEvent,因为设置了监听优先级更高。在onTouchEvent中,如果设置了longClickListener,则onClick会被调用。如果顶级ViewGroup不拦截事件,则事件会传递给它事件链上的子View,这个时候子View的dispatchTouchEvent会被调用,如此循环。

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {
.....
    // Handle an initial down.
    if (actionMasked == MotionEvent.ACTION_DOWN) {   
        // Throw away all previous state when starting a new touch gesture.    
        // The framework may have dropped the up or cancel event for the previous gesture 
        // due to an app switch, ANR, or some other state change.    
            cancelAndClearTouchTargets(ev);   
            resetTouchState();
        }
     // Check for interception.
    final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN  || mFirstTouchTarget != null) {   
    final boolean disallowIntercept = (mGroupFlags & 
    FLAG_DISALLOW_INTERCEPT) != 0;    
    if (!disallowIntercept) {       
        intercepted = onInterceptTouchEvent(ev);    
        ev.setAction(action); 
        // restore action in case it was 
        changed    } else {     
        intercepted = false;   
        }
    } else {   
        // There are no touch targets and this action is not an 
        initial down  
        // so this view group continues to intercept touches.   
        intercepted = true;
    }
    .....
}
  • FLAG_DISALLOW_INTERCEPT标志位由子View的requestDisallowInterceptTouchEvent设置,一旦设置后,ViewGroup无法拦截ACTION_DOWN以外的其他点击事件,因为ACTION_DOWN会重置FLAG_DISALLOW_INTERCEPT标志位,因此,当面对ACTION_DOWN事件时,ViewGroup总是会调用自己的onInterceptTouchEvent方法询问自己是否要拦截事件。在上面的源码中可以看到,如果事件为ACTION_DOWN,就会做一些重置操作。
    结论
    当ViewGroup决定拦截事件的时候,那么 后续的点击事件会默认交给它处理,并不再调用它的onInterceptTouchEvent方法。所以第一:onInterceptTouchEvent不是每次事件都会调用,如果我能想提前处理所有的点击事件,要选择dispatchTouchEvent方法,只有这个方法能确保每次都会调用。第二点,FLAG_DISALLOW_INTERCEPT标志位的左右可以作为处理滑动冲突的一种思路。
  • 假设事件交由子元素处理,如果父容器在ACTION_UP返回了true,就会导致子元素无法接受到ACTION_UP事件,这个时候子元素的onClick事件就无法触发,但是父容器比较特殊,一旦它开始拦截任何一个事件,那么后续的事件都会交给它处理,而ACTION_UP作为最后一个事件也必定可以传递给父容器,哪怕父容器的onInterecptTouchEvent方法在ACTION_UP返回了true。
  • 如果使用内部拦截法,则需要让父元素不拦截ACTION_DOWN事件,这样才能让事件传递到子元素。

4.View的工作原理

4.1 ViewRoot和DecorView

  • ViewRoot 对应的是ViewRootImpl类,是连接WindowManager和DecorView的纽带。View的三大流程均是通过ViewRoot完成的。在ActivityThread中,当Activity对象创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立连接
  • View的绘制流程是从ViewRoot的performTraversals方法开始。经过measure,layout,draw三个过程才能最终将一个View绘制出来。
  • measure 是测量View的宽高。Measure完成后,可以通过getMeasuredWidth和getMeasuredHight方法得到View测量后的宽高(getMeasuredWidth()获取的是view原始的大小,也就是这个view在XML文件中配置或者是代码中设置的大小。getWidth()获取的是这个view最终显示的大小,这个大小有可能等于原始的大小也有可能不等于原始大小)
  • layout 是用来确定View在父容器中的放置位置。Layout过程决定了View的四个顶点的坐标和实际的View的宽高,完成以后可以通过getTop…getRight等方法拿到四个顶点的位置。并可以通过getWidth和getHight方法来拿到最终View的宽高。
  • draw 负责将View绘制在屏幕上。Draw过程决定了View的显示,还有draw方法完成后,View的内容才能呈现在屏幕上。
  • DecorView作为顶级View,一般情况下它内部会包含一个竖直方向的LinearLayout,在这个LinearLayout中分为上下部分(具体和Android版本和主题有关),上面为标题栏,下面为内容栏,在Activiy我们通过setContentView设置的布局文件其实就是被添加到内容栏中,而内容栏的id为content,因此可以理解为什么Activity指定布局的方法不叫setView而叫setContentView,因为我们布局是调教到id为conteht的FrameLayoutzhong。我们可以通过如下方式拿到content
ViewGroup content = (ViewGroup)fingdViewById(android.id.content) 

如何拿到我们设置的View的方式如下

content.getChildAt(0)

通过源码可以指定DecorView其实是一个FrameLayout,View层的时间都先经过DecorView,然后才传递到我们的View。

4.2理解MeasureSpec

  • MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以说很大程度是因为还手父容器的影响,因为父容器影响View的MeasureSpec创建过程。在测量过程中,系统将View的LayoutParams根据父容器施加的规则,转换成对应的MeasureSpec,然后再根据这个MeasureSpec来测试View的宽高,这里的宽高是测量的宽高,而不是最终的宽高。
  • MeasureSpec代表的是一个32位的Int值,高2位代表SpceMode,低30位代表SpceSize
  • 三种模式SpceMode
  1. UNSPECIFIED父容器不对View大小做限制要多大给多大,这种情况一般用于系统内部,表示一种测量的状态,如:ListView,ScrollView
    2.EXACTLY 确切的大小,,父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpceSize所指定的值,对应于LayoutParms中的march_parent和具体的数值这两种模式。如:100dp或者march_parent
    3.AT_MOST大小不可超过某数值,父容器指定了一个可用大小,即SpceSize,View的大小不能大于这个值,具体是什么值要看不同的View具体实现。它对应于LayoutParms中的wrap_content
  • 对于定义View(DecorView)它的大小由窗口的尺寸和其自身的LayoutParms共同确定,普通的View,由父容器的MeasureSpce和自身的LayoutParms共同决定MeasureSpce。MeasureSpce一旦确定,onMeasuere中就可以确定View的测量宽高

4.3View的工作流程

4.3.1 measure
  • measure分两种情况,如果只是一个原始的View,那么measure就可以完成,如果是一个ViewGroup,除了完成自己的测量,还需要遍历去调用子元素的measure方法,各个子元素再递归去执行这个流程。

View的onMeasure的源码

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),                                        widthMeasureSpec),  getDefaultSize(getSuggestedMinimumHeight(), 
    heightMeasureSpec));
}

setMeasuredDimension(int measuredWidth, int measuredHeight)方法会设置View的宽高测量值。观察
getDefaultSize方法。

public static int getDefaultSize(int size, int measureSpec) {    
        int result = size;    
        int specMode = MeasureSpec.getMode(measureSpec);    
        int specSize = MeasureSpec.getSize(measureSpec);    
        switch (specMode) {    
        case MeasureSpec.UNSPECIFIED:       
        result = size;        
        break;    
        case MeasureSpec.AT_MOST:    
        case MeasureSpec.EXACTLY:        
        result = specSize;        
        break;    
        }   
    return result;
}
  • AT_MOSTEXACTLY这两种情况下,返回值就是MeasureSpec中的spceSize,而这个spceSize就是View测量后的大小,这里多次提到测量后的大小,是因为View的最终大小是在layout阶段中确定的。
  • UNSPECIFIED情况下,一般用于系统内部的测量过程。
  • 传入的size为getSuggestedMinimumWidth(),和getSuggestedMinimumHeight()源码为
protected int getSuggestedMinimumWidth() {   
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

protected int getSuggestedMinimumHeight() {    
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

以getSuggestedMinimumWidth为例,如果View没有设置背景,则返回值View的宽度为mMinWidth
,mMinWidth对应的是android:MinWidth这个属性设置的值,因此View的宽度即为android:MinWidth属性指定的值。如果这些属于没有指定,则值为0;如果指定了背景,则View的宽度为max(mMinWidth, mBackground.getMinimumWidth())。mMinWidth的意义我们已经知道,mBackground.getMinimumWidth()源码为:(Drawable)

public int getMinimumHeight() {   
    final int intrinsicHeight = getIntrinsicHeight();   
    return intrinsicHeight > 0 ? intrinsicHeight : 0;
}

getMinimumWidth返回的是Drawable的原始高度,如果没有高度则返回0。比如ShapeDrawable无原始宽高,BitmapDrawable有原始宽高(图片的尺寸)
*getSuggestedMinimumWidth()和getSuggestedMinimumHeight()的返回值就是View在UNSPECIFIED情况下测量的宽高。

  • 如果直接继承View的自定义控件需要重写onMeasure方法,并设置属性为wrap_content时的大小,否则在布局在使用wrap_content相当于使用了match_parent。参考代码和表4-1 MeasureSpce的创建规则。
  • 如果View使用的是wrap_content则它的SpceMode是AT_MOST,在这种情况下,它的宽高为spceSize,根据表4-1,这种情况下的spceSize就是parentSize,也就是父容器中目前可以使用的带下,也就是父容器当前剩余的空间大小。相当于match_parent。解决问题代码参考书中

ViewGroup的measure
ViewGroup提供了measureChildren方法

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {   
    final int size = mChildrenCount;    
    final View[] children = mChildren;   
    for (int i = 0; i < size; ++i) {       
        final View child = children[i];      
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {    
        measureChild(child, widthMeasureSpec, heightMeasureSpec);       
        }    
    }
}

对每个子元素进行了mesure,通过measureChild()方法

protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {    
        final LayoutParams lp = child.getLayoutParams();   
        final int childWidthMeasureSpec = 
        getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);    
        final int childHeightMeasureSpec = 
        getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);    
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

该方法就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec()方法创建子元素的MeasureSpec,最近再将MeasureSpec传递给View的 measure()方法进行测量。

  • 因为View的measure过程和Activity的生命周期不是同步的,所以无法保证Activity执行了 onCreate,onStart,onResume时某个View已经测量完毕了,如果没有测量完毕,则获得宽高就是0
    1.onWindowFocusChanged
  1. view.post(runnable)
    3.ViewTreeObvserver
    4.view.measure
4.3.2 layout
  • Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,会在onLayout中遍历所有子元素兵调用其layout方法,在layout方法中onLayout方法又会被调用。layout确定View自身的位置,onLayout确定所有子元素的位置。
4.3.3 drwa

1.绘制背景(background.graw(canvas))
2.绘制自己(onDraw)
3.绘制children(dispathDraw)
4.绘制装饰(onDrawScrollBars)

  • setWillNotDraw 当自定义View继承ViewGroup并且本身不剧本绘制功能时,就可以开启这个标志位,便于系统进行后续优化。如果知道一个ViewGroup需要通过onDraw来绘制内容时,可以显示的关闭WILL_NOT_DRAW 这个标志位。

4.4自定义View

4.4.1 自定义View分类

1.继承View重写onDraw方法
2.继承ViewGroup派生的特殊Layout
3.继承特点的View(如TextView)
4.继承特点的ViewGroup(如LinearLayout)

4.4.2自定义View须知

1.让View支持warp_content
2.如果有必要,支持padding
3.尽量不要再View中使用Handler,没必要
4.View中如果有线程或者动画,需要及时停止
5.View带有滑动嵌套情形时,需要处理好滑动冲突

5.RemoteView

  • 作用域通知栏和窗口小部件,窗口小部件其实就是广播
  • PendingIntent的匹配规则为:如果两个Pending的Intent和requestCode都相同,则是一个PendingIntent。两个Intent相同的规则为:Intemt的ComPonenName和inter-filter都相同。 Extras不参与对比。

6 .Drawable

6.1 Drawable的分类

BitmapDrawable 表示一张图片,也可以通过XML的方式描述以设置更多的效果(可以可以代表一张.9图
以下是各个属性的含义:

  • android:src: 图片的资源ID
  • android:antialias 是否开启图片抗锯齿功能
  • android:dither 是否开启抖动效果
  • android:filter 是否开启过滤效果
  • android:gravity 当图片小于容器的尺寸时,可以对图片进行定位
  • android:mipMap 纹理映射
  • android:tileMode 平铺模式。当平铺模式开启后,gravity会失效

ShapeDrawable可以理解为颜色构造的图形,可以是纯色,也可以是渐变色
以下是各个属性的含义:

  • android:shape 表示图形的形状
  • 表示shape的四个角的角度
  • 表示渐变效果,与互斥
  • 表示纯色效果
  • Shape的描边
  • 表示空白
  • shape的大小
    LayerDrawable 对应XML的标签为,它表示一个层次化的Drawable集合
    StateListDrawable 对应于标签,也表示Drawable集合,每个Drawable对应着View的一种状态,这样系统就会根据View的状态来选择合适的Drawable(IMoer项目中使用过)
    以下是各个属性的含义:
  • addroid:constantSize 值为true表示StateListDrawable的固定大小不变,默认值为false
  • android:dither 是否开启抖动效果
  • android:variablePadding StateListDrawable的padding是否会随着状态改变,默认为false,不建议开启此选项

LeveIListDrawable 对应标签,一个Drawable集合,没一个Drawable都有一个对应等级(level)的概念,根据不同的等级切换对应的Drawable。(ImageView有个方法setImageLevel可以来切换Drawable)
TransitionDrawable 对应标签,用于实现两个Drawable之间的淡入淡出效果。
InsetDrawable 对应标签,可以将其他的Drawable内嵌到自己当中,并可以在四周流出一定的间距。(面试题:为一个充满整个屏幕的LinearLayout布局指定背景图,是否可以让背景图不充满屏幕?请用代码描述实现过程。解决此题,可以使用嵌入(Inset)图像资源来指定图像,然后像使用普通图像资源一样使用嵌入图像资源。)
ScaleDrawable 对应标签,可以根据自己的等级(level)将指定的Drawable缩放到一定比例。
ClipDrawable 对应标签,可以根据自己的等级(level)来裁剪另一个Drawable。

7.Android动画深入分析

可以分为View动画和属性动画,属性动画API11后才支持,之前的版本可通过兼容库使用属性动画。

7.1 View动画

  • View动画支持4个效果,平移,缩放,旋转,透明度等动画,帧动画也属于View动画。View动画可以通过XML定义,也可通过代码动态创建。XML定义可读性会更好。XML需要定义在res\anim目录下
7.1.1View动画分类

平移动画标签对应
以下是各个属性的含义:

  • android:fromXDelta 表示X的起始值,比如0
  • android:fromYDelta 表示Y的起始值,比如0
  • android:toXDelta 表示X的结束值比如100
  • android:toYDelta 表示Y的结束值比如100

缩放标签对应
以下是各个属性的含义:

  • android:fromXScale 表示水平方向缩放的起始值,比如0.5
  • android:fromYScale 表示竖直方向的起始值,比如0.5
  • android:pivotX 表示水平方向缩放的结束值,比如1
  • android:pivotY 表示竖直方向缩放的结束值,比如1
  • android:toXScale 缩放的轴点的X坐标,它会影响缩放的效果
  • android:toYScale 缩放的轴点的Y坐标,它会影响缩放的效果

旋转标签对应<rotate

以下是各个属性的含义:

  • android:fromDegrees 表示开始的角度,比如0
  • android:toDegrees 表示结束的角度,比如180
  • android:pivotX 旋转轴点的X坐标
  • android:pivotY 旋转轴点的Y坐标

透明度标签对应
以下是各个属性的含义:

  • android:fromAlpha 表示透明度的起始值,比如0.1
  • android:toAlpha 表示透明度的结束值,比如1

还有一些常用属性,比如android:duration,表示动画持续时间,android:fillArter,表示动画结束后View是否停留在结束位置。

7.1.2自定义View动画

自定义View动画主要是矩阵变化的过程

7.2 View动画的特殊使用场景

7.2.1 LayoutAnimation

作用于ViewGroup,指定一个动画,子元素的出场就都会具有这种动画。ListView的每个item的动画就是这种方式。

7.2.2 Activity的切换效果

Activirty有默认的切换效果,这个效果我们也可以自定义。通过overridePendingTransition(enterAnim: Int, exitAnim: Int)这个方法实现。通过overridePendingTransition方法必须在StartActivity或者finsh方法后面,否则动画将不起作用

7.3 属性动画

常用的动画类有:ValueAnimator,ObjectAnimator,AnimatorSet。ObjectAnimator继承与ValueAnimator。AnimatorSet是动画集合。属性动画也可以通过代码动态实现和XML定义。
XML需要定义在res\animator目录下
以下是各个属性的含义:

  • android:propertyName ,是objectAnimator特有的,其他属性都一样。它表示属性动画作用对象的属性名称。
  • android:duration 表示动画的时长
  • android:valueFrom 表示属性的起始值
  • android:valueTo 表示属性的结束值
  • android:valuestartOffset 表示动画的延迟时间
  • android:valuerepeatCount 表示动画的重复次数
  • android:valuerepeatMode 动画的重复模式
  • android:valuevalueType 表示propertyName所指的类型,如果是颜色,则不需要指定,系统会自动对颜色属性进行处理
7.3.1插值器和估值器

插值器是根据时间的流逝的百分比计算当前属性改变的百分比
估值器根据当前属性改变的百分比计算改变后的属性值

7.3.2对任意属性做动画

**属性动画原理原理:**属性动画要求动画作用的对象提供该属性的get,set方法,属性动画根据外界传递过来的初始值和最终值,以动画的效果多次去调用set方法,每次传递的set值都不一样,随着时间的推移,传递的值越来越接近最终值。同时这个对象的属性必须能够通过某种方式映射出来,比如带来UI的改变之类(如果不满足,动画无效果,但是不是Crash)

  • TextView有android:layout_width和android:width对应setView方法,android:width对应setView方法,是设置最大和最小宽度的。
7.4 使用动画的注意事项

1.OOM,主要是帧动画
2.内存泄露,属性动画有一类无限循环动画,需要及时停止。View 动画不存在此问题
3.兼容性问题,3.0以下的系统有兼容性问题
4.View动画的问题。View动画是对View的影像做动画,并不是改变View的状态,因此有时候会出现动画完成后,View影像无法隐藏的问题,需要调用view.clearAnimation()清除View动画解决问题
5.不要使用PX,尽量使用DP
6.动画元素交互,3.0之前的系统不管是属性动画还是View动画,新位置均无法触发单机事件,同时老位置可以。3.0后属性动画可以,View动画任然只有原位置可以触发单机效果
7.硬件加速,建议开启,提升动画流畅性。

8.理解Window和WindowManager的场景Flag

Window是一个抽象类,具体实现是PhoneWindow
WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerServicer中
WindowManager和WindowManagerServer的交互是一个IPC过程
Android中所有的视图都是通过Window来呈现的,不管是Activity还是Dialog,Toast,他们的视图都是依附在Window上的,因此Window实际上是View的直接管理者。就连Activity的setContentView在底层也是通过Window来完成的( getWndow())

public void setContentView(@LayoutRes int layoutResID) {   
    getWndow().setContentView(layoutResID); 
    initWindowDecorActionBar();
}

8.1WindowManager的常见Flags

  • FLAG_NOT_FOCUSABLE
    Constant Value: 8 (0x00000008)
    设置之后window永远不会获取焦点,所以用户不能给此window发送点击事件
    焦点会传递给在其下面的可获取焦点的window
    这个flag同时会启用 FLAG_NOT_TOUCH_MODAL flag , 不管你有没有手动设置
    设置这个flag同时表明了这个window不会和软键盘交互,
    (这句话的翻译我不知道对不对)所以window会独立于激活的软键盘之上(这句话的意思就是window会在Z轴上置于输入法之上,所以window可以全屏使用来覆盖住输入法,你可以使用 FLAG_ALT_FOCUSABLE_IM 来修改这个行为)

  • FLAG_NOT_TOUCH_MODAL
    Constant Value: 32 (0x00000020)
    即使这个window是可获取焦点的,
    也允许window之外点击事件传递给其他在其之后的window
    如果不设置这个值,则window消费掉所有点击事件,不管这些点击事件是不是在window的范围之内
    //如果要做悬浮框,我想这个flag肯定得设置,但api>=23就别想了这个flag简而言之就是说,当前window区域以外的点击事件传递给下层window,当前window区域以内的点击事件自己处理

  • FLAG_SHOW_WHEN_LOCKED
    Constant Value: 524288 (0x00080000)
    一个特殊的flag,使得window可以在锁屏状态下显示
    这个flag会使得window比keyguard或其他锁屏界面具有更高的层级
    可以配合FLAG_KEEP_SCREEN_ON使用,点亮屏幕,在显示keyguard window之前显示你的window.
    可以配合FLAG_DISMISS_KEYGUARD使用来自动解锁没密码的keyguards
    这个flag只能应用在最顶层的全屏window上用人话说就是可以让window显示在锁屏界面上

8.2WindowManager的Type

Type代表Windw的类型,Window有3重类型。应用Window,子Window,系统Window。
Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖层级小的Window的上面。
应用Window对应着一个Activity,层级在1~99
子Window不能单独存在,需要附属在特定的父子Window中,比如Dialog,层级在1000~1999
系统Window是需要声明权限才能创建的系统Window,比如Toast和系统状态栏,层级在2000~2999

8.3Window的内部机制

Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl建立联系,因此Window并不是实际存在的,它是以View的形式存在。WindowManager的三个接口,addView,updateViewLayout,removeView都是针对View的。

8.3.1Window的添加过程

WindowManager是一个接口,真正的实现是WindowManagerImpl。

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {  
    applyDefaultToken(params);    
    mGlobal.addView(view, params, mContext.getDisplay(), 
    mParentWindow);
}

WindowManagerImp并没有处理Winodw的三大操作,而是交给了WindowManagerGlobal(mGlobal)处理。

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

是典型的桥接模式。
WindowManagerGlobal的addView分为以下几步:
1.检查参数是否合法,如果是子Window那么还需要调整一些布局参数
2.创建ViewRootImpl并将View添加到列表中

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =newArrayList<WindowManager.LayoutParams>();

mViews存储的是所有的Window所对应的View
mRoots存储的是所有Window所对应的ViewRootImpl
mParams存储的是所有Window所对应的布局参数
在addView中,通过以下方式将Window的一些列对象添加到列表中

root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

3.通过ViewRootImpl来更新界面并完成Window的添加过程

root.setView(view, wparams, panelParentView);

这个步骤由ViewRootImpl的setView方法完成。View的绘制过程全部是由ViewRootImpl完成,addView过程也不例外,setView内部有个requestLayout(),通过这个方法完成一部刷新请求。

@Override
public void requestLayout() { 
    if (!mHandlingLayoutInLayoutRequest) {  
        checkThread();      
        mLayoutRequested = true;     
        scheduleTraversals();   
    }
}

scheduleTraversals()方法实际是View的绘制入口。
在steView方法中,会通过WindowSession最终来完成Window的添加过程。
mWindowSession的类型是IWindowSession,是一个Binder对象,真正的实现类是Session。所以Window的添加过程是一次IPC调用。
Session内部会通过WindowManagerService来实现Window的添加。

8.3.2Window的删除过程

WindowManagerGlobal的removeView通过findViewLocked先找到待删除View的索引,查找过程就是建立数组的遍历。然后再通过removeViewLocked方法做进一步的删除。

public void removeView(View view, boolean immediate) { 
    if (view == null) {    
        throw new IllegalArgumentException("view must not be null");   
    }   
    synchronized (mLock) {      
        int index = findViewLocked(view, true);     
        View curView = mRoots.get(index).getView();   
        removeViewLocked(index, immediate);     
        if (curView == view) {       
            return;       
        }        
        throw new IllegalStateException("Calling with view " +view + " but the ViewAncestor is attached to " + curView);  
    }
}

removeViewLocked是通过ViewRootImpl来完成删除操作的。WindowManager中提供了两个接口,removeView和removeViewImmediate,分别表示异步删除和同步删除(Immediate,立即的)
一般来说都是异步。异步删除由ViewRootImpl的die方法完成。

private void removeViewLocked(int index, boolean immediate) {   
    ViewRootImpl root = mRoots.get(index);  
    View view = root.getView();   
    if (view != null) {     
        InputMethodManager imm = InputMethodManager.getInstance();    
        if (imm != null) {            
            imm.windowDismissed(mViews.get(index).getWindowToken());  
        }   
    }   
    boolean deferred = root.die(immediate);   
    if (view != null) {   
        view.assignParent(null);  
        if (deferred) {     
            mDyingViews.add(view);     
        }   
    }
}

die只是发送了一个请求删除的消息后,就立刻返回了。这个时候View并没有完成删除操作,所以最后悔将其添加到mDyingViews中,mDyingViews表示带删除的列表。
die方法内部只是做了简单判断,如果是异步删除,就发送一个MSG_DIE的消息,ViewRootImpl的Handler会处理此消息,并调用doDie方法,如果是同步删除,就不发生消息,直接调用doDie方法。
doDie方法内部会调用dispatchDetachedFromWindow方法,也是实现真正移除View的操作。dispatchDetachedFrom2Window做了以下4件事:
1.垃圾回收相关工作,比如清除数据和消息,移除回调
2.通过Session的remove方法移除Window代码为mWindowSession.remove(mWindow);,这同样是一个IPC过程。最终会调用WindowManagerService的removeView方法。
3.调用View的dispatchDetachedFromWindow方法
4.调用WindowManagerGlobal的doRemoveView方法刷新数据。包括mRoots,mParams以及mDyingViews,需要将当前Winodw所关联的这三类对象从列表中删除。

8.3.3Window的更新过程

WindowManagerGlobal的updateViewLayout完成更新。

view.setLayoutParams(wparams);

通过setLayoutParams方法更新LayoutParams。

ViewRootImpl中会通过scheduleTraversals()方法对View重新测量,布局,绘制。
ViewRootImpl还会通过WindowSession来更新Winodw视图。这个过程最终也是由WindowManagerService的relayoutWindow()来具体实现,同样是一个IPC过程。

8.4Window的创建过程

8.4.1 Activity的winodw创建过程

Acctivity的启动过程最终会有ActivityThread中的 performLaunchActivity()方法来完成启动过程,在这个方法内部会通过调用类加载器创建Activity的实例对象,并调用attach方法为其关联运行过程

activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback);

attach方法中,系统会创建Activity附属的Window对象并为其设置回调接口,Window是通过PolicyManager的makeNewWindow方法实现的。

  • Activity的视图是如何附加到Window上的
    Activity的视图由setContent方法提供
 public void setContentView(@LayoutRes int layoutResID) {   
    getWindow().setContentView(layoutResID);  
    initWindowDecorActionBar();
}

可以看出,Activity将具体实现交给了Window。而Window的具体实现是PhoneWindow。PhoneWindow的setContentView遵循以下步骤:
1.如果没有DecorView就创建,由installDecor()方法完成创建
2.将View添加到DecorView的mContntParent中。(Activity的setContentView只是将Activity的布局文件添加到DecorView的mContntParent中)
3.回调Activity的 onContentChanged方法通知Activity视图已经发生改变。

public void onContentChanged() {

Activity的onContentChanged是个空实现,我们可以在子Activity中处理这个回调。

8.4.2 Dialog的winodw创建过程

遵循如下几个步骤:
1.创建Window
创建后的实际对象其实就是PhoneWindow。
2.初始化DecorView,并将Dialog的视图添加到DecorView中
和Activity相似,也是通过Window的setContentView方法
3.将DecorView添加到Window中
Dialog的show方法会通过WindowManager将DecorView添加到Window中

mWindowManager.addView(mDecor, l);

DilaLog的上下文必须采用Activity的Context,不能使用Application,是因为没有应用token导致的。系统Window比较特殊,可以不需要token。

8.4.2 Toast的winodw创建过程

Toast因为有定时取消功能,所以内部使用了Handler,且有两类IPC过程。
Toast的显示也隐藏都是通过NMS实现。

9.四大组件的工作过程

除了BroadcastReceiver以外,另外三大组件必须在AndroidManifest中注册,BroadcastReceiver可以在AndroidManifest中注册也可以在代码中注册。
Activity,Service,BroadcastReceiver需要借用Intent调用,ContentProvider不需要
Activity是用户可以感知的,其他三大组件对用户来说是不可感知的。
Service组件有两种运行状态,绑定状态和启动状态,Activity只有一种运行启动状态。尽管Service组件是用于执行后台计算的,但是它本身是运行在主线程中的,所以耗时的后台计算任然需要在单独的线程中去完成。处于绑定状态的Service可以很方便的和外界进行通信。
BroadcastReceiver是一种消息型组件。用于在不同组件,甚至不同应用中进行消息传递。静态注册是在AndroidManifest中注册,这种广播在应用安装的时候就会被系统解析,动态注册是在代码中通过Context.RegisterRecever()实现,并且在不需要的时候需要通过Context.unRegisterRecever()来解除广播,动态注册的广播必须在应用启动才能注册和接收广播。BroadcastReceiver可以用来实现低耦合的观察者模式。由于BroadcastReceiver的特性,不适合执行耗时操作,BroadcastReceiver一般来说不需要停止,也没有停止的概念。
BroadcastReceiver不适合执行耗时操作原因有二:
1.BroadcastReceiver 一般处于主线程。 耗时操作会导致 ANR
2.BroadcastReceiver 启动时间较短。 如果一个进程里面只存在一个 br组件。并且在其中开启子线程执行耗时任务。 系统会认为该进程是优先级最低的 空进程。很容易将其杀死。
那么如何在BroadcastReceiver中执行耗时操作呢,一般有两种方式:
1.在当前BroadcastReceiver中另起线程操作
2.由当前BroadcastReceiver启动新的Service,在新的Service中操作(第一种方法并不推荐。因为大家都知道,安卓在内存不足或其他资源不够的情况下会作清理。而BroadcastReceiver在onReceive()调用后,就只剩下一个线程在跑了,没有service的级别高!)
ContentProvider是一种数据共享型组件,用于向其他组件甚至其他应用共享数据,内部需要实现增删改查四种操作,它内部维持这一份数据集合。内部的增删改查操作需要维护好线程同步,因为这个几个方法是在Binder线程池中被调用的。ContentProvider组件也不需要手动停止。

9.1 Activity的工作过程

9.1.1 Activity的启动过程分析

startActivity()方法有好几种重载方式(好像只看到两种),但是最终都是调用startActivityForResult
(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options)方法。

Instrumentation.ActivityResult ar =  mInstrumentation.execStartActivity( his, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options);

上面代码里的参数mMainThread.getApplicationThread()的类型的ApplicationThread,ApplicationThread是ActivityThread的内部类,ApplicationThread和ActivityThread在Activity的启动过程中发挥着很重要的作用。
Instrumentation的execStartActivity方法源码如下:

public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, 
Activity target, Intent intent, int requestCode, Bundle options) {  
    IApplicationThread whoThread = (IApplicationThread) contextThread;   
    Uri referrer = target != null ? target.onProvideReferrer() : null;  
    if (referrer != null) {      
        intent.putExtra(Intent.EXTRA_REFERRER, referrer);   
    }   
    if (mActivityMonitors != null) {    
        synchronized (mSync) {    
             final int N = mActivityMonitors.size();    
             for (int i=0; i<N; i++) {         
                final ActivityMonitor am =  mActivityMonitors.get(i);   
                ActivityResult result = null;   
                if (am.ignoreMatchingSpecificIntents()) {    
                result = am.onStartActivity(intent);   
                 }             
              if (result != null) {        
                am.mHits++;              
                 return result;        
             } else if (am.match(who, null, intent)) {     
                am.mHits++;          
                 if (am.isBlocking()) {  
                    return requestCode >= 0 ? am.getResult() : null;     
                 }         
                break;     
               }        
           }     
         }   
    }   try {      
    intent.migrateExtraStreamToClipData(); 
    intent.prepareToLeaveProcess(who);   
    int result = ActivityManager.getService().startActivity(whoThread, who.getBasePackageName(), 
    intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ?      target.mEmbeddedID : null, requestCode, 0, null, options);      
    checkStartActivityResult(result, intent);    
    catch (
        RemoteException e) { 
            throw new RuntimeException("Failure from system", e);
    }   
    return null;
}

启动Activity真正的实现由ActivityManager.getService().startActivity方法来完成。(书中说是由ActivityManagerNative.getDefault()完成,可能是Android系统更新后改了方法。但是内部源码与书中介绍大致相同,getDefault()就是我看到源码中的getService()方法。)
ActivityManager.getService().startActivity的到的其实是一个IActivityManager类型的Binder对象。因此他的具体实现是AMS,可以发现,AMS的这个Binder对象是一个单例模式对外提供(Singleton)

public static IActivityManager getService() {    
    return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =  newSingleton<IActivityManager>() {     
        @Override          
        protected IActivityManager create() {        
            final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);     
            final IActivityManager am = IActivityManager.Stub.asInterface(b);       
            return am;     
        }     
};

可以看出是由AMS 里的startActivity方法进行Activity的启动的(ActivityManager.getService().startActivity)。
重新看一下Instrumentation的execStartActivity方法源码。在ActivityManager.getService() .startActivity方法下面有有个checkStartActivityResult(result, intent);方法。看方法名也知道,是检查启动Activity的结果。

public static void checkStartActivityResult(int res, Object intent) { 
    if (!ActivityManager.isStartResultFatalError(res)) {      
    return;    
    }  
    switch (res) {     
    case ActivityManager.START_INTENT_NOT_RESOLVED:     
    case ActivityManager.START_CLASS_NOT_FOUND:     
        if (intent instanceof Intent &&  ((Intent)intent).getComponent() != null)  
        throw new ActivityNotFoundException( "Unable to find explicit activity class " + 
        ((Intent)intent).getComponent().toShortString() + "; have you declared this activity in 
        your AndroidManifest.xml?");     
        throw new ActivityNotFoundException( No Activity found to handle " + intent);  
    
    case ActivityManager.START_PERMISSION_DENIED:     
        throw new SecurityException("Not allowed to startactivity "  + intent);       
    case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:     
        throw new AndroidRuntimeException(  "FORWARD_RESULT_FLAG used while also requesting a result");       
    case ActivityManager.START_NOT_ACTIVITY:    
        throw new IllegalArgumentException( PendingIntent is not an activity"); 
    case ActivityManager.START_NOT_VOICE_COMPATIBLE:      
        throw new SecurityException( "Starting under voice control not allowed for: 
    " + intent);        
    case ActivityManager.START_VOICE_NOT_ACTIVE_SESSION:       
        throw new IllegalStateException("Session calling startVoiceActivity does not 
    match active session");    
    case ActivityManager.START_VOICE_HIDDEN_SESSION:    
        throw new IllegalStateException( "Cannot start voice activity on a hidden 
    session");     
    case ActivityManager.START_ASSISTANT_NOT_ACTIVE_SESSION:   
        throw new IllegalStateException( Session calling startAssistantActivity does 
    not match active session");   
    case ActivityManager.START_ASSISTANT_HIDDEN_SESSION:     
        throw new IllegalStateException( "Cannot start assistant activity on a hidden 
    session");      
    case ActivityManager.START_CANCELED:         
        throw new AndroidRuntimeException("Activity could not be started for "  + intent);        default:            
        throw new AndroidRuntimeException("Unknown error code " + res + " when starting " + intent);   
    }
}

可以看到熟悉的"Unable to find explicit activity class "+ ((Intent)intent).getComponent().toShortString()+ "; have you declared this activity in your
AndroidManifest.xml?"就是启动的Activity没有在AndroidManifest中注册时抛出的异常。
继续看AMS 里的startActivity方法是如何进行Activity的启动的(ActivityManager.getService().startActivity。)

@Override
public final int startActivity(IApplicationThread caller, String callingPackage,Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,   int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {    
    return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions,UserHandle.getCallingUserId());
}

看出Activity的启动又交给了startActivityAsUser()方法了。(由于我电脑的源码与书中不一致,我的是API28,应该是被更改过,先看书中源码,笔记中就不再贴出源码,大致过程更改的应该不会太多,先理解书中的)由于源码不同,大致情况是启动Activity的任务在ActivityStackSupervisor和ActivityStack中不断的传递,最后Activity的启动过程回到了ApplicationThread。ApplicationThread是主线程,也就是UI线程ActivityThread的内部类。

private class ApplicationThread extends IApplicationThread.Stub {
    ......
}

最后由ApplicationThread的scheduleLaunchActivity方法来启动Activity,scheduleLaunchActivity的实现非常简单,就是发送了一个启动Activity的信息交给Handler处理。这个Handler就是之前所知道的H。发送的消息就是H.LAUNCH_ACTIVITY。在H中,对消息LAUNCH_ACTIVITY的处理方式就是调用ActivityThread的handleRelaunchActivity方法。

@Override
public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
    ......
    final Activity a = performLaunchActivity(r, customIntent);
    ......
}

handleLaunchActivity方法调用performLaunchActivity()方法最终来完成Activity的启动。

9.1.2 performLaunchActivity()方法所在的事

该小段附近的源码均为performLaunchActivity()方法的源码
1.从ActivityClientRecord中获取待启动Activity的组件信息

@Override
public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
    ......
       ActivityInfo aInfo = r.activityInfo;
       if (r.packageInfo == null) {    
           r.packageInfo = getPackageInfo(aInfo.applicationInfo,r.compatInfo, 
           Context.CONTEXT_INCLUDE_CODE);
    }
    ComponentName component = r.intent.getComponent();
    if (component == null) {   
    component = r.intent.resolveActivity( mInitialApplication.getPackageManager());    r.intent.setComponent(component);
    }
    if (r.activityInfo.targetActivity != null) {  
        component = new ComponentName(r.activityInfo.packageName,  r.activityInfo.targetActivity);
    }
        ......
}

2.通过Instrumentation的newActivity方法使用类加载器创建Activity对象

Activity activity = null;
try {   
        java.lang.ClassLoader cl = appContext.getClassLoader();  
        activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);    
        StrictMode.incrementExpectedActivityCount(activity.getClass());   
        r.intent.setExtrasClassLoader(cl);    
        r.intent.prepareToEnterProcess();   
        if (r.state != null) {        
        r.state.setClassLoader(cl);  
     }
 catch (Exception e) {  
        if (!mInstrumentation.onException(activity, e)) {    
        throw new RuntimeException( "Unable to instantiate activity " + component + ": " + e.toString(), e);   
    }
}

3.通过LoadedAPK的makeApplication方法来尝试创建Application对象

Application app = r.packageInfo.makeApplication(false, mInstrumentation);

public Application makeApplication(boolean forceDefaultAppClass,        Instrumentation instrumentation) {   
    if (mApplication != null) {    
    return mApplication;  
    }
    ......
    if (instrumentation != null) {  
    try {        
    instrumentation.callApplicationOnCreate(app);   
    } catch (Exception e) {
        if (!instrumentation.onException(app, e)) {   
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);   
            throw new RuntimeException( "Unable to create application " + app.getClass().getName() =+ ": " + e.toString(), e);
            }
    }
    ......
}

可以看出,如果Application已经存在,就不会创建了这就意味着一个应用只有一个Application。
与第二步一样,都是通过Application的创建也是通过Instrumentation来完成的,都是通过类加载器完成的。
创建完毕后会调用callApplicationOnCreate方法来调用Application的onCreate()方法。

public void callApplicationOnCreate(Application app) {    
    app.onCreate();
}

4.创建ContextImpl对象,并通过Activity的attach方法来完成一些重要数据的初始化

ContextImpl appContext = createBaseContextForActivity(r);
......
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
......
activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback);
......

ContextImpl是一个很重要的数据结构,是Context的具体体现,Context的大部分逻辑都是由ContextImpl来完成的。ContextImpl是通过Activity的attach来个Activity建立关联的,除此之外,在attach方法中还会完成Window的创建,并建立自己和Window的关联,这样当Window接收到外部输入事件后就可以将事件传递给Activity。

9.1.3 总结Activity的工作过程

不管通过哪个startActivity()方法最终都是调用startActivityForResult方法开启动Activty,在startActivityForResult方法内部会调用Instrumentation.execStartActivity方法,在execStartActivity方法中会调用ActivityManagerNative.getDefault().startActivity,也就是ActivityManagerService(AMS)的startActivity方法。在startActivity方法中,启动Activity的任务在ActivityStackSupervisor和ActivityStack中不断的传递,最后Activity的启动过程回到了ApplicationThread,由ApplicationThread的scheduleLaunchActivity方法发送一个消息H.LAUNCH_ACTIVITY给Handler,H接收到信息后,调用ActivityThread的handleRelaunchActivity方法,handleRelaunchActivity方法内部调用performLaunchActivity()方法最终完成Activity的启动。

9.2 Service的工作过程

Service分为两种工作状态,一种是启动状态,用于执行后台计算,一种是绑定状态,主要用于其他组件和Service的交互。两种状态是可以共存的。

9.2.1 Service的启动过程
@Override
public ComponentName startService(Intent service) {   
    return mBase.startService(service);
}

mBase就是Context(ContextImpl)。Activity被创建的时候attach就会将一个ContextImpl对象关联起来,这个ContextImpl就是mBase。
ContextImpl的startService调用了startServiceCommon方法,

private ComponentName startServiceCommon(Intent service, boolean 
requireForeground,        UserHandle user) {
    ......
    ComponentName cn = ActivityManager.getService().startService(    mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver()), requireForeground,  getOpPackageName(), user.getIdentifier());
    ......
}

通过ActivityManager.getService().startService来启动了一个服务(和Activity一样,书中是ActivityManagerNative.getDefault().startService)
startService方法内内有段代码

try {   
    res = mServices.startServiceLocked(caller, service, resolvedType, callingPid, callingUid,            requireForeground, callingPackage, userId);
} finally {   
    Binder.restoreCallingIdentity(origId);
}

所以是由AMS的mServices对象完成了后续的启动过程。mServices的类型是ActiveServices,是一个辅助AMS进行Service管理的类,包括Service的启动绑定和停止等。
在ActiveServices的startServiceLocked方法中经过一堆的传递,最后也是回到了ApplicationThread中。由scheduleCreateService开始启动:

public final void scheduleCreateService(IBinder token, ServiceInfo info, CompatibilityInfo compatInfo, int processState) {   
    updateProcessState(processState, false);  
    CreateServiceData s = new CreateServiceData();   
    s.token = token;  
    s.info = info;   
    s.compatInfo = compatInfo;   
    sendMessage(H.CREATE_SERVICE, s);
}

与Activity启动过程类似,也是发送了一个H.CREATE_SERVICE信息给Handler H来完成。H对这个消息的处理就是调用handleCreateService()方法。
handleCreateService()方法主要做了以下几件事:
1.通过类加载器创建Service的实例
2.创建Application对象,并调用其onCreate。当然Application对象创建也是一次,如果已有就不会再创建了
3.创建ContextImpl对象,并通过Service的attach方法完成建立联系,与Activity类型,毕竟Activity和
Service都是一个Context。

5.最后调用Service的onCreate方法将Service对象存储到ActivityThread中的一个列表中,这个列表的定义如下

final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();

这个时候Service已经启动了,这个时候ActivityThread会通过handleServiceArgs方法调用Service的onStartCommand方法。

9.2.2 Service的绑定过程

与Service的启动过程相似,也是通过ContextImpl的bindServiceCommon完成。
bindServiceCommon会通过AMS完成Service的具体绑定过程。

public int bindService(IApplicationThread caller,....){
    ......
    synchronized(this) {  
        return mServices.bindServiceLocked(caller, token, service, resolvedType, connection, flags, callingPackage, userId);
    }
}

由Activity的启动过程分析知道mServices其实是ActiveServices,在AMS的bindService方法中调用了ActiveServices的bindServiceLocked方法。与前面的类似,在bindServiceLocked方法中经过几次传递,最后还是回到了ApplicationThread中,通过scheduleBindService方法发送了一个H.BIND_SERVICE
消息给Handler H,

public final void scheduleBindService(IBinder token,...)
{
    ......
    sendMessage(H.BIND_SERVICE, s);
}

H接收到消息后,交给ActivityThread的handleBindService方法处理,handleBindService方法内部会根据Service的token取出Service对象,然后调用Service的onBind方法,完成绑定过程。(onBind方法会返回一个Binder对象给客户端使用)
这个时候客户端并不知道已经绑定完成了,由我们熟悉的AMS的publishService方法来完成通知客户端的任务。

public void publishService(IBinder token, ...)
{
    // Refuse possible leaked file descriptors
    if (intent != null && intent.hasFileDescriptors() == true) {   
        throw new IllegalArgumentException("File descriptors passed 
        in Intent");
    }
   
    synchronized(this) {   
            if (!(token instanceof ServiceRecord)) {    
            throw new IllegalArgumentException("Invalid service 
            token");  
        }   
    mServices.publishServiceLocked((ServiceRecord)token, intent, service);

    }
}

mServices是ActiveServices早就知道了,publishServiceLocked方法最核心的一句代码是c.conn.connected(r.name, service, false);
connected方法内部post方法将RunConnection方法运行在主线程中,在RunConnection方法中通过ServiceConnection对象的onServiceConnected方法完成最后的通知。

9.3 BroadcastRecevier的工作过程

静态注册的广播在应用安装时由系统自动完成注册,具体来说是有PMS(PackageManagerService)完成,其他三大组件也都是在应用安装时由PMS完成解析并注册的。

9.3.1 广播的注册过程

以下分析动态广播注册的过程:
由ContextWrapper开始进入过程的。最后是由AMS的registerRecevier完成广播的注册的。

9.3.1 广播的发送和接收过程

ContextImpl直接向AMS发送了一个异步请求用户发送广播,在ASM的broadcastIntent方法中调用了broadcastIntentLocaked方法,在broadcastIntentLocaked方法内部会根据intent-filter查找匹配的广播接收者,并且经过一系列的条件过滤,最终会将满足条件的广播接受者添加到BroadcastQueue中,接着BroadcastQueue会将广播发送给相应的广播接收者。
广播的接收最后会通过ApplicationThread来完成广播的接收。

9.4 ContentProvider的工作过程

  • 当进程启动时,ContentProvider会同时启动,并发布到AMS中,ContentProvider的onCreate要先与Application的onCreate,这是四大组件中少有的现象。

  • 当一个应用启动时,入口方法为main方法,main方式是一个静态方法,在main方法中会创建ActivityThread的实例,并创建主线程的消息队列,然后在ActivityThread中的attach方法中会远程调用AMS的attachApplication方法,并将ApplicationThread提供给AMS。ApplicationThread是一个Binder对象,它的接口是IApplicationThread,主要用于ActivityThread和AMS通信。ActivityThread会创建Application对象和并加装ContenProvider,ActivityThread会先加载ContentProvider,然后再调用Application的onCreate方法。

  • ContentProvider一般是单例的,到底是不是单例一般是通过它的android:mulitprocess属性决定,为false的时候是单例。

  • ContentProvider的四个方法任何一个都可以触发ContentProvider的启动过程。

  • AMS会先启动目标ContentProvider所在的进程,然后再启动ContentProvider。AMS的attachApplication方法调用attachApplicationLocked方法,attachApplicationLocked又调用ActivityThread的bindApplication,这是个进程间调用。ActivityThread的bindApplication会发送一个BIND_APPLICATION类型的消息给mH,mH是一个Handler,它接收到消息后会调用ActivityThread的handleBindApplication方法。handleBindApplication方法完成了Application的创建以及ContentProvider的创建。可以分为以下四个步骤:

    1.创建ContextIml和Instrumentation
    2.创建Application对象
    3.启动当前进程的ContentProvider并调用onCreate方法
    4.调用Application的onCreate方法

9.5 最简单的总结

  • 对于Activity和Service的启动过程,先是一个我们知道方法为入口,比如startActivity,然后在内部经过一系列传递,最后交给了ApplicationThread的scheduXXX方法,(scheduLaunchActivity,scheduCreateService等)scheduXXX方法发送一个消息给H,H对其做处理,一般是调用handleXXX方法,然后在handleXXX方法做最后的方法调用完成整改过程
  • 广播的注册过程是通过AMS,发送是通过AMS完成,接收是通过ApplicationThread(不是很敢确定)
  • ContentProvider的启动也是通过AMS最后再通过Handler mH(不是H)的handleBindApplication方法。

10.Android的消息机制

MessageQueue 消息队列,内部是采用单链表的数据结构来存储信息的列表
Looper消息循环,MessageQueue只是消息的存储单元,Looper则处理消息。Looper会无限循环的去查看是否有新的消息,如果有的话就处理消息,否则就一直等待。Looper中还有一个特殊的概念:ThreadLocal,ThreadLocal并不是线程,而是在每个线程中存储数据。
Handler创建的时候会采用当前线程的Looper来构造消息循环系统,就是通过ThreadLocal。因为ThreadLocal可以在不同的线程中互不干扰的存储并提供数据。通过ThreadLocal可以很轻松的获得到每个线程的Looper。
线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper,主线程也是UI线程也是ActivityThread,被创建的时候就会初始化Looper,所以UI线程可以默认使用Handler

10.1 Android的消息机制概述

Handler的主要作用是将一个任务切换到某个指定的线程中去执行。为什么只主线程才能更新UI呢?因为在ViewRootImpl中有如下代码对UI操作做了验证:

void checkThread() {  
    if (mThread != Thread.currentThread()) {   
        throw new CalledFromWrongThreadException("Only the original thread that created a view 
        hierarchy can touch its views.");   
    }
}

为什么不允许在子线程访问UI
因为Android的UI控件不是线程安全的,在多线程中并发访问会导致UI控制处于不可预期状态。
为什么不在UI控件的访问添加锁机制
1.加上锁机制会使UI访问操作变的复杂
2.加上锁机制会使UI访问的效率降低,因为锁机制会阻塞某些线程的执行

Looper是运行在创建Handler所在的线程中的,所以Handler的业务逻辑就被切换到了创建Handler所在的线程中执行了。

10.2 Android的消息机制分析

Handler的post方法将一个Runable投递到Handler内部的Looper去处理。(也可以通过send的一系列方法发送,post最终也是通过send完成发送的)

10.2.1 ThreadLocal的工作原理

ThreadLocal是一个线程内部的数据存储类,是一个泛型类,通过它可以在指定的线程中存储数据,数据存储后只有在指定的线程中可以获得存储的数据。日常开发中ThreadLocal使用的很少,某些特殊的情况下,如Looper,ActivityThread,AMS中使用到了。

ThreadLocal的作用就是当某些数据是以线程为作用域,且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。ThreadLocal还有一个使用场景就的复杂逻辑下的对象传递,比如监听器的传递

不同的线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的的线程中取出一个数组,然后在从数组中根据当前ThreadLocal的索引去查找队友的value值。显然不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的线程中维护一套数据副本且彼此互不干扰

public T get() { 
    Thread t = Thread.currentThread();  
    ThreadLocalMap map = getMap(t);  
    if (map != null) {      
        ThreadLocalMap.Entry e = map.getEntry(this);    
        if (e != null) {         
            @SuppressWarnings("unchecked") 
            T result = (T)e.value;    
            return result;     
        }   
    }   
    return setInitialValue();
}

ThreadLocal内部会从各自的的线程中取出一个数组,通过getMap(Thread t)

ThreadLocalMap getMap(Thread t) {  
    return t.threadLocals;
}
10.2.2 消息队列的工作原理

消息队列主要有两个操作:插入和读取。读取本身对伴随着删除操作。插入和读取对应的方法分别为enqueueMessage()和next()

  • enqueueMessage()是往消息队列中插入一条消息
  • next()是从消息队列中读取一条信息,并将其从消息队列中移除。源码中next()是一个无限循环的方法,如果消息队列中没有消息,就会阻塞在这里,当有新的消息来到时,next()会返回这条消息,并将其从链表中删除。
    之前的笔记中说了消息队列并不是队列,其实内部是单链表的数据结构维护的消息列表。单链表的优势在于插入和删除。
10.2.3 Looper的工作原理
private Looper(boolean quitAllowed) {  
    mQueue = new MessageQueue(); 
    mThread = Thread.currentThread();
}
  • Looper的构造方法会创建一个MessageQueue,并将当前线程的对象保持起来。
    通过Looper.prepare()创建一个Looper
    通过Looper.loop()来开启消息循环
  • Looper除了prepare()方法外Looper还提供了prepareMainLooper()方法,该方法本质也是通过prepare()方法来实现的,prepareMainLooper()方法主要是给主线程也就是ActivityThread创建Looper使用的。所以Looper提供了Looper.getMainLooper()方法,它可以在任何地方地道主线程的Looper。
  • Looper提供了退出的方法,分别为quit和quitSafely
    quit 会直接退出Looper
    quitSafely 会设定一个退出标记,然后把消息队列中已有的消息处理完毕后会安全的退出
public void quit() {    
    mQueue.quit(false);
}
public void quitSafely() {  
    mQueue.quit(true);
}


  • Looper退出后,Handler发送的信息会失败,这个时候Handler的send方法会返回false。

Looper.loop()方法调用后,消息循环系统才会真的起作用。

for (;;) {  
    Message msg = queue.next(); 
    // might block   
    if (msg == null) {  
    // No message indicates that the message queue is quitting.       
    return; 
    ......
    try {    msg.target.dispatchMessage(msg);    
    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
    }finally {   
        if (traceTag != 0) {   
         Trace.traceEnd(traceTag); 
        }
    }
    ......
}
  • loop()是一个死循环,唯一跳出循环的方式是MessageQueue的next()方法返回null。
  • 当Looper的quit方法调用后,MessageQueue的quit或者quitSafely就会调用通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null。
10.2.4 Handler的工作原理

Handler的主要工作是负责消息的发送和接收过程。消息的发送可以通过post和send的一系列方法来实现。post的一系列方法最终是通过send的一系列方法实现的。
Handler发送消息的过程仅仅是像消息对应插入一条信息。MessageQueue的next()方法就是返回这条信息给Looper,Looper接收到信息后就开始处理了。消息最后由Looper交给Handler处理。即Handler的dispatchMessage方法会被调用。这个时候Handler就进入了处理消息的阶段。

/** * Handle system messages here. */
public void dispatchMessage(Message msg) {    
    if (msg.callback != null) {    
        handleCallback(msg);   
    } else {   
        if (mCallback != null) {      
            if (mCallback.handleMessage(msg)) {    
                return;   
                }      
            }     
        handleMessage(msg);  
    }
}

dispatchMessage(Handler)处理消息过程如下:
1.检查Message的callback是否为null,不为null就通过 handleCallback()方法 。Message的callback是一个Runable对象,实际上就是Handler的post方法所传递的Runable参数。handleCallback方法为:

private static void handleCallback(Message message) {    
    message.callback.run();
}

2.检查mCallback是否为null,不为null就调用mCallback的handleMessage()方法来处理消息。mCallback的类型是Callback,且是个接口。
(Callback可以用来创建一个Handler,当我们不想派生子类时,可以用Callback来创建一个Handler)
3.最后调用Handler的handleMessage()方法处理信息。

10.3 主线程的消息循环

Android的主线程就是ActivityThread,主线程的入口是main()方法,通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()方法开启主线程循环。

public static void main(String[] args) {
    ......
     Looper.prepareMainLooper();
     ......
     Looper.loop();
    ......
}

ActivityThread需要一个Handler来和消息队列交互,这个Handler就是H,H里面定义了一组消息类型,主要包含了四大组件启动和停止等过程。

class H extends Handler {   
    public static final int BIND_APPLICATION        = 110;
    ......
}
  • ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方法完成ActivityThread的请求后回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H接收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。

11.Android的线程和线程池

AsyncTask,HandlerThread,IntentService本质都是线程。

  • AsyncTask封装了线程池和Handler,主要是为了方便开发者在子线程更新UI。
  • HandlerThread是一种具有消息循环的线程,内部可以使用Handler。
  • IntentService是一种服务,系统对其进行了封装,使其可以更方便地执行后台任务r。IntentService内部采用了HandlerThread来执行任务,当任务执行完毕后, IntentService会自动退出。从任务执行的角度看, IntentService更像一个后台线程,但是 IntentService是一种服务,它不容易被杀死,从而尽量保证任务的执行。如果是一个后台线程,由于这个时候进程中没有活动的四大组件,那么这个进程的优先级就会非常低,会很容易被系统杀死。 这就是 IntentService的优点。

线程是操作系统调度的最小单元,也是一种受限的系统资源,也就是说线程不能无限制的产出,线程的创建和销毁都有相应的开销。
当系统存在大量的线程时,系统会通过时间片轮转的方式调度每个线程,所以说不可能做到绝对的线程并行,除非线程数量小于CPU核心数。

11.1主线程和子线程

主线程也叫UI线程就是ActrivityThread,里面有个main方法,里面会初始化Looper,和执行loop方法。
主线程的作用是运行四大组件以及处理它们和用户的交互,而子线程的作用则是执行耗时任务。

11.2Android中的线程形态

11.2.1 AsyncTask
  • AsyncTask是一种轻量级的异步任务类,是一个抽象的泛型类。
  • AsyncTask有4个核心方法:
    1.onPreExecute:执行后台耗时操作前被调用,通常用于进行初始化操作。
    2.doInBackground:必须重写,异步执行后台线程要完成的任务,耗时操作将在此方法中完成。
    3.onProgressUpdate:当在doInBackground方法中调用publishProgress方法更新任务执行进度后,将调用此方法.通过此方法我们可以知晓任务的完成进度。
    4.onPostExecute:当doInBackground方法完成后,系统将自动调用此方法,并将doInBackground方法返回的值传入此方法.通过此方法进行UI的更新。
  • AsyncTask使用的限制条件:
    1.AsyncTask的类必须在主线程加载。
    2.AsyncTask的对象必须在主线程中创建
    3.execute方法必须在住线程调用
    4.不要在程序中直接调用它的4个核心方法
    5.一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行异常
    6.Android1.6以前AsyncTask是串行执行任务的,1.6的时候AsyncTask开始采用线程池里处理并行任务,但是从3.0开是,为了避免AsyncTask所带来的并发错误,AsyncTask又采用一个线程串行执行任务。尽管如此,3.0以后版本中,任然可以通过AsyncTask的executeOnExecute方法并行的执行任务。
  • AsyncTask的工作原理
    execute()内部会调用executeOnExecutor()方法
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {    
   return executeOnExecutor(sDefaultExecutor, params);
}

  • AsyncTask中有两个线程池(SerialExecute和THREAD_POOL_EXECUTOR)和一个Handler(internalHandler)。其中线程池SerialExecute用于任务的排队,THREAD_POOL_EXECUTOR用于真正地执行任务。internalHandler用于执行环境从线程池切换到主线程。
11.2.2 HandlerThread

HandlerThread继承Thread,只是在run方法里创建了looper,并调用了loop()方法。(Looper的构造方法源码中,会创建一个消息队列(MessagerQueue))

@Override
public void run() {   
    mTid = Process.myTid();   
    Looper.prepare();  
    synchronized (this) {      
        mLooper = Looper.myLooper();    
        notifyAll(); 
    }   
    Process.setThreadPriority(mPriority);  
    onLooperPrepared();   
    Looper.loop();  
    mTid = -1;
}

由于HandlerThread的run方法是一个无限循环(因为调用了Looper.loop()),所以当确定不再需要使用HandlerThread的时候应该通过它的quit或者quitSafely方法终止线程的执行。

11.2.3 IntentService

IntentService是一种特殊的Service,继承Service并且是一个抽象类,因此必须创建它的子类才能使用IntentService。IntentService可用于执行后台任务,任务执行完成后悔自动停止。
在实现上IntentService封装了HandlerThread和Handler。(从它的onCreate方法可以看出)

@Override
public void onCreate() {   
    // TODO: It would be nice to have an option to hold a partial wakelock   
    // during processing, and to have a staticv startService(Context, Intent)   
    // method that would launch the service & hand off a wakelock.    
    super.onCreate();  
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");  
    thread.start();    
    
    mServiceLooper = thread.getLooper(); 
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

IntentService第一次被启动的时候,调用用onCreate()方法,内部会创建一个HandlerThread ,然后通过HandlerThread 的Looper去创建一个ServiceHandler对象mServiceHandler。这样通过mServiceHandler
发送的消息最终都会在HandlerThread 中执行。

11.3Android中的线程池

线程池的优点:
1,.重用线程池里的线程,避免因为线程的创建和销毁而带来的性能开销。
2.能有效的控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
3.能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。
Android中的线程池都是直接或间接通过配置ThreadPoolExecute来实现的。

11.3.1ThreadPoolExecute

比较常用的构造方法如下

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {   
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit,  workQueue,  Executors.defaultThreadFactory(), defaultHandler);
}

corePoolSize 线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使他们处于闲置状态。(当ThreadPoolExecute的allowCoreThreadTimeOut属性设置为true的时候,那么闲置的核心线程在等待新任务到来时会有超时策略,这个时间间隔由keepAliveTime所指定。当等待时间超过keepAliveTime所指定的时间后,核心线程就会被终止)
maximumPoolSize线程池所能容纳的最大线程数,当活动线程数达到这个值的时候,后续线程会被阻塞。
keepAliveTime非核心线程,闲置时的超时时长,超过这个时长,非核心线程就会被回收。当ThreadPoolExecute的allowCoreThreadTimeOut属性设置为true的时候,keepAliveTime同样会作用于核心线程。
unit 用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒),TimeUnit.SECONDS(秒),TimeUnit.MINUTES(分钟),
workQueue 线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。
threadFactory线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,里面只有一个方法

public interface ThreadFactory { 
    Thread newThread(Runnable r);
}

ThreadPoolExecute执行任务遵循的规则
1.如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
2.如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
3.如果步骤2无法将任务插入到任务队列中,这往往是由于任务队列已满。这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
4.如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecute会调用RejectedExecuetionHandler的RejectedExecuetion方法通知调用者。
ThreadPoolExecute的配置参数在AsyncTask中就有体现,AsyncTask对THREAD_POOL_EXECUTOR这个线程池进行了配置:

  1. 核心线程数 = CPU核心数+1
  2. 线程池最大线程数 = CPU核心数 * 2+1
  3. 核心线程无超时机制,非核心线程在闲置时的超时时长为1秒
  4. 任务队列的容量为128
11.3.2线程池的分类

Android有常见的4中线程池,它们都是直接或者间接的通过配置ThreadPoolExecute来实现自己的特性

  • FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {   
    return new ThreadPoolExecutor(nThreads, nThreads, 0L,TimeUnit.MILLISECONDS,new 
    LinkedBlockingQueue<Runnable>());
}

作用: 是一个线程数量固定的线程池,当线程处于空闲状态时,它们不会被回收,除非线程池被关闭了。当所有任务都处于活动状态时,新任务都会处于等待状态,知道有线程空闲出来。由于FixedThreadPool只有核心线程并且这些核心线程不会被回收,意味着它能更加快速地相应外界的请求。通过源码可以看到它只有核心线程,(核心线程数等于最大线程数)并且核心线程没有超时机制,另外任务队列也是没有限制大小的。
特征:
(1)线程池中的线程处于一定的量,可以很好的控制线程的并发量
(2)线程可以重复被使用,在显示关闭之前,都将一直存在
(3)超出一定量的线程被提交时候需在队列中等待
创建方式:
(1)Executors.newFixedThreadPool(int nThreads);//nThreads为线程的数量
(2)Executors.newFixedThreadPool(int nThreads,ThreadFactory threadFactory);//nThreads为线程的数量,threadFactory创建线程的工厂方式

  • CachedThreadPool
public static ExecutorService newCachedThreadPool() {  
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());        
}

作用: 是一种线程数量不固定的线程池(因为Integer.MAX_VALUE是一个很大很大的数,
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程。
特征:
(1)线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
(2)线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
(3)当线程池中,没有可用线程,会重新创建一个线程
创建方式:
Executors.newCachedThreadPool();

  • ScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int 
corePoolSize) {    
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {    
    super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue());
}

作用: 核心线程数量是固定的,非核心线程的数量是没有限制的。并且非核心线程限制时会被回收。主要用于执行定时任务和具有固定周期的重复任务
特征:
(1)线程池中具有指定数量的线程,即便是空线程也将保留
(2)可定时或者延迟执行线程活动
(3)线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
创建方式:
(1)Executors.newScheduledThreadPool(int corePoolSize);// corePoolSize线程的个数
(2)newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);// corePoolSize线程的个数,threadFactory创建线程的工厂

  • SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {  
return new FinalizableDelegatedExecutorService  (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}

作用: 只有一个核心线程。它确保所有的任务都在同一个线程中按顺序执行,SingleThreadExecutor
的意义在于统一所有外界的任务到同一个线程中,这个使得这些任务之间不需要处理线程同步的问题

特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
创建方式:
(1)Executors.newSingleThreadExecutor() ;
(2)Executors.newSingleThreadExecutor(ThreadFactory threadFactory);// threadFactory创建线程的工厂方式

  • SingleThreadScheduledExecutor
public static ScheduledExecutorService  newSingleThreadScheduledExecutor() {   
    return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
}
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, nteger.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue());
}

作用: 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。

特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
(2)可定时或者延迟执行线程活动
创建方式:
(1)Executors.newSingleThreadScheduledExecutor() ;
(2)Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory);//threadFactory创建线程的工厂

12.Bitmap的加载和Cache

12.1 Bitmap的高效加载

BitmapFactory提供了4个方法加载图片:
1.decodeFile 从文件系统加载图片,间接调用了decodeStream方法
2.decodeResource 从资源中加载图片,间接调用了decodeStream方法
3.decodeStream 从输入流加载图片
4.decodeByteArray 从字节数组中加载图片

  • 这四类方法都是android底层实现的,对应着BitmapFactory类的几个native方法。
  • 高效加载Bitmap的核心思想就是采用BitmapFactory的Option来加载所需要尺寸的图片。
  • 通过BitmapFactory.Option裁剪图片,则用到了inSampleSize参数,即采样率。当inSampleSize为1时,采样后的图片大小为原大小。当inSampleSize大于1时,比如为2的时候。采样后的宽高都是原图片大小的1/2,而像素就为原图片的1/4,所以内存也为原来的1/4。
  • **inSampleSize的值总是为2的指数,1,2,4,8,16…如果不是2个指数,则会向下取整,选择一个最接近2的指数来代替。比如inSampleSize为3,系统就会选择2来代替,而不是4。(经过作者验证,并非所有Android版本都成立,因此将其作为一个开发建议即可)

12.2 Android中的缓存策略

优先级:内存>存储设备>网络
目前最常用的缓存算法是LRU算法。
常用的缓存策略一般为两种:

  • LruCache 常被称作为内存缓存,Lru是Least Recently Used的缩写,即最近最少使用算法,当缓存快满的时候会淘汰最近最少使用的缓存目标。
  • DiskCache 常被称作存储缓存,DiskLruCache并不是Android内置的库,而且需要存储权限。算法思想与前者相似,只是操作本地文件。
    通过二者的完美结合,就可以实现一个具有很高实用价值的ImagerLoder。
    强引用 直接的对象引用
    软引用 当一个对象只有软引用存在时,系统内存不足时此对象会被gc回收
    弱引用 当一个对象只有弱引用存在时,此对象随时会被gc回收

12.3 ImageLoder的实现

1.图片压缩功能的实现(BitmapFactory.Option,inSampleSize)
2.内存缓存和磁盘缓存的实现(使用LruCache,DiskCache)
3.同步加载和异步加载的接口设计(先去内存读取,再去磁盘读取,最后再通过线程池今晚网络读取)

13.综合技术

  • 可以使用CrashHandler来获取应用的crash信息。(可以使用友盟统计啊)
  • 一个应用的方法数不能超过65536个,Google提供了mulitidex方案解决,分为多个的的dex文件。(反编译的时候,有的时候不只一个dex文件就是这个原因),微信将某些功能做成插件,也能避免这个问题
  • 插件化
  • 反编译

14.JNI和NKD编程

15.Android的性能优化

  • 布局优化
    首先删除布局中无用的控件和层级,其次选择性能较低的ViewGroup,比如能用线性布局和相对布局实现的界面,优先选择线性布局。因为RelativeLayout相对来说功能比较复杂,它的过程需要花费更多的CPU时间。FrameLayout,LinearLayout都是简单高效的ViewGroup,因此可以多考虑使用它们。
    采用标签提高程序初始化效率,一般与标签配合使用。
    ViewStub继承View,非常的轻量级,且宽高都是0,ViewStub在开发者很多界面正常时间不会显示,比如网络异常界面。通过ViewStub可以做到使用的时候在加载。提高了程序初始化时的性能。
  • 绘制优化
    避免在onDraw方法执行大量操作。体现在两方面:
    1.onDraw中不要创建新的局部对象,因为onDraw方法可能会被多次调用,这样一瞬间就会产生大量的临时对象。
    2.onDraw中不要执行耗时任务,也不能执行成千上万次的循环操作,这会造成View的绘制过程不流畅。
  • 内存泄露优化
    1.静态变量导致的内存泄露
    2.单例模式导致的内存泄露
    3.属性动画导致的内存泄露
    4.ListView和Bitmap优化
    5.线程优化

猜你喜欢

转载自blog.csdn.net/qq_38679144/article/details/87817037
今日推荐