2019年5月-8月每日收获的知识点

2019.5.5

1.AIDL文件里就是一个接口,不过这个接口比较特殊,它只能定义方法,不能定义常量;且AIDL所支持的类型有限,只支持以下6种情况的类型:

         1)基本数据类型(int ,double ,float等)。

          2)String和Charsequence类型。

          3)List只支持ArrayList,但入参类型可以定义List。

          4)Map支持HashMap,但入参类型可以定义Map。

          5)自定义的对象必须是实现了Parcelable接口。

          6)AIDL接口类型。

2.在AIDL中自定义的对象除了需要实现Parcelable以外,还需要显示倒入类的包名,即便该类与AIDL文件在同一包名下也需要。并且,需要单独定义文件名为对象类名的AIDL文件,文件里声明该类为Parcelable类型。

3.客户端远程调用服务端的方法,服务端的方法是在Binder线程池里运行的,也就是说客户端调用服务端方法一般不要在UI线程里操作,而服务端的方法里有耗时操作的话一般不要开线程,因为其本身运行在Binder线程池里。

4.CopyOnWriteArrayList支持并发读写,RemoteCallbackList专门用来处理进程间注册、反注册监听事件,因为我们知道我们客户端传给Binder的对象,会在Binder线程池里做转换,会导致返反注册的时候找不到相应的注册对象,RemoteCallbackList能解决这个问题。

5.AsyncTask内部实现原理就是Handler+Thread,官方文档说它的构造方法只能在UI线程中调用,但实际上,也能在子线程中构建实例,并调用execute()方法。

6.Binder是Android中的一个类,实现了IBinder接口。从IPC角度来说,它是Android的一种跨进程通信方式;从应用层来说,Binder是客户端和服务端通信的媒介,客户端bindService后,获得一个服务端的Binder对象,该对象包含服务端的业务调用,通过该对象,,客户端可以访问服务端提供的服务和数据;从Android Framework来说,Binder是ServiceManager连接各种manager(ActivityManager、PackageManager等)和ManagerService的桥梁。

7.为什么要有代理模式:控制和管理对类的访问。

2019.5.9

1. ContentProvider:天生用来进程间通信,底层实现也是基于Binder,但数据形式不仅限于数据库,还支持文件如图片、视频等。ContentProvider提供文件句柄给外部使用,以便能操作文件。系统为我们预置了很多ContentProvider,比如通讯录、日程表等。

 2. 我们也可以自定义ContentProvider,并实现其6个回调方法,其中onCreate()用来做初始化的一些操作,其运行在Main线程里,不能做耗时操作,其他的4个方法query、insert、update、delete、getType都运行在Binder线程池中。在做增删改查的操作中,多出现多线程的情况,所以要处理好同步,当ContentProvider中有只有一个SQLiteDatabase对象操作数据库时,我们可以不考虑同步问题,因为SQLiteDatabase内部已经做了同步,但如果多个database对象操作数据库,就要想办法保证同步了。

3. 在增删改的方法里因为数据会变动,所以执行完了以后后要调用getContentResolver()对象的notifyChange()方法,通知数据变动。我们在客户端就是用getContentResolver()对象的增删改查方法来进行信息交互。

4.自定义的ContentProvider在清单文件里有android:authorities属性,这个属性是它的唯一标示,在客户端利用改值来创建Uri对象,也可以自定义权限来限制客户端调用,还专门有读和写的权限。如果定义了,客户端要想使用必须声明应有的权限,不然客户端会报异常。

5.加固的好处:防止反编译,提高代码安全性。 常用的方式:梆梆安全、360加固、爱加密等。

   差别:梆梆安全和360加固看不到项目中的类,爱加密能看到类,但看不到具体方法。

  加固原理:第三方加固的应用会生成一个apk,然后把我们的apk读取出来,再封装到第三方的apk里。

6.如何APK瘦身?

        1)开启混淆。2)开启shrinkResource,会把无用的图片资源变成一个像素点。3)对图片使用tinyPng压缩。4)使用webP格式进一步压缩图片资源。5)使用三方库的时候,引入代码,删减不需要的部分。

7.后台返回的字符串里有\n,但在到移动端后换行前多了转义符反斜杠,\\n。处理方式字符串里将\\n替换成\n再显示即可。

mMsgView.setText(msg.replace("\\n", "\n"));

2019.5.10

1.Socket分为流式套接字和用户数据报套接字两种。流式套接字对应网络的传输控制层的TCP协议,用户数据报套接字对应网络的传输控制层的UDP协议。

2.TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过“三次握手“才能完成,为了提供稳定的数据传输功能,其本身提供了超时重连机制,因此具有很高的稳定性。

3.UDP是无连接的,提供不稳定的单向通信,当然其也可以实现双向通信功能。在性能上,UDP具有更高的效率,其缺点是不能保证数据一定能够正确传输,尤其是网络拥堵的情况下。

