Android开发面经4

1、JNI

JNI详解
JNI面试题

Android JNI开发步骤(借助于NDK)?

Android为了便于开发人员调用C、C++等本地代码,为我们封装了一层接口,也就是JNI(Java Native Interface Java本地接口),我们都知道Java是跨平台语言,正是因为跨平台导致了Java程序在和本地交互的时候出现了短板,使得他与本地代码的交互能力不是 很强,于是Java为我们提供了JNI来专门和本地代码来进行交互,具体到Android中,为我们提供了NDK来方便的通过JNI访问本地代码,接下来 我们简单描述下通过NDK开发JNI的流程, 先来说说通过JNI调用本地代码的原理:
(1):我们把C/C++源程序编译打包成动态库(dll或者so),存放在我们项目中的lib目录下;
(2):在java程序中我们调用带native修饰的方法,他的具体实现是在C/C++程序中的,系统会调用动态库中相应的实现方法,如果有需要的话,还会返回处理结果; 开发步骤:
(1):首先创建一个Android工程,和我们平常创建没什么区别;
(2):添加本地动态库支持,具体就是右击刚刚创建的Android项目,找到Android Tools,接着找到Android Tools中的Add Native Support,然后输入动态库的名称(系统会为我们自动加上前缀"lib"和后缀"so"的),创建完成之后会在项目中生成一个jni的目录,目录里面 会有一个.cpp以及Android.mk文件;
(3):创建两个文件,一个是java源文件,这个文件中其实就是对native方法的声明而已,我们称之为代理动态库方法;另一个是C/C++源文件, 是对代理方法的实现;首先对于Java代理文件,静态导入动态库,并编写好代理方法,然后利用javah命令生成C语言的方法签名,接着复制方法签名到 C/C++源文件中,补充参数名和方法体就可以了;
(4):接着我们需要完成C/C++源文件的方法体内容,也就是我们具体的业务逻辑操作部分了;
(5):最后就是在我们的程序中调用native方法来调用C/C++代码了;

2、Android常见动画和UI效果

事件分发机制?

首先应该搞清楚两个问题:事件分发机制分发的是什么?怎么进行分发?
分发的是MotionEvent事件了,因而我们讨论的问题就成了当MotionEvent事件生成之后,事件是怎么传递到某一个View控件上面并且得到处理的过程;
android事件产生后的传递过程是从Activity—>Window—>View的,即隧道式传递,而View又分为不包含子 View的View以及包含子View的ViewGroup,事件产生之后首先传递到Activity上面,而Activity接着会传递到 PhoneWindow上,PhoneWindow会传递给RootView,而RootView其实就是DecorView了,接下来便是从 DecorView到View上的分发过程了,具体就可以分成ViewGroup和View的分发两种情况了;

  • 对于ViewGroup而言,当事件分发到当前ViewGroup上面的时候,首先会调用他的dispatchTouchEvent方法,在 dispatchTouchEvent方法里面会调用onInterceptTouchEvent来判断是否要拦截当前事件,如果要拦截的话,就会调用 ViewGroup自己的onTouchEvent方法了,如果onInterceptTouchEvent返回false的话表示不拦截当前事件,那么 事件将会继续往当前ViewGroup的子View上面传递了,如果他的子View是ViewGroup的话,则重复ViewGroup事件分发过程,如 果子View就是View的话,则转到下面的View分发过程;
  • 对于View而言,事件传递过来首先当然也是执行他的dispatchTouchEvent方法了,如果我们为当前View设置了 onTouchListener监听器的话,首先就会执行他的回调方法onTouch了,这个方法的返回值将决定事件是否要继续传递下去了,如果返回 false的话,表示事件没有被消费,还会继续传递下去,如果返回true的话,表示事件已经被消费了,不再需要向下传递了;如果返回false,那么将 会执行当前View的onTouchEvent方法,如果我们为当前View设置了onLongClickListener监听器的话,则首先会执行他的 回调方法onLongClick,和onTouch方法类似,如果该方法返回true表示事件被消费,不会继续向下传递,返回false的话,事件会继续 向下传递,为了分析,我们假定返回false,如果我们设置了onClickListener监听器的话,则会执行他的回调方法onClick,该方法是 没有返回值的,所以也是我们事件分发机制中最后执行的方法了;可以注意到的一点就是只要你的当前View是clickable或者 longclickable的,View的onTouchEvent方法默认都会返回true,也就是说对于事件传递到View上来说,系统默认是由 View来消费事件的,但是ViewGroup就不是这样了;
  • 上面的事件分发过程只是正常情况下的,如果有这样一种情况,比如事件传递到最里层的View之后,调用该View的oonTouchEvent方法返回了 false,那么这时候事件将通过冒泡式的方式向他的父View传递,调用它父View的onTouchEvent方法,如果正好他的父View的 onTouchEvent方法也返回false的话,这个时候事件最终将会传递到Activity的onTouchEvent方法了,也就是最终就只能由 Activity自己来处理了;

