一, 背景
入行android已近三年,做过的项目不多,但是也算是国内知名app。针对于此,是时候总结一下android的由浅入深的知识点,同时也加深自己对以往知识的回顾。
二,技术点介绍
Android的四大组件和生命周期
每一个android的开发成员一开始必须要知道的四大组件分别是:
Activity:一个暴漏给开发最基本的单元,通常是一个可见的界面,也有可能是浮在界面上的一个dialog。跳转和通信都是基于intent进行的。其中个人觉得对此要掌握的是四种启动模式和生命周期:
下面是这四种模式的作用:
(1)standard
默认模式,可以不用写配置。在这个模式下,都会默认创建一个新的实例。因此,在这种模式下,可以有多个相同的实例,也允许多个相同Activity叠加。
(2)singleTop
可以有多个实例,但是不允许多个相同Activity叠加。即,如果Activity在栈顶的时候,启动相同的Activity,不会创建新的实例,而会调用其onNewIntent方法。
(3)singleTask
只有一个实例。在同一个应用程序中启动他的时候,若Activity不存在,则会在当前task创建一个新的实例,若存在,则会把task中在其之上的其它Activity destory掉并调用它的onNewIntent方法。
如果是在别的应用程序中启动它,则会新建一个task,并在该task中启动这个Activity,singleTask允许别的Activity与其在一个task中共存,也就是说,如果我在这个singleTask的实例中再打开新的Activity,这个新的Activity还是会在singleTask的实例的task中。
生命周期:
Service:相对于Activity,如果说Activity是在人前,那么service就是在人后。顾名思义他是一种服务。一般来说他是用来操作一些耗时的任务,或者是执行长期运行的任务。
启动service的方法有两种,startService和bindservice。
(1)startService
执行startService时,Service会经历onCreate->onStartCommand。当执行stopService时,直接调用onDestroy方法。调用者如果没有stopService,Service会一直在后台运行,下次调用者再起来仍然可以stopService。
(2)bindService
执行bindService时,Service会经历onCreate->onBind。这个时候调用者和Service绑定在一起。调用者调用unbindService方法或者调用者Context不存在了(如Activity被finish了),Service就会调用onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。
(3)Service和Activity是如何通信的
通常的用法就是在onStartCommand方法里执行任务,这也是Activity和service最直接的通信了。比如以下的代码。
通过这种运用,感觉他俩和陌生啊,就是通知以下服务你该执行了。难道服务就不该主动撩一下Activity吗?当然可以,onBind不是吃素的。这个方法里返回一个IBinder接口,这个里面我们可以做很多事情,但是如果用到这个,启动对应的service方法是onBindservice。
@Override
publicIBinder onBind(Intent intent) {
mServiceInUse=true;
returnmBinder;
}
ContentProvider:为存储和获取数据提供统一的接口,可以在不同应用之间共享数据。比如访问电话通讯录。每一个ContentProvider都有一个uri给其提供数据,可以进行增删改除的操作。
BroadcastReiver:广播的注册方式分两种静态(在manifest里面静态注册)和动态(在代码里注册)
需要注意的是在onReceive里面执行的操作不能超过五秒,否则会ANR,换句话说这里面不要进行复杂的操作。
Android的Framework和通信机制
首先放一张大家的熟知的Framework结构图:
依次为:Applicaiton--framework--library--linux kernal. 个人喜欢依次称之为app, api,jni以及binder驱动。
消息机制
消息的存在就是就是为了子线程和主线程的通信。android界面的刷新只能放在主线程,而耗时的操作我们只能放在子线程。消息就是两者之间的传话者,至于消息内部是怎样实现的。大致的流程如下
说明:Handler必须要在主线程生命,实际上handler是关联了messagueue,looper在messagequeque里面取消息关联一个threadlocal线程,说白了就是hander在messagequeque取消息交给looper出来的子线程threadlocal去处理。
注意:hanlder使用不当会产生内存泄漏。一般发生在非静态内部类。
关于他们只见的关系,可以如下图表示:
参考文献:http://www.cnblogs.com/angeldevil/p/3340644.html
JNI浅谈
通过上面Android的框架图可以看出,application和framework层都是java编写的,而library和linux内核都是C或者C++编写的。上层的java如何和下层的c联系起来,jni就是两者关联的桥梁。
关于如何写一个jni的例子,网上有很多。下面我就总结一下:
java 调用jni,一般是先加载一个jni的so库。例如:
System.loadLibrary("test.so");
然后就是定义和jni里面匹配的方法,记得要加关键字:Native。例如:
private Native void testJava();
jni也可以调用java(通常说的反射):
jclass clazz = (*env)->FindClass(env, "包名/MainActivity");
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID methodID = (*env)->GetMethodID(env, clazz, "方法名", "(Ljava/lang/String;)V");
//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
SDK的开发与应用
sdk全程Software Development Kit。软件开发的工具包。简单的来讲他就像是一个api接口的大整合,也包括一些文档等。
在这里不罗嗦了,有i经验的人都知道自己的sdk开发需要注意什么。
View的渲染机制
看完这张图,至少知道了android对view的渲染都是谁在主事。大家都知道我们人眼流畅的画面是每秒60帧。换句话说小于等于60帧的界面对我们而言都是流畅的,否则就会出现卡顿。android没隔16ms就会触发一次ui的渲染。
说到这,暂且不去细说android view的绘制流程,onmesure onlayout和ondraw的联系,个人觉得分析一下有关ui方面的优化来的更重要。
了解到以上的情况,我们首先要保证fps是小于60的。这就要从几方面入手:
第一,减少不必要的层级,避免增加GPU的工作量导致过度绘制。我们可以打开手机的GPU绘制看哪个布局出现过渡绘制的情况。也可以用Hierarchy Viewer工具。
第二,图片的正确使用,使用png代替jpg图片
第三,清理不必要的背景,当实在需要时可以设置透明。
第四,如果是自定义的view,记得优化自己自定义的代码。
动画机制
这个我已经专门写过一篇文档:http://www.jianshu.com/writer#/notebooks/10152985/notes/9503294
常用的架构开发架构模式
1. MVC
可以看出是一个单向的,view有数据请求要先通知c,C告诉model。model再给view反馈。
不足:
(1)增加了系统结构和实现的复杂性。对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。
(2)视图与控制器间的过于紧密的连接。视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。
(3)视图对模型数据的低效率访问。依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。
(4) 目前,一般高级的界面工具或构造器不支持MVC架构。改造这些工具以适应MVC需要和建立分离的部件的代价是很高的,从而造成使用MVC的困难。
2. MVP
通过上图可以看出:
1. 各部分之间的通信,都是双向的。
2. View 与 Model 不发生联系,都通过 Presenter 传递。
3. View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。
3. MVVM
MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。
和MVP唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。Angular 和 Ember 都采用这种模式
多线程和线程池
(1)Android多线程处理
AsyncTask:为 UI 线程与工作线程之间进行快速的切换提供一种简单便捷的机制。适用于当下立即需要启动,但是异步执行的生命周期短暂的使用场景。
HandlerThread:为某些回调方法或者等待某些任务的执行设置一个专属的线程,并提供线程任务的调度机制。
ThreadPool:把任务分解成不同的单元,分发到各个不同的线程上,进行同时并发处理。
IntentService:适合于执行由 UI 触发的后台 Service 任务,并可以把后台任务执行的情况通过一定的机制反馈给 UI。
虽然android提供了以上四种应付并发处理的方案,但是在什么时候选择什么样的方式很重要,稍有不慎就有可能oom。这里我不能一一详细讲解,但是我们需要知道一点,不管那种处理方式其最终的处理原理其实是交给了Handle,meesagequeue,looper这三个家伙。通过打开我们常用的AsyncTask源码便可知,毕竟我们只是用,一些繁琐的任务处理,执行还是以上三个家伙协助完成的。
有关详细介绍,参考大神的文:http://www.cnblogs.com/bugly/p/5519510.html
http://blog.csdn.net/github_33304260/article/details/70213300
(2)线程池
显而易见,线程池是为线程服务的。首先看一个常用的例子:
new Thread(new Runnable() {
@Override
public void run() {
//do sth .
}
}).start();
我们经常会这样使用,还觉得妙不可言,但是过多频繁的使用就会引发以下问题:
1、线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间的消耗则比较明显,将导致性能上的缺失
2、大量的线程创建、执行和销毁是非常耗cpu和内存的,这样将直接影响系统的吞吐量,导致性能急剧下降,如果内存资源占用的比较多,还很可能造成OOM
3、大量的线程的创建和销毁很容易导致GC频繁的执行,从而发生内存抖动现象,而发生了内存抖动,对于移动端来说,最大的影响就是造成界面卡顿
为了解决以上问题而又可以这样使用,那么线程池应运而生了。他的出现像是诞生了一位leader,要统一管理这些随心所欲的家伙了。他带来的好处如下:
1、线程的创建和销毁由线程池维护,一个线程在完成任务后并不会立即销毁,而是由后续的任务复用这个线程,从而减少线程的创建和销毁,节约系统的开销
2、线程池旨在线程的复用,这就可以节约我们用以往的方式创建线程和销毁所消耗的时间,减少线程频繁调度的开销,从而节约系统资源,提高系统吞吐量
3、在执行大量异步任务时提高了性能
4、Java内置的一套ExecutorService线程池相关的api,可以更方便的控制线程的最大并发数、线程的定时任务、单线程的顺序执行等
同时他管理有方,把线程归类,总有一款适合你的‘池子’:
1、newFixedThreadPool() :
作用:该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变,即不会再创建新的线程,也不会销毁已经创建好的线程,自始自终都是那几个固定的线程在工作,所以该线程池可以控制线程的最大并发数。
假如有一个新任务提交时,线程池中如果有空闲的线程则立即使用空闲线程来处理任务,如果没有,则会把这个新任务存在一个任务队列中,一旦有线程空闲了,则按FIFO方式处理任务队列中的任务。
2、newCachedThreadPool() :
作用:该方法返回一个可以根据实际情况调整线程池中线程的数量的线程池。即该线程池中的线程数量不确定,是根据实际情况动态调整的。
假如该线程池中的所有线程都正在工作,而此时有新任务提交,那么将会创建新的线程去处理该任务,而此时假如之前有一些线程完成了任务,现在又有新任务提交,那么将不会创建新线程去处理,而是复用空闲的线程去处理新任务。那么此时有人有疑问了,那这样来说该线程池的线程岂不是会越集越多?其实并不会,因为线程池中的线程都有一个“保持活动时间”的参数,通过配置它,如果线程池中的空闲线程的空闲时间超过该“保存活动时间”则立刻停止该线程,而该线程池默认的“保持活动时间”为60s。
3、newSingleThreadExecutor() :
作用:该方法返回一个只有一个线程的线程池,即每次只能执行一个线程任务,多余的任务会保存到一个任务队列中,等待这一个线程空闲,当这个线程空闲了再按FIFO方式顺序执行任务队列中的任务。
4、newScheduledThreadPool() :
作用:该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。
5、newSingleThreadScheduledExecutor() :
作用:该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。只不过和上面的区别是该线程池大小为1,而上面的可以指定线程池的大小。
参考文献:http://blog.csdn.net/u010687392/article/details/49850803
常用的图片加载模式
(1)Glide && Picasso
之所以把这两个拿出来一起说事,是因为两者的使用方式非常相像。例如加载一张图片:
Glide.with(getContext())
.load(url)
.placeholder(Drawables.sPlaceholderDrawable) //初始化显示
.error(Drawables.sErrorDrawable) //加载失误显示
.into(mImageView);
Picasso.with(context)
.load(url)
.placeholder(R.drawable.user_placeholder)
.error(R.drawable.user_placeholder_error)
.into(imageView);
相同点:通过以上例子看起来用法简直一毛一样。用法简单,体积小。
不同点:picasso不支持缩略图,不支持gif,加载速度一般。但是体积小。Glide支持这两种,且加载速度高于picasso。
参考文献:http://blog.csdn.net/qq_25690935/article/details/50548457
(2)Fresco
Pipeline
它负责从网络,从本地文件系统,本地资源加载图片。为了最大限度节省空间和CPU时间,它含有3级缓存设计(2级内存,1级磁盘)
内存管理
在5.0以下系统,Fresco将图片放到一个特别的内存区域,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。
动图加载(加载Gif和Webp)
图片加载
Fresco的Image Pipeline允许你用很多种方式来自定义图片加载过程,比如:
为同一个图片指定不同的远程路径,或者使用已经存在本地缓存中的图片
先显示一个低清晰度的图片,等高清图下载完之后再显示高清图加载完成回调通知
对于本地图,如有EXIF缩略图,在大图加载完成之前,可先显示缩略图缩放或者旋转图片对已下载的图片再次处理,支持WebP解码
图片渐进式呈现
Android 本身的图片库不支持此格式,但是Fresco支持。使用时仅需要提供一个图片的URI即可,剩下的事情,Fresco会处理。
Drawable
Fresco 中设计有一个叫做 Drawees 模块,它会在图片加载完成前显示占位图,加载成功后自动替换为目标图片。当图片不再显示在屏幕上时,它会及时地释放内存和空间占用
参考文献:https://www.fresco-cn.org/
插件化的运用
插件化曾经在app开发风靡一时,包括现在也很多项目都在使用,很多人都认为插件化的诞生是为了解决65535的问题,而实际上他的好处并不仅仅只有这一处。
宿主和插件分开编译
并发开发
动态更新插件
按需下载模块
方法数或变量数爆棚
开源的插件化框架
Qihoo360/DroidPlugin
CtripMobile/DynamicAPK
mmin18/AndroidDynamicLoader
singwhatiwanna/dynamic-load-apk
houkx/android-pluginmgr
bunnyblue/ACDD
wequick/Small
参考文献:http://www.jianshu.com/p/353514d315a7
热补丁
和上面说讲到的插件化的部分原理很像。先看一下android的类加载:
我们常用的DExCalssLoader和PathClassLoader最终还是会走到classloader里面去,通过对calssloader的解读,我们找到关键的两个东西:dexElements数据和findclass方法:
public Class findClass(String name, Listsuppressed) {
//遍历该数组
for (Element element : dexElements) {
//初始化DexFile
DexFile dex = element.dexFile;
if (dex != null) {
//调用DexFile类的loadClassBinaryName方法返回Class实例
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
因此,讲白一点,我们所谓的热修复就是偷梁换柱,把我们的dex文件放在最前面。
性能优化
这个话题基本上是每个技术人员长挂在嘴边的问题,但是真正做起来并非那么的容易,有余android系统本身具备某些因素导致我们使用不当就会造成性能问题。比如常见的问题oom。
Android内存本身面临的问题:
1.有限的堆内存,原始只有16M
2.内存大小消耗等根据设备,操作系统等级,屏幕尺寸的不同而不同
3.程序不能直接控制
4.支持后台多任务处理(multitasking)
5.运行在虚拟机之上
针对以上问题,我们都知道5r原则,也尽可能的去做到,何谓5r?
答曰:
1.Reckon(计算)
首先需要知道你的app所消耗内存的情况,知己知彼才能百战不殆
2.Reduce(减少)
消耗更少的资源
3.Reuse(重用)
当第一次使用完以后,尽量给其他的使用
4.Review(检查)
5.Recycle(回收)
返回资源给生产流
有关5r的东西可以说是泛泛之谈,每个人真正理解使用的都不一样,就个人经验而言,我们想要提高性能,从两方面入手,看的见的和看不见的。
看的见的就是呈现眼前的ui,看不见的就是关系到整个app生死存亡的内存。
对于UI,上面讲道的ui渲染,我们大概清楚了,避免过度绘制。另外就是我们常见的oom很多都是发生在bitmap上,图片处理一直是个大头,我们最好采用fresco,毕竟他本身对性能做了处理,同时我们也要做到手动回收,捕获异常,尽可能的用若引用代替强引用。合理利用缓存机制。
另外就是看不见的内存分配。在这里我只说两块,栈(stack)和堆(heap),熟悉的人都知道,前者是程序控制不了,后者就是我们能控制的,经常会通过new或者malloc去分配空间。所以,由我们控制的东西就有可能出现问题,为此我们要格外小心。之所以会出现oom,是因为android分给heap的大小是固定一般是16m,如果java申请内存超过所分配的阈值就会出现oom。
网络加载模式
android的通信都是基于http的协议。内部http协议的通信主要有两种方式,HttpUrlConnection和HttpClient。为了更好的处理网络的异步加载提高性能,现在比较常用的网络加载框架如下:
(1)Okhttp:和Vollery一样OkHttp也是一款HTTP框架,它支持get请求和post请求,支持基于Http的文件上传和下载,支持加载图片,支持下载文件透明的GZIP压缩,支持响应缓存避免重复的网络请求,支持使用连接池来降低响应延迟问题。
由于目前我自己的项目使用的也是此框架,所以对此多说一点。首先我们需要大概知道他的原理:
上图是一个很简单的流程,任何网络请求无外乎就是基于http协议完成对数据的访问。更深一层次来说是基于TCP/IP协议,至于哪个更牛逼就看访问速度,高效性,容错性了。
okhttp在请求的时候会有一个engine,这个engine就像是一个管理站,他控制着需要那种方式的请求,比如是异步还是同步。在connetion的时候,就是真正建立连接的时候,有自己的线程池机制,更选择更优化的处理方式。而真正达到服务端的时候,会先经过路由,路由其实就是一个注册表(个人理解)是一套协议,比如代理,IP地址等,选择正确的协议去打开server的大门。下面是请求的流程:
用法:
一般来说项目中的使用,也不会直接使用,还是会此进行再次封装。比如:
真正使用很简单:
(2)Vollery:高并发的处理方式,对http协议进行了很好的封装,用法简单,但是对于大数据的访问,比如大图片的下载,视频的下载等就会表现的很糟糕。换句话说他虽然在性能上进行了优化,但是只是适合对小数据量的操作。
每一种网络请求,我们都真正关心的都是response返回的数据,为此我们需要掌握两个常见的数据类型。
A.StringRequest
StringRequest stringRequest =newStringRequest("http://www.baidu.com",
newResponse.Listener() {
@Override
publicvoidonResponse(String response) {
// TODO your task
}
},newResponse.ErrorListener() {
@Override
publicvoidonErrorResponse(VolleyError error) {
// returned error message
}
});
B.JsonRequest
由于JsonRequest是一个抽象类无法直接创建它的实例.但是天无绝人之路,他有JsonRequest有JsonObjectRequest和JsonArrayRequest两个子类。
JsonObjectRequest jsonObjectRequest =newJsonObjectRequest("http://www.baidu.com",null,
newResponse.Listener() {
@Override
publicvoidonResponse(JSONObject response) {
// Todo}
},newResponse.ErrorListener() {
@Override
publicvoidonErrorResponse(VolleyError error) {
// Todo
}
});
可以看出用起来很简单,也很相似。不过值得提一句的是 在发送请求之前我们都需要new出来一个RequestQueue。然后把返回的结果丢进去就行了。这是一个请求队列,可以很好的处理高并发。
常用的加密方式
1. MD5:数字摘要。是指通过算法将长数据变为短数据,通常用来标识数据的唯一性。具有不可逆性,通常情况下为了让加密过程变得不可预测,如下代码:
public static String digest(String content){
StringBuilder builder = new StringBuilder();
try {
MessageDigest msgDitest = MessageDigest.getInstance("MD5");
msgDitest.update(content.getBytes());
byte[] digests = msgDitest.digest();
builder.append(Integer.toHexString(digests[i] & 0xff ));//
。。。。。
2. SHA1:sha1也具有不可逆性,比md5长度更长,所以更安全,但是机密的效率md5要慢一些,如文件的秒传功能,以及相同的v4包冲突都是可以根据sha1值进行比对的。
public static String digest(String content){
StringBuilder builder = new StringBuilder();
try {
MessageDigest msgDitest = MessageDigest.getInstance("SHA-1");
msgDitest.update(content.getBytes());
byte[] digests = msgDitest.digest();
//将每个字节转为16进制
for (int i=0;i <= digests.length(); i++){
builder.append(Integer.toHexString(digests[i] & 0xff +8));//+8为加盐操作
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return builder.toString();
}
3. RSA:非对称加密算法,最流行的公钥密码算法,使用长度可变的秘钥不可逆,既能用于数据加密,也可以应用于数字签名,但是RSA非对称加密内容长度有限制,1024位key的最多只能加密127位数据
//1.生成密钥对,设计口号try { MapgenKeyPair = RSACrypt.genKeyPair();
//2.获取公钥
publicKey = RSACrypt.getPublicKey(genKeyPair);
//3.获取私钥
privateKey = RSACrypt.getPrivateKey(genKeyPair);
} catch (Exception e) {
e.printStackTrace();
}
private boolean isRSAEncrypt = false;
protected void useRSA() {
try {
if(isRSAEncrypt){
//公钥解密
String str = text.getText().toString();
byte[] bs = RSACrypt.decryptByPublicKey(RSACrypt.decode(str), publicKey);
text.setText(new String(bs));
}else {
//私钥加密
byte[] bs = RSACrypt.encryptByPrivateKey(data.getBytes(), privateKey);
text.setText(RSACrypt.encode(bs));
}
isRSAEncrypt = !isRSAEncrypt;
} catch (Exception e) {
e.printStackTrace();
}
}
4.Base64:算不上什么加密算法,只是对数据进行编码传输。
5. Des&&3Des:对称加密,算法公开,计算量小,速度快!但是一旦别人拿到密钥全盘结束,安全性低。
String data = "aaaaa";
String desKey = "cccccccc";// 密钥,口号
boolean isDesEncrypt = false;
private void useDes() {
try {
if(isDesEncrypt){
//解密
text.setText(Des.decrypt(text.getText().toString(), desKey));
}else {
//加密
text.setText(Des.encrypt(data, desKey));
}
isDesEncrypt = !isDesEncrypt;
} catch (Exception e) {
e.printStackTrace();
}
}
代码的管理工具
SVN和GIt。
备注:使用性的东西暂时不过多的解释。
其他:
3G技术:移动多媒体通信系统。三种主流的3g技术:WCDMA,CDMA2000,TD-SCDMA
VideoView:用于视频播放,视频播放的原理一般是系统会首先确定视频的格式,然后得到视频的编码..然后对编码进行解码,得到一帧一帧的图像,最后在画布上进行迅速更新,显然需要在独立的线程中完成,这时就需要使用surfaceView了。
基本使用(加载视频):
setVideoPath(String Path);加载路径下的视频
setVideoURL(URL url);加载url所对应的视频。
播放视频:
mVideoView.setVideoPath(“路径”);
mVideoView.setMediaController(new MediaController(MainActivity.this));
mVideoView.start();
SurfaceView:它拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面。由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行绘制。又由于不会占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,另一方面又不会导致用户输入得不到及时响应。
一般来说,每一个窗口在SurfaceFlinger服务中都对应有一个Layer,用来描述它的绘图表面。对于那些具有SurfaceView的窗口来说,每一个SurfaceView在SurfaceFlinger服务中还对应有一个独立的Layer或者LayerBuffer,用来单独描述它的绘图表面,以区别于它的宿主窗口的绘图表面。
无论是LayerBuffer,还是Layer,它们都是以LayerBase为基类的,也就是说,SurfaceFlinger服务把所有的LayerBuffer和Layer都抽象为LayerBase,因此就可以用统一的流程来绘制和合成它们的UI。由于LayerBuffer的绘制和合成与Layer的绘制和合成是类似的,因此本文不打算对LayerBuffer的绘制和合成操作进行分析。
继承SurfaceView,并实现SurfaceHolder.Callback接口,实现它的三个方法:surfaceCreated,surfaceChanged,surfaceDestroyed。
surfaceCreated(SurfaceHolder holder):surface创建的时候调用,一般在该方法中启动绘图的线程。
surfaceChanged(SurfaceHolder holder, int format, int width,int height):surface尺寸发生改变的时候调用,如横竖屏切换。
surfaceDestroyed(SurfaceHolder holder) :surface被销毁的时候调用,如退出游戏画面,一般在该方法中停止绘图线程。
还需要获得SurfaceHolder,并添加回调函数,这样这三个方法才会执行。
RecycleView:
自定义布局,请通过布局管理器LayoutManager
自定义控制Item间的间隔(可绘制),请通过ItemDecoration
自定义控制Item增删的动画,请通过ItemAnimator
备注:这个是我们目前最常用的控件,就不再做过多的介绍。和listview,gridview等比较起来学习效果会更好。
三,总结
不知道这篇文章能否给大家带去收益,但是对自身而言,受益匪浅。