4.使用Socket来通信需要注意两点,1)需要声明网络权限;2)不能在主线程中访问网络,会抛异常。

5.不同模块对资源的命名可能产生冲突,为了解决这个问题,我们可以在对于module的build.gradle文件里添加资源名前缀显示,如下所示:

           android{

                      resourcePrefix  'contact_'

          }

2019.5.18

1.View的坐标是正方向是向右和向下。View的位置参数队形left、right、top、bottom,其中left表示左上角横坐标,top表示左上角纵坐标,right表示右下角横坐标,bottom表示右下角纵坐标。也即有:width = right - left   ,height = bottom - top;  在View的源码中对应mLeft,mRight,mTop,mBottom,获取方式是getLeft(),getRight(),getTop(),getBottom()。这些坐标都是相对于父容器来说的。

2.从Android 3.0 开始,View又增加了几个额外参数,x,y,translationX,translationY。其中x,y是左上角的坐标,translationX和translationY是左上角相对于父容器的偏移量,其中偏移量默认值为0。和View的四个基本位置参数一样,View也为他们提供了get/set方法,这几个参数换算关系如下所示:x = left  + translationX ,y = top + translationY。需要注意的是,View在平移的过程中,top和left表示的是原始左上角的位置信息,其值并不会发生改变,此时发生改变的是x、y、translationX、translationY。

2019.5.21

1.MontionEvent:通过该对象我们可以得到点击事件发生的x和y坐标。系统提供了两组方法:getX/getY和getRawX和getRawY。他们的区别是,getX/getY返回的是相对于当前View左上角的x和y的坐标,而getRawX/getRawY返回的是相对于手机屏幕左上角的x和y坐标。

2.TouchSlop:系统所能识别出的被认为是滑动的最小距离,在不同设备上这个值可能是不同的。通过如下方式即可获取这个常量:ViewConfiguration.get(getContext()).getScaledTouchSlop()。

2019.5.22

1.Intent无法传递大数据,是因为其内部使用了Binder通信机制,Binder事务缓冲区限制了传递数据的大小,其限定为1M,但这个大小是当前进程共享的,并不是说传递1M以下的数据就不会出现问题。

2.传递长字符串、bitmap等大数据不要用Intent,否则会抛TransactionTooLargeException。

3.解决这种问题,根据数据的标示还原数据,或者先持久化再还原,也可以用EventBus的粘性事件来解决。在某些情况下,Activity会销毁重建,如果用Intent传值,可以从Intent中继续获取到上一次页面传值的数据,如果用EventBus传值,有可能导致数据丢失。

4.在Activity中使用EventBus,需要根据activity的生命周期register()和unregister()。普通的事件,只会发生在register之后才能接收的到,register之前是收不到的。但EventBus的粘性事件(StickEvent)可以实现,它内部维护了一个Map叫stickEvents用来存储粘性事件。

5.粘性事件的发送使用的是postSticky(),它会将该事件缓存到stickEvents中,当下次注册的时候,将事件取出,抛给注册的组件,以此来达到滞后事件的发送和接受。但存储在Map里的对象,不会自动清理,需要开发者手动清理,EventBus提供了两个方法,定向清理对象和一次性全部清理对象,removeStickyEvent()和removeAllStickyEvents()。

6.三次握手过程:

            刚开始客户端处于closed状态,服务端处于listen状态。

            第一次握手:客户端发送SYN(syncnize)报文,并指明客户端的初始化序列号ISN,此时客户端处于SYN_send状态。

            第二次握手:服务端接收到客户端发来的SYN报文后,会以自己的SYN报文做应答,并指定自己的初始化序列号ISN,同时                                   会把客户端的ISN加1作为ACK(acknowledgement)的值,表示自己收到了客户端的SYN报文,此时服务端                                     处于SYN_REVD状态。

           第三次握手:客户端接受到服务端发的SYN+ACK报文后,会回复一个ACK报文给服务端,当然这个值也是以服务端的ISN                                     加1作为值,表明客户端已经接收到SYN报文,此时客户端处于establised状态。

           服务器收到ACK报文后,也处于establised状态,此时双方便建立起了链接。        

7.为什么需要三次握手,不是两次?

      第一次握手,客户端发,服务端接受,能确认客服端发送能力和服务端接收能力正常;第二次握手,服务端发,客户端接收,能确认服务端的发送、接受能力,客户端的发送、接收能力正常,但服务端并不知道客户端接收是否正常,所以需要第三次握手;客户端发,服务端接收,表明客户端收发能能和服务端收发能力都正常。

8.三次握手的作用?

      1)确认双方的接收能力和发送能力是否正常。

       2)指定自己的初始化序列号,为后面的可靠传输做准备。

       3)如果是https协议的话,三次握手的过程中还会进行数字证书的验证以及加密密钥的生成等。