事件分发机制需要注意的几点:
(1):如果说除Activity之外的View都没有消费掉DOWN事件的话,那么事件将不再会传递到Activity里面的子View了,将直接由Activity自己调用自己的onTouchEvent方法来处理了;
(2):一旦一个ViewGroup决定拦截事件,那么这个事件序列剩余的部分将不再会由该ViewGroup的子View去处理了,即事件将在此 ViewGroup层停止向下传递,同时随后的事件序列将不再会调用onInterceptTouchEvent方法了;
(3):如果一个View开始处理事件但是没有消费掉DOWN事件,那么这个事件序列随后的事件将不再由该View来处理,通俗点讲就是你自己没能力就别瞎BB,要不以后的事件就都不给你了;
(4):View的onTouchEvent方法是否执行是和他的onTouchListener回调方法onTouch的返回值息息相关 的,onTouch返回false,onTouchEvent方法不执行;onTouch返回false,onTouchEvent方法执行,因为 onTouchEvent里面会执行onClick,所以造成了onClick是否执行和onTouch的返回值有了关系;

View视图绘制过程原理?

View视图绘制需要搞清楚两个问题,一个是从哪里开始绘制,一个是怎么绘制?

  • 先说从哪里开始绘制的问题:我们平常在使用Activity的时候,都会调用setContentView来设置布局文件,没错,视图绘制就是从这个方法开始的;
  • 再来说说怎么绘制的:
    在我们的Activity中调用了setContentView之后,会转而执行PhoneWindow的setContentView,在这个方法里面 会判断我们存放内容的ViewGroup(这个ViewGroup可以是DecorView也可以是DecorView的子View)是否存在。不存在的 话则会创建一个DecorView出来,并且会创建出相应的窗体风格,存在的话则会删除原先ViewGroup上面已有的View,接着会调用 LayoutInflater的inflate方法以pull解析的方式将当前布局文件中存在的View通过addView的方式添加到 ViewGroup上面来,接着在addView方法里面就会执行我们常见的invalidate方法了,这个方法不只是在View视图绘制的过程中经常 用到,其实动画的实现原理也是不断的调用这个方法来实现视图不断重绘的,执行这个方法的时候会调用他的父View的invalidateChild方法, 这个方法是属于ViewParent的,ViewGroup以及ViewRootImpl中都对他进行了实现,invalidateChild里 面主要做的事就是通过do while循环一层一层计算出当前View的四个点所对应的矩阵在ViewRoot中所对应的位置,那么有了这个矩阵的位置之后最终都会执行到 ViewRootImpl的invalidateChildInParent方法,执行这个方法的时候首先会检查当前线程是不是主线程,因为我们要开始准 备更新UI了,不是主线程的话是不允许更新UI的,接着就会执行scheduleTraversals方法了,这个方法会通过handler来执行 doTraversal方法,在这个方法里面就见到了我们平常所熟悉的View视图绘制的起点方法performTraversals了;
  • 那么接下来就是真正的视图绘制流程了,大体上讲View的绘制经历了Measure测量、Layout布局以及Draw绘制三个过程,具体来讲是从 ViewRootImpl的performTraversals方法开始,首先执行的将是performMeasure方法,这个方法里面会传入两个 MeasureSpec类型的参数,他在很大程度上决定了View的尺寸规格,对于DecorView来说宽高的MeasureSpec值的获取与窗口尺 寸以及自身的LayoutParams有关,对于普通View来说其宽高的MeasureSpec值的获取由父容器以及自身的LayoutParams属 性共同决定,在performMeasure里面会执行measure方法,在measure方法里面会执行onMeasure方法,到这里 Measure测量过程对View与ViewGroup来说是没有区别的,但是从onMeasure开始两者有差别了,因为View本身已经不存在子 View了,所以他onMeasure方法将执行setMeasuredDimension方法,该方法会设置View的测量值,但是对于 ViewGroup来说,因为它里面还存在着子View,那么我们就需要继续测量它里面的子View了,调用的方法是measureChild方法,该方 法内部又会执行measure方法,而measure方法转而又会执行onMeasure方法,这样不断的递归进行下去,知道整个View树测量结束,这 样performMeasure方法执行结束了;接着便是执行performLayout方法了,performMeasure只是测量出View树中 View的大小了,但是还不知道View的位置,所以也就出现了performLayout方法了,performLayout方法首先会执行 layout方法,以确定View自身的位置,如果当前View是ViewGroup的话,则会执行onLayout方法。在onLayout方法里面又 会递归的执行layout方法,直到当前遍历到的View不再是ViewGroup为止,这样整个layout布局过程就结束了;在View树中View 的大小以及位置都确定之后,接下来就是真正的绘制View显示在界面的过程了,该过程首先从performDraw方法开始,performDraw方法 首先执行draw方法,在draw方法中首先绘制背景、接着调用onDraw方法绘制自己,如果当前View是ViewGroup的话,还要调用 dispatchDraw方法绘制当前ViewGroup的子View,而dispatchDraw方法里面实际上是通过drawChild方法间接调用 draw方法形成递归绘制整个View树,直到当前View不再是ViewGroup为止,这样整个View的绘制过程就结束了;

