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(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的弹性滑动一般有以下三种方法:
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.常见的滑动冲突的解决方法有两种:
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;
}
/**
* 重写子元素的事件分发方法
*/
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有三类,每一种都有特殊的含义:
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的方法有如下几种:
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)的源码):
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类:
2.自定义View过程中,如果处理不好,会影响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/