9.ISN码固定吗?

    三次握手的一个重要功能是客户端和服务端交换ISN码,以便让对方知道接下来接收数据的时候如何按照序列号组装数据,如果ISN是固定的,攻击者很容易猜到后续的确认号,因此ISN是动态生成的。

10.半连接队列?三次握手时可以携带数据吗?

     在一次请求的三次握手过程没有完成时(也即没有完全建立连接时),服务端会将此状态下请求放入一个队列里,也即半连接队列。

三次握手前两次是不可以携带数据的,第三次握手可以,如果前两次可以的话,服务器容易受到恶意攻击,比如将SYN报文中放入大量数据,服务端就要话时间和内存来接收数据,而第三次握手时,客户端已经是established状态了,对客户端来说,已经建立了连接,并且也知道服务端的收、发能力是正常的,所以携带数据没毛病。

11.四次挥手过程?

       开始双方都处于established状态,假如是客户端先发起关闭请求:

       第一次挥手:客户端发送一个FIN报文,报文中包含一个序列号,此时客户端处于CLOSED_WAIT1状态。

       第二次挥手:服务端收到FIN报文后,向客户端发送一个ACK报文,并把客户端的序列号+1作为ACK报文的序列号值,表示收                               到了客户端的报文,此时服务端处于CLOSED_WAIT2状态。

      第三次挥手:如果服务端也想断开连接,会向客户端发送一个FIN报文,且指定一个序列号,此时服务端处于LAST_ACK状态。

     第四次挥手:  客户端收到FIN报文后,会发送一个ACK报文,且把服务端序列号+1作为报文的序列号值,此时客户端处于                                       TIME_WAIT状态,需要过一阵子确保服务端收到自己的ACK报文后才会进入CLOSED状态。

     服务端收到ACK报文后就关闭连接,处于CLOSED状态。

     客户端处于TIME_WAIT状态后,要等一阵子才close的原因是因为,在一定时间内如果服务端没有收到ACK报文,则会重新发送FIN报文,客户端再次收到FIN后,就知道之前的ACK报文丢失了,需要重新发送。

2019.5.23

1.VelocityTracker:速度追踪,用于追踪手指在滑动过程中的速度,包括水平方向和竖直方向的速度,如果手指从右向左滑动,水平方向的速度为负值。使用方法大致为:

//首先在View的onTouchEvent方法中追踪当前点击事件的速度。
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
//如果想知道当前水平和竖直方向的滑动速度,可以如下:
velocityTracker.computerCurrentVelocity(1000);   //单位为毫秒,一段时间内手指滑动过的像素数
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();

//当不用的时候,需调用clear方法来重制并回收内存:
velocityTracker.clear();
velocityTracker.recycle();

2.GestureDetector:手势检测,用于辅助检测用户的单击、滑动、长按、双击等事件。使用他的时候一般要实现OnGestureListener接口里的一些方法,比较常用的有onSingleTapUp、onFling、onScroll、onLongPress、onDoubleTap等。实际开发中,可以不用GestureDetector,完全可以自己在View的onTouchEvent()中实现所需监听,看个人喜好。一般如果只监听滑动相关,建议在onTouchEvent中实现,如果要监听双击这种行为,那么就用GestureDetector。

3.Scroller:弹性滑动对象,用于实现View的弹性滑动。它需要和View的computeScroll()方法配合才能完成这个功能。

2019.6.10

1.gradle常用命令:

    1)查看帮助:./gradlew -help   或 ./gradlew -h  或./gradlew -?

     2)查看所有可执行的tasks:./gradlew tasks

     3)强制刷新依赖:./gradlew --refresh-dependencies assemble

2019.6.11

1.服务端的异常一般分为三种:

     1)客户端所传参数异常 ;2)需要用户知道的业务异常;3)通用的服务端异常。

   客户端应该添加异常兜底机制,防止用户看到一些无法理解的报错信息。

2019.6.21

1.translationX和translationY是View左上角相对于父容器的偏移量。

2.Android中的多线程切换,主要利用Runnable和Callable来定义工作内容,使用线程池来实现异步并行,使用Handler机制通知主线程(有些视情况而定),使用Future的接口回调。

3.每个Thread对象都可以启动一个新线程,也可以不启动。

thread.start();   //启动一个新线程
thread.run();     //不启动新线程,在当前线程执行。

4.Thread是有优先级的,优先级高的就有机会获得更多的CPU资源,默认情况下,新建的Thread和当前的Thread优先级一样。设置优先级的两种方式:

thread.setPriority(Thread.MaX_PRIORITY);   //1-10,通过线程设置

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //-20~19,通过进程设置,一般Android建议用这种设置

5.线程间通信的几种方式:

    1)Future:如果做简单的通信,最常用的是通过接口回调来实现。Future就是这样一种接口,它可以解决线程通信的问题,Future接口定义了done、canceled等回调函数,当工作线程的任务完成时,它会在工作线程中通过回调告知我们,我们再采用其他手段通知其他线程。