解决滑动冲突的方式?

在自定义View的过程经常会遇到滑动冲突问题,一般滑动冲突的类型有三种:
(1)外部View滑动方向和内部View滑动方向不一致;
(2)外部View滑动方向和内部View滑动方向一致;
(3)上述两种情况的嵌套;
一般我们解决滑动冲突都是利用的事件分发机制,有两种方式外部拦截法和内部拦截法:

  • 外部拦截法:实 现思路是事件首先是通过父容器的拦截处理,如果父容器不需要该事件的话,则不拦截,将事件传递到子View上面,如果父容器决定拦截的话,则在父容器的 onTouchEvent里面直接处理该事件,这种方法符合事件分发机制;具体实现措施是修改父容器的onInterceptTouchEvent方法, 在达到某一条件的时候,让该方法直接返回true就可以把事件拦截下来进而调用自己的onTouchEvent方法来处理了,但是有一点需要注意的是如果 想要让子View能够收到事件,我们需要在onInterceptTouchEvent方法里面判断如果是DOWN事件的话,返回false,这样后续的MOVE以及UP事件才有机会传递到子View上面,如果你直接在onInterceptTouchEvent方法里面DOWN情况下返回了true,那么后续的MOVE以及UP事件将由当前View的onTouchEvent处理了,这样你的拦截将根本没有意义的,拦截只是在满足一定条件才会拦截,并不是所有情况下都拦截;
  • 内部拦截法:实 现思路是事件从父容器传递到子View上面,父容器不做任何干预性的措施,所有的事件都会传递到子View上面,如果子元素需要改事件,那么就由子元素消 耗掉了,该事件也就不会回传了,如果子元素不需要该事件,那么他就会回传给父容器来处理了;具体实现措施需要借助于 requestDisallowInterceptTouchEvent方法,该方法用来告诉父容器要不要拦截当前事件,为了配合子View能够调用这个 方法成功,父容器必须默认能够拦截除了DOWN事件以外的事件,为什么要除了DOWN事件以外呢?因为如果一旦父容器拦截了DOWN事件,那么后续事件将 不再会传递到子元素了,内部拦截法也就失去作用了;
  • 个人认为外部拦截法是符合正常逻辑的,按照事件隧道式分发过程,如果父容器需要就直接拦截,不需要则传递到子View;内部拦截法相当于人为干预分发这个 过程,我会保证事件先都到子View上面,至于子View需不需要就要看我自己了,如果我不需要就回传给父容器了,需要的话自己就消耗掉了;感觉这两种方 式只是父容器和子View处理事件的优先级不同而已;

Android动画原理?

Android动画可以分为View动画、帧动画、属性动画,其中View动画又可以分为平移(Translate)、缩放(Scale)、旋转 (Rotate)、透明度(Alpha)四种,帧动画可以认为是View动画的一种,实现原理类似于放电影,通过一帧一帧的图片进行播放来达到动画的效 果,正是因为这点需要注意他可能会出现OOM异常,属性动画是3.0之后出现的,他也可以实现View动画的效果;