mFuture = new FutureTask<MyBizClass>(runable) {
    
    @Override
    protected void done(){
    
        ...//还是在工作线程里。
    }
}

     2)Condition :它其实是和Lock一起使用的,因为其本身定位就是一种多线程间协调通信的工具,Condition可以在某些条件下,唤醒等待进程。

  

Lock lock = new ReentrantLock();
Condition notFull = lock.newCondition();  //定义Lock的Condition
...
while(count == items.length)
         notFull.await();   //等待conditon的状态
...
  notFull.signal();         //达到condition的状态

3)Handler:最完整的线程间通信机制,也是我们最熟悉的,Handler利用线程封闭的ThreadLocal维持一个消息队列,Handler的核心是通过这个消息队列来传递Message,从而实现线程间通信。

6.AsyncTask的多线程任务是通过线程池实现的工作线程,在完成任务后利用Future的done回调来通知任务完成,并通过Handler机制通知主线程去执行onPostExecute等回调函数。

2019.6.26

1.实现View的弹性滑动一般有以下三种方法:

  1. 使用Scroller。其本身不能实现view的滑动,需要配合view的computeScroll方法才能完成弹性滑动的效果,它不断地让view重绘(通过postInvalidate()方法),而每一次绘制距离起始时间会有一个时间间隔,通过这个时间间隔就可以得出当前View的滑动位置,知道了滑动位置就可以通过scrollTo()方法来完成View的滑动。就这样,每一次重绘都会导致View进行小幅度滑动,知道时间间隔达到初始设置的时间间隔,这就是Scroller的滑动机制。
  2. 通过动画。我们可以使用属性动画很容易的实现View的滑动,也可以模仿Scroller的原理,在动画的addUpdateListener回调方法里,通过动画的每一帧到来时,获得动画的完成比例,然后根据这个比例计算出当前View所要滑动的距离,再通过scrollTo方法完成滑动。需要注意的是,通过这种方式实现的是View内容的滑动,View本身的位置并没有滑动。
  3. 使用延时策略。通过发送一系列的延时消息,从而达到一种渐进式效果,具体来说可以使用Handler的sendMessageDelay()或者View的postDelayed()方法,也可以使用线程的sleep方法。

2019.7.2

1.从手指接触屏幕开始,到手指离开屏幕那一刻结束,这个过程所产生的一系列事件叫同一个事件序列。

2.正常情况下,一个事件序列只能被一个View拦截且消耗。一旦一个元素拦截了某次事件,那么同一事件序列的所有事件都会直接交给他处理,因此同一事件序列的事件不能分别由两个View同时处理,但通过特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent()强行传递给其他View处理。

3.一旦一个View决定拦截,那么这个事件序列都只能由他处理(如果事件能够传递给它的话),并且他的onInterceptTouchEvent()不会再被调用。

4.某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent()返回了false),那么同一事件序列的其他事件都不会再交给他处理,并且事件将重新交给它的父元素去处理,即父元素的onTouchEvent()会被调用。(事件交给你,你就必须消耗掉,否则,其他事件再也不交给你处理)

5.如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent()方法并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。

2019.7.3

1.在项目的build.gradle里添加如下代码,可以使得gradle每隔多长时间检测远程版本更新:

buildscript {
        configurations.all {
        it.resolutionStrategy.cacheDynamicVersionsFor(5, 'minutes')
        it.resolutionStrategy.cacheChangingModulesFor(0, 'seconds')
    }

}

2.项目里添加三方依赖的时候最好指定版本,如果像这些写compile 'com.baidu.mobstat:mtj-sdk:latest.integration',每次当gradle文件放生变化时都会重新下载,导致编译变慢,最好像这样指定版本:compile 'com.baidu.mobstat:mtj-sdk:3.9.3.8'。

2019.7.4

1.Activity对点击事件的分发过程:

        当一个点击事件发生时,事件最先传给当前的Activity,由Activity的dispatchTouchEvent()方法进行事件派发,具体工作是由Activity内部的Window来完成的。Window会将事件传递给decor view,decor view就是当前界面的底层容器(即setContentView()所设置的View的父容器)。

2.Window是个抽象类,它的唯一实现类是PhoneWindow,PhoneWindow直接将事件传递给DecorView。

3.DecorView是顶级View,继承FrameLayout,所以事件最终会传递给View。

4.顶级View对事件的分发过程:

      点击事件到达顶级View(一般是个ViewGroup)后,会调用ViewGroup的dispatchTouchEvent()方法,其方法内部是这样的,如果顶级的ViewGroup拦截事件,即onInterceptTouchEvent()返回true,则事件交由ViewGroup处理,这时如果ViewGroup的mOnTouchListener被设置,则onTouch()会被调用,否则onTouchListener()会被调用,也就是说onTouch会屏蔽到onTouchListener()。在onTouchListener中如果设置了mOnClickListener,则onClick会被调用。如果顶级View不拦截事件,则事件会传递到它所在的事件链上的子View,这时子View的dispatchTouchEvent()方法就会被调用。到此,事件已经从顶级View传递到了子级View。接下来的传递过程和顶级View是一致的。

5.子view可以通过requestDisallowIntercept()来干预父View的事件分发,一旦设置以后,ViewGroup将无法拦截除ACTION_DOWN以外的事件,因为ViewGroup在事件分发的时候,如果是ACTION_DOWN事件就会重置FLAG_DISALLOW_INTERCEPTION这个标记位,将导致子View中设置这个标记位失效。

2019.7.10

1.常见的滑动冲突场景有以下三种:

  • 外部滑动方向和内部滑动方向不一致;
  • 外部滑动方向和内部滑动方向一致;
  • 上面两种情况的嵌套;

2.常见的滑动冲突的解决方法有两种:

  • 外部拦截法:所谓的外部拦截法是指点击事件都先经过父容器拦截处理,如果父容器需要此事件就做拦截,不需要就不拦截。这种方法比较符合点击事件的分发机制,它需要重写父容器的onInterceptTouchEvent()方法,在内部做相应的拦截处理即可。(如果父容器需要处理事件,那么在onInterceptTouchEvent()方法会返回true,然后在父容器的onTouchEvent()方法内部做事件处理)代码示例如下:
public boolean onInterceptTouchEvent(MotionEvent event){

    boolean intercepted = false;
    
    int x = (int) event.getX();
    
    int y = (int) event.getY();
    
    switch(event.getAction()){

       case MotionEvent.ACTION_DOWN:
        //此处必须返回false,不然后续的move和down事件都会直接交给父容器处理,不会再传递给子元素
        intercepted = false;  
        break; 

        case MotionEvent.ACTION_MOVE:
        if(父容器需要当前点击事件){
            intercepted = true;
        }else{
            intercepted = false;
        }
        break;

        case MotionEvent.ACTION_UP:
        //此处必须返回false,否则子元素就无法接收到UP事件,子元素的onClick事件就无法触发。
        intercepted = false;
        break;
    }

    mLastXIntercept = x;
    mLastYIntercept = y;
    
    return intercepted;
}
  • 内部拦截法:内部拦截法是指父容器不拦截任何事件,所有事件都传递给子元素,如果子元素需要就直接消耗,否则交由父容器处理。该方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来稍复杂。它需要重写子元素的dispatchTouchEvent()和父容器的onInterceptTouchEvent()。代码示例如下:
/**
 * 重写子元素的事件分发方法
 */
public boolean dispatchTouchEvent(MotionEvent event){
    
    int x = (int) event.getX();
    int y = (int) event.getY();
    
    switch(event.getAction()){
    
        case MotionEvent.ACTION_DOWN:
            parent.requestDisallowInterceptTouchEvent(true);
            break;

        case MotionEvent.ACTION_MOVE:
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            if(父容器需要此类点击事件){
                parent.requestDisallowInterceptTouchEvent(false);
            }
            break;         
        
        case MotionEvent.ACTION_UP:
            break;
    }

    mLastX = x;
    mLastY = y;

    return super.dispatchTouchEvent(event);

}
/**
* 重写父元素的事件拦截方法
*/
public boolean onInterceptTouchEvent(MotionEvent event){
    
    int action = event.getAction();
    
    /**
     * 父元素拦截除了Down事件以外的事件,这样当子元素调用 
     * requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需事件。
     * DOWN事件不受FLAG_DISALLOW_INTERCEPT控制,所以一旦父容器拦截了down事件,那么所有的事件
     * 都无法传递到子元素了。
     */
    if(action == MotionEvent.ACTION_DOWN){
        return false;
    }else{
        return true;
    }
}

3.当点击数事件传递到ViewGroup时,dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()三者的关系如下伪代码所示:

public boolean dispatchTouchEvent(MotionEvent event){
    
    boolean consume = false;

    if(onInterceptTouchEvent(event)){
        consume = onTouchEvent(event);
    }else{
        consume = child.dispatchTouchEvent(event);
    }    
    
    return consume;

}

2019.7.11

1.ViewRoot是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window,同时创建ViewRootImpl对象,并将ViewRootImpl和DecorView建立关联。

2.View的绘制流程从ViewRoot的performTraversals()方法开始的,经过measure、layout、draw三个过程才将View绘制出来,其中measure用来测量View的宽高,layout用来确定View在父容器中的放置位置,draw负责View绘制在屏幕上。

3.DecorView作为顶级的View,它是一个FrameLayout,一般情况下,它内部会包含一个竖直方向的LinearLayout,在LinearLayout中有上下两部分,上面是标题栏,下面是内容,我们Activity中setContentView方法就是设置内容部分,内容部分是一个id为content的FrameLayout。