在讲解动画原理之前需要明白两个概念,插值器和估值器:
插值器:根据时间流逝的百分比来计算出属性值改变的百分比,对应的接口是Interpolator;
估值器:根据属性改变的百分比计算出属性的改变值,对应的接口是TypeEvaluator;


  • 先来说说View动画实现原理,其实如果你看View动画实现过程的源码的话就会发现,View动画其实就是在不断的调用View的invalidate 方法来进行View的绘制以达到动画的效果的,所以理解View动画的核心其实应该是首先理解View的绘制过程;
    我们使用View动画都是通过View的startAnimation方法开始的,那么分析View动画原理自然应该从这个方法开始了,这个方法里面会调 用setAnimation设置当前View的动画,并且随后调用了我们经常见的invalidate方法,这个方法具体执行过程上面已经说过了,最后都 会执行到ViewRootImpl的performTraversals方法,该方法就是我们进行视图绘制经常见到的开始方法了,经过一系列的 measure测量以及layout布局过程执行到draw绘画阶段,这个阶段是我们动画比较关心的阶段,毕竟要在界面显示嘛,没有draw怎么做到,调 用draw方法之后绘制流程是这样的:首选绘制背景,接着绘制自己,随后调用dispatchDraw绘制自己的孩子,在调用每个子View的draw方 法之前,需要绘制的View的绘制位置是Canvas通过translate方法切换了,这点也看出来View动画实际上一直在动的是画布,而并不是 View本身,最后还要绘制滚动条等修饰内容,这里调用了dispatchDraw方法,但是View没有实现这个方法,ViewGroup作为View 的子类实现了这个方法,在ViewGroup的dispatchDraw方法中会执行drawChild方法来绘制当前ViewGroup的子 View,drawChild方法实际上调用的就是View的draw方法了,这个draw方法是不同于前面ViewGroup绘制自己的draw方法, 这个draw方法中有一个时间参数和画布参数Canvas,具体的绘制就是通过这个画布参数实现的,但是ChildView的画布是由其 ParentView提供的,ParentView会根据ChildView在其内部的布局来调整Canvas,当子View调用,在该draw方法中会 通过getAnimation获取到我们设置到View上的动画,接着便执行了drawAnimation方法来进行动画绘制了,在drawAnimation方 法里面首先通过执行getChildTransformation方法获得子View的Transformation值,那么 Transformation是什么呢?它主要进行的是矩阵运算的,其中有两个比较关键的属性其中之一是Matrix用于存储View的平移、缩放、旋转 信息,还有一个alpha属性,主要存储的是View的透明度信息的,接着就会执行getTransformation方法,把刚刚获取的Transformation值以及当前时间作为参数传入,在getTransformation方 法里面会通过当前时间计算出时间流逝的百分比,并且将该百分比作为参数调用插值器的getInterpolation方法,获得时间流逝百分比对应的属性 改变的百分比,当然这里你可以使用自己定义的插值器,有了属性改变百分比之后我们就可以调用applyTransformation方法来进行具体的动画 实现了,当然如果你自己想要实现自己定义的动画,可以重写applyTransformation方法,这样View动画的第一帧就绘制好了,那么后续的帧该怎么绘制呢?如果你细心的话会发现getTransformation有一个boolean类型的返回值,没错就是靠这个返回值来进行后续帧绘制的,查看getTransformation方法文档说明会发现返回真表示还有后续帧存在,具体判别方法当然就是通过比较当前时间是否超过动画要求最迟时间了,返回true则会继续执行invalidate方法,相当于又回到最开始处进行递归的绘制,返回false的话则动画结束,这就是View动画的执行过程了;
  • 帧动画因为可以理解为电影的放映过程,所以他的一帧一帧过程是我们自己提供的,因为系统本身只需要切换我们提供的资源图片就可以了,没有多大原理需要解释;
  • 属性动画实现原理:
    既然名字上有属性两个字,那么肯定是通过改变View的属性来达到动画效果的,这点和View动画是有很大差别的,View动画只是ParentView 不断的调整ChildView的画布
    来实现动画的,本质上View的属性是没有发生变化的,所以当你对移动到某个地方的View进行一些比如点击或者触摸 操作的时候是根本不会执行当前移动过来的View的事件方法的,原因就在于你移动过去的只是原先View的影像而已,而属性动画就不一样了,他是实实在在 的改变View属性真正的在移动的;属性动画要求动画作用的对象必须提供想要改变属性的set方法,如果你没有传递初始值的话还需要提供该属性的get方 法,属性动画会根据你传入的该属性的初始值和最终值以动画的效果(也就是计算出某一时刻属性需要改变的值)多次通过反射调用set方法动态的改变作用对象 的属性值,随着时间的推移,这个值将越来越接近设置的最终值,以达到动画的效果;

Android动画注意点:
在使用帧动画的时候,因为动画图片资源是我们自己提供的,所以一定要注意可能会出现的OOM异常;
View动画只是ParentView不断调整ChildView的画布实现的,只是对View的影响做动画,而不是真正的改变View的状态;
View动画在移动之后,移动的位置处的View不能触发事件,但是属性动画是可以的;

3、Android网络请求机制&流行网络框架&封装

网络请求机制

HttpClient和HttpURLConnection的区别

Android主要提供了两种方式进行网络请求:HttpClient与HttpUrlConnection。这两种方式都支持https协议,以流的形式进行上传和下载、配置超时事件、IPV6、以及连接池等功能。
HttpClient拥有众多API,实现较稳定,bug数量少。但难以扩展,维护成本高。Android6.0后被移除。
HttpUrlConnection提供简单、轻量级API,易于扩展。但Android2.2前有个重大bug。2.3后修改了bug并且提供了压缩和缓存机制,有效提升了网络性能。

Volley,OkHttp,Retrofit(三大常用Android网络框架)之间的区别和核心原理和使用场景

1、Volley是Google推出的网络通信框架,适合数据量小但通信频繁的网络操作。但不适合大文件下载。
特点是:(1)可进行Post、Get网络请求与图像异步处理请求(2)对网络请求进行排序与优先级处理(3)对网络请求进行缓存(4)多级别取消请求(5)与Activity生命周期联动
它的工作原理是先将请求加入缓存队列,并通过cacheDispatcher查询本地是否缓存本次请求结果,如果命中,则从缓存中解析结果并返回主线程;如果没有命中则将请求添加到网络队列,并通过networkDispatcher发送网络请求,获得并解析响应结果,将结果写入缓存并返回主线程。
2、OkHttp是Square团队开发的支持Http2/SPDY的网络通信框架,即支持共享同一个Socket处理同一个服务器的所有请求,若SPDY不可用,则通过连接池来减少请求延时。支持重连机制、缓存响应数据及GZIP减少数据流量。
OkHttp核心设计模式是拦截器责任链模式,采用责任链的模式来使每个功能分开,每个拦截器自行完成自己的任务,并且将不属于自己的任务交给下一个,简化了各自的责任和逻辑,实现了网络请求。这样设计的好处在于使每个责任链可以分层实现缓存、压缩、网络IO和请求等功能,并且可以对响应的数据做其他的逻辑处理。
OkHttp适用于数据量大的重量级网络请求。
3、Retrofit是基于RESTful风格推出的网络请求框架封装,是基于OKHttp的网络请求框架的二次封装。其底层是通过OKHttp进行网络请求,而Retrofit仅负责网络请求接口的封装,从而简化了用户网络请求的参数配置,还能与Rxjava结合。
Retrofit采用了大量设计模式封装OkHttp,它的核心工作原理是将Http请求抽象为Java接口,在接口中用注解描述和配置网络请求参数。Retrofit使用动态代理的方式,动态地将网络请求接口的注解解析成HTTP请求。最终通过OKHttp执行Http请求。
Retrofit在任何场景下都优先选择,尤其是后台Api遵循RESTful风格且项目中使用RxJava的场景。

网络请求缓存处理,okhttp如何处理网络缓存的?

Http网络请求缓存处理:
强制缓存:当用户端第一次请求数据是,服务端返回了缓存的过期时间(Expires与Cache-Control),没有过期即可以继续使用缓存;如果过期则不使用缓存,无需再请求服务端。
对比缓存:当用户端第一次请求数据时,服务端会将缓存标识(Etag/If-None-Match与Last-Modified/If-Modified-Since)与数据一起返回给用户端,用户端将两者都备份到缓存中 ,再次请求数据时,用户端将上次备份的缓存
标识发送给服务端,服务端根据缓存标识进行判断缓存是否过期,假如返回304,则表示缓存可用,从缓存中读取数据;假如返回200,标识缓存不可用,请求服务端,并使用最新返回的数据。