//得到content
ViewGroup content = (ViewGroup) findViewById(android.R.id.content);
//得到我们Activity中设置的View
content.getChildAt(0);

2019.7.12

1.MeasureSpec(测量规格):代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize。SpecMode是测量模式,SpecSize是指在某种测量模式下的规格大小。

2.在测量过程中,系统会将View的LayoutParams根据父容器所施加的规格转换成对应的MeasureSpec,然后再根据这个MeasureSpec测量出View的宽、高。这里的宽高是测量的宽高,不一定等于View的最终宽、高。

3.SpecMode有三类,每一种都有特殊的含义:

  • UNSPECIFIED(未明确的):父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态。
  • EXACTLY(精确的):父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize指定的值。它对应与LayoutParams中的match_parent和具体的数值这两种模式。
  • AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现,它对应与LayoutParams中的wrap_content。

4.MeasureSpec和LayoutParams的对应关系:

系统内部是通过MeasureSpec来进行View的测量,View的MeasureSpec由自身LayoutParams和父容器两者共同决定。对于顶层的DecorView,其MeasureSpec由窗口的尺寸和其自身LayoutParams来共同决定。MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。

2019.8.2

1.ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成。

2.View的绘制流程是从ViewRoot的performTraversals方法开始,它经过measure、layout、draw三个过程才将一个view绘制出来,其中measure用来测量View的宽高,layout用来确定View在父容器中的放置位置,而draw则负责将View绘制在屏幕上。

3.measure完成后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取到View测量后的宽高,在几乎所有情况下,他都等同与View的最终宽高,特殊情况除外。layout过程决定View的四个定点的坐标和实际view的宽高,完成以后,可以通过getTop、getBottom、getLeft、getRight来拿到View的四个定点位置,并可以通过getWidth、getHeigth来获得view的最终宽高;Draw过程则决定了view的显示,只有draw方法完成以后View的内容才能呈现在屏幕上。

2019.8.6

1.MeasureSpec和LayoutParams的对应关系:我们可以给View设置LayoutParams,在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽、高。但MeasureSpec不是唯一有LayoutParams决定,LayoutParams需要和父容器的MeasureSpec一起才能决定View的MeasureSpec,从而进一步确定View的宽高。

2.对于普通View来说,其measure过程由ViewGroup传递而来,ViewGroup在对子元素进行测量之前,会先调用measureChildWithMargins()方法来得到子元素的MeasureSpec。很显然子元素的MeasureSpec与父元素的MeasureSpec和自身的LayoutParams有关。

3.一般来说,当View采用固定宽高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且其大小遵循LayoutParams中的大小;当View的宽高是match_parent的时,如果父容器的模式是精准模式,那么View也是精准模式并且其大小是父容器的剩余空间,如果父容器的大小是最大模式,那么View也是最大模式且其大小不会超过父容器的剩余空间;当View的宽高是wrap_content时,不管父容器的模式是精准模式还是最大模式,View的模式总是最大化且大小不超过父容器的剩余空间。

2019.8.7

1.View的工作流程:View的工作流程主要是指measure、layout、draw这三大流程,即测量、布局、和绘制。其中measure确定View的测量宽高,layout确定View的最终宽高和四个定点的位置,而draw则将View绘制到屏幕上。

2.measure过程:分两种情况,如果只是一个原始View的话,那么通过measure方法就完成了其测量过程;如果是一个ViewGroup,除了完成自身的测量过程以外,还会遍历去调用所有子元素的measure方法,各个子元素再递归执行这个流程。

3.View的measure过程:View的measure过程由measure方法来完成,measure方法是一个final类型的方法,意味着子类不能重写此方法,在view的measure方法中会调用View的onMeasure方法,也即是只需要看onMeasure方法即可。在onMeasure内部调用了一个setMeasuredDimension方法,来设置View宽高的测量值。

2019.8.8

1.在setMeasureDimension(width,height)方法中传入的参数是方法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;
    }
}

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

 

2.由getDefaultSize()方法来看,View的宽高由specSize决定,我们可以得出结论:直接继承View的自定义控件需要重写onMeasure()方法并设置wrap_content时的自身大小,否则在布局中设置wrap_content就相当于使用match_parent。

3.如何解决这个问题:在onMeasure()方法中针对宽高为wrap_content的情况,设置一个默认的宽、高。至于这个默认值,灵活指定即可。

4.ViewGroup的measure过程:除了完成自己的测量过程以外,还会遍历去调用子元素的measure方法,各个子元素再递归去执行这个过程。和View 不同的是,ViewGroup是一个抽象类,它没有重写View的onMeasure方法,但是它提供了一个叫measureChildren的方法。

5.ViewGroup并没有定义其测量的具体过程,因为它是一个抽象类,其测量过程需要各个子类去具体实现。ViewGroup之所以不像View一样对onMeasure方法做统一处理,是因为其子类有不同的布局特性,导致他们测量细节各不相同。