4、Android不同版本特性&多屏幕适配&自定义组件开发

屏幕适配方案?

(1):为了确保你的布局能够自适应各种不同屏幕大小,应该在布局中使用"wrap_content"和"match_parent"来确定他的宽 高,"wrap_content"相应的视图宽高会被设置成刚好能包括视图中内容的最小值,而使用"match_parent"则会让视图的宽高延伸至整 个父布局;
(2):使用RelativeLayout可以准确的控制子视图之间的位置关系,而LinearLayout只能简单的一个挨着一个排列,RelativeLayout允许布局的子控件之间使用相对定位的方式控制控件的位置;
(3):使用Size限定符,能够根据屏幕的配置加载不同的布局文件,比如横屏和竖屏状态下,屏幕大小不同加载不同的布局文件,具体方法可以通过配置限定 符来实现,配置限定符允许程序在运行时根据当前设备的配置自动加载合适的资源,比如在小屏幕状态下加载res/layout/…下的布局文件,在大 屏幕下加载res/layout-large/…下的布局文件,大屏幕下加载的是包含有large限定符的目录下的布局文件;
(4):使用Size限定符有个比较头疼的问题就是里面的large到底多大才算大呢?这个大小我们是没法决定的,因而这时候我们可以通过 Smallest-width限定符,Smallest-width限定符允许设定一个具体的最小值,这样就可以做到我们自己指定到底什么情况下进行布局 切换了,具体实现是通过res/layout/…和res/layout-sw600dp/…来实现的,当屏幕大小超过600dp的时候 就会调用res/layout-sw600dp/…下面的布局文件,而小于600dp的时候则会调用res/layout/…下的布局 文件了;
(5):另外,我们可以使用Orientation限定符来让在同一屏幕大小的情况下,在横屏和竖屏的状态下可以加载不同的布局文件,具体实现就是使用 Orientation限定符了,比如横屏状态可以使用res/layout-port/…在竖屏的状态下使用res/layout-land /…,当然为了简化,我们可以使用布局别名的方式来实现;
(6):支持不同屏幕的大小往往意味着你的图片也要有自适应能力,我们可以通过使用Android提供的draw9patch工具来指定哪些区域在屏幕适 配的时候可以被拉伸,哪些区域在适配的时候不可以被拉伸,使用draw9patch工具会生成.9.png类型的图片,他是特殊的png图片;

版本特性:

  • Android5.0新特性:

MaterialDesign设计风格

支持多种设备(智能手机、平板电脑、笔记本电脑、智能电视、汽车、智能手表等)

支持64位ART虚拟机

  • Android6.0新特性:

动态权限管理

指纹识别

支持快速充电的切换

支持文件夹拖拽应用

相机新增专业模式

  • Android7.0新特性:

分屏多任务

支持Java8开发

夜间模式

  • Android8.0新特性:

画中画

通知标志

自动填写

系统优化

后台限制

  • Android9.0新特性:

支持刘海屏

室内wifi定位

新的图片解码等

  • Android10.x
    折叠屏支持
    夜间模式
    5G支持
    手势导航

  • Android11.x
    权限和隐私
    网络优化。
    折叠屏支持优化,增加铰链角度传感器API等。
    高刷新率支持。

  • Android12.x
    1、增加默认启动页

自定义View的步骤?

(1):继承View或者View的子类;
(2):在res/values/attrs.xml中新增节点自定义属性;
(3):将自定义View放到布局文件中,注意命名空间的格式;
xmlns:custom=“http://schemas.android.com/apk/res/[自定义View所在的包路径]”
(4):在xml文件中设定指定属性值;
(5):获取自定义属性,并且为View设置必要的事件;
(6):进行自定义的绘制,实现onDraw方法,当然你也可以实现onMeasure进行自定义测量;
(7):覆写onTouch等事件相关方法;
(8):优化你自定义的View,比如为了减低刷新频率你可以在onDraw中调用四个参数的invalidate方法,因为无参的invalidate方法刷新的是整个View树,而四个参数的invalidate方法刷新的是指定部分的View;

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值