6.LinearLayout的大致测量过程:其分为竖直方向和水平方向,如果是竖直方向,它会遍历每个子元素,并将子元素的高和margin依次累加,当子元素测量完毕后,LinearLayout会根据子元素的情况测量自己的大小。针对竖直方向的LinearLayout而言,它的水平方向的测量过程遵循View的测量过程,在竖直方向的测量过程和View有所不同。具体的来说,如果它布局中采用的是match_parent或具体数值,那么它的测量过程和View一致,即高度为specSize;如果他的布局采用wrap_content,那么它的高度是所有子元素所占的高度总和,但是仍然不能超过它的父容器的剩余空间,当然最终高度还要考虑自身在竖直方向的padding。

2019.8.12

1.measure完成以后,我们可以通过getMeasuredWidth、getMeasuredHeight方法来获取View的测量宽高。但在某些极端情况下,系统可能需要多次measure才能确定最终的宽高,在这种情况下,在onMeasure方法中拿到的宽高是不准确的,一个比较好的习惯是在onlayout方法中获取View的测量宽高或者最终宽高。

2.如果我们想在Activity一启动就想获取View的宽高,其实在onCreate、onStart、onResume中均无法正确得到某个View的宽高信息,这是因为View的measure过程和Acitivity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate、onStart、onResume时某个View已经测量完毕,如果View还没有测量完毕,那么获得的宽高就是0。

3.解决2的方法有如下几种:

  • Activity/View#onWindowFocusChanged:该方法在Activity的窗口得到和失去焦点时均会被调用一次,具体的说就是,当Activity继续执行和暂停时,onWindowFocusChanged均会被调用,如果频繁的进行onResume和onPause,那么该方法也会被频繁调用。在回调该方法时,View都已经初始化完毕,宽高都准备好了,这个时候获取宽高是没有问题的。
  • view.post(runnable):通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了。代码如下:
    protected void onStart(){
        super.onStart();
        view.post(new Runnable(){
            @Override
            public void run(){
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
    }

     

  • ViewTreeObserver:使用ViewTreeObserver的众多回调可以完成这个功能,比如使用onGlobalLayoutListener这个接口,当View树的状态发生改变或者View树内部的View可见性发生改变时,onGlobalLayout方法将被回调,因此这是个获取View宽高的好时机。需要注意的是:伴随着View树的状态改变,onGlobalLayout会被调用多次。典型代码如下:

protected void onStart(){
    super.onStart();
    
    ViewTreeObserver observer = view.getViewObserver();
    observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){

        @Override
        public void onGlobalLayout(){
            view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
        }
    });
}

view.measure(int widthMeasureSpec,int heigthMeasureSpec):通过手动对View进行measure来的到View的宽高,该情况比较复杂。

2019.8.13

1.layout方法的大致流程:首先会通过setFrame方法来设定View的四个顶点的位置,即mLeft、mRight、mTop、mBottom,顶点位置一旦确认,View在父容器中的位置也就确认了;接着会调用onLayout方法,这个方法的用途是父容器确定子元素的位置,和onMeasure方法类似,onLayout的具体实现同样和具体的布局有关,所以View和ViewGroup并没有真正实现onLayout方法。

2.getMeasuredWidth方法和getWidth方法的区别:在View的默认实现中,View的测量宽高和最终宽高是相等的,只不过测量宽高形成于View 的measure过程,而最终宽高形成于View的layout过程,即两者的赋值时机不同,测量宽高稍早一些。开发当中,我们可以默认View的测量宽高就等于最终宽高,但在某些特殊情况下会导致两者不一致,例如,假如重写View的layout方法,代码如下:

public void layout(l,t,r,b){
    super.layout(l,t,r+100,b+100);
}

上述代码会导致在任何情况下View的最终宽高总比测量宽高大100px。另外一种情况是在某些情况下,View需要多次测量才能确定自己的测量宽高,在前几次的测量过程中,其得出的测量宽高有可能可最终宽高不一致,但最终来说,测量宽高还是和最终宽高相同。

2019.8.14

1.draw过程:Draw过程比较简单,它的作用就是将View绘制到屏幕上,View的绘制过程遵循如下几步(也是draw(canvas)的源码):

  • 绘制背景:background.draw(canvas);
  • 绘制自己:onDraw(canvas);
  • 绘制children:dispatchDraw(canvas);
  • 绘制装饰:onDrawScrollBars(canvas);

2.View绘制过程的传递是通过dispatchDraw来实现的,dispatchDraw会遍历所有子元素的draw方法,如此,draw事件就一层一层的传递了下去。

3.View有一个特殊的方法setWillNotDraw,从源码可以看出如果一个View不需要绘制任何内容,那么设置这个标记为true后,系统会进行相应的优化。默认情况下,View没有启用这个标记位,但是ViewGroup会默认启用这个标记位。该标记为对开发的实际意义为:当我们自定义控件继承ViewGroup并且本身不具备绘制功能时,就可以开启这个标记位,从而便于系统进行后续的优化。当然,明确知道一个ViewGroup需要通过onDraw来绘制内容时,我们需要显示的关闭WILL_NOT_DRAW这个标记位。

2019.8.16

1.自定义View一般分为4类:

  1. 继承View重写onDraw方法:这种方式主要用来实现一些不规则效果,静态或动态的显示一些不规则图形。很显然,需要通过绘制的方式来实现,即重写onDraw方法。采用这种方式需要自己支持wrap_content,并自己处理padding。
  2. 继承ViewGroup派生特殊的Layout:这种方式主要用于实现自定义的布局,即除了系统提供的几种常用布局以外,我们需要重新定义一种新布局,当某种效果看起来像几种View组合在一起的时候,可以采用这种方式。采用这种方式稍复杂些,需要合适的处理ViewGroup的测量、布局过程这两个过程,并同时处理子元素的测量和布局过程。
  3. 继承特定的View(比如TextView):这种方式较常见,用来扩展某种已有的View的功能,比如TextView,这种方式比较容易实现,并且该方式不需要自己支持wrap_content和padding。
  4. 继承特定的ViewGroup(比如LinearLayout):该方式也比较常见,当某种效果看起来很像几个View组合在一起的时候,可以采用这种方式实现。采用该方式并不需要自己处理ViewGroup的测量、布局两个过程。它与方法2的区别在于,一般方法2能实现的效果,方法4也能实现,主要是方法2更接近与View底层。

2.自定义View过程中,如果处理不好,会影响View的正常使用或内存泄漏等。具体注意事项如下所示:

  1. 让View支持wrap_content:直接继承View或ViewGroup的控件,如果不在onMeasure中对wrap_content做特殊处理,那么当外界在布局中使用wrap_content时,就无法达到预期效果。
  2. 如果有必要,让你的View支持padding:直接继承View的控件,如果不在draw方法中处理padding,那么padding属性是无法起作用的。直接继承ViewGroup的控件,需要在onMeasure和onLayout中考虑padding和子元素margin对其造成的影响,不然将导致padding和子元素的margin失效。
  3. 尽量不要在View中使用Handler,没必要:View本身就提供了post系列的方法,完全可以替代handler的作用,当然除非你很明确的要使用Handler来发送信息。
  4. View中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWindow:如果有线程或动画需要停止时,那么onDetachedFromWindow是一个很好的时机。当包含此View的activity退出或当前View被remove时,View的onDetachedFromWindow方法会被调用,和此方法对应的是onAttachedToWindow,当包含此View的activity启动时,View的onAttachedToWindow方法会被调用。同时,当View变得不可见时,我们也需要停止线程或动画,如果不及时处理这种问题,有可能造成内存泄漏。
  5. View带有滑动嵌套情形时,需要处理好滑动冲突。

3.下面是一个自定义view的例子,其中处理了wrap_content和padding。

public class CircleView{

    private int mColor = Color.RED;
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
    public CircleView(Context context){
        super(context);
        init();    
    }
    public CircleView(Context context,AttributeSet attrs){

        this.(context,attrs,0);
    }

    public CircleView(Context context,AttributeSet attrs,int defStyleAttr){
        super(context,attrs,defStyleAttr);
        
        TypedArray a = 
           context.obtainStyledAttributes(attrs,R.styleable.CircleView);
        
        mColor = a.getColor(R.styleable.CircleView_circle_color,Color.RED);
        a.recycle();
        init();
    }

    public void init(){
        mPaint.setColor(mColor);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);

        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heigthMeasureSpec);
        
        //以下为处理wrap_content,如果是,则默认给设置个固定值,不然wrap_content不起作用
        if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode ==             
            MeasureSpec.AT_MOST){
            setMesuredDimension(200,200);
        }else if(widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200,heightSpecSize);
        }else if(heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize,200);
        }
            
    }

    @Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        //在onDraw方法里要处理padding,不然在布局里设置padding会不起作用
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();

        int width = getWidth()-paddingLeft-paddingRight;
        int height = getHeight - paddingTop -paddingBottom;
        
        int radius = Math.min(width,height)/2;
        canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,radius,mPaint);
        
    }

}

2019.8.19

1.继承ViewGroup派生特殊的Layout:这种方式主要用于实现自定义布局,采用这种方式稍微复杂些,需要合适的处理ViewGroup的测量、布局这两个过程,并同时处理子元素的测量和布局两个过程。

2.快速查看某个Activity信息:adb shell dumpsys activity top

3.如果只想查看栈顶Activity的名称:adb shell dumpsys activity top | grep "ACTIVITY" -A 0。

2019.8.28

1.查看手机anr文件:adb shell ls /data/anr/

2.导出anr文件到桌面:adb pull /data/anr/traces.txt ~/Desktop/ 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值