十一.Handler机制
1、定义&作用
定义:Android中线程之间消息传递、异步通信的机制。
作用:将工作线程的消息传递到UI主线程中,从而是实现工作线程对主线程的更新,避免线程操作的不安全。
2、原理
相关组件:
· Handler:消息处理者,添加消息Message到MessageQueue中,再通过Looper循环取出消息。
主要方法:handler.post()、handler.sendMessage()、handler.dispatchMessage()、handler.handleMessage()
· Message:存储需要操作的数据
· MessageQueue:存放消息的数据结构
主要方法:queue.enqueueMessage()
· Looper:消息循环
主要方法:looper.prepare()、looper.loop()
在主线程中创建Looper和MessageQueue,通过handler.sendMessage或者handler.post发送消息进入MessageQueue。
Looper不断的循环从消息队列中取出消息,发送给消息创建者handler。handler接收消息,在handleMessage方法中处理。
相关资料文章:
Android异步通信:手把手带你深入分析 Handler机制源码
Android异步通信:这是一份Handler消息传递机制的使用教程
Android 多线程:你的 Handler 内存泄露 了吗?
十二.自定义view
-
如图所示
-
View的绘制是从上往下一层层迭代下来的。DecorView-->ViewGroup(--->ViewGroup)-->View ,按照这个流程从上往下,依次measure(测量),layout(布局),draw(绘制)。
View分类:(1)单一视图 (2)视图组ViewGroup
无论是measure过程、layout过程、draw过程都是从树的根节点开始(即树形结构的顶端),view的位置是相对于父view而言的。
1、onMeasure 测量
作用:决定view的大小
onMeasure 过程
顶层ViewGroup->measure->onMeasure->measureChildren->子View->measure->onMeasure->测量完毕
ViewGroup的测量过程需要重写onMeasure方法,根据布局的特性重写。measureSpec测量规格
measureSpec(测量规格 32位的int值) = mode(测量模式 高2位 31.32位) + size(具体大小 低30位)
MeasureSpec.getMode()获取mode
MeasureSpec.getSize()获取size
MeasureSpec.makeMeasureSpec(size,mode)根据传入的size和mode返回对应的measureSpecmode测量模式分为三类:
(1)UNSPECIFIED:未指明大小,父视图不约束子视图
(2)EXACTLY:明确大小,父视图为子视图明确指定一个确切的尺寸 使用match_parent或具体数值
(3)AT_MOST:最大尺寸,父视图为子视图指定一个最大尺寸 wrap_content
2、onLayout 布局
作用:获取四个顶点,决定View的位置。
- 布局过程
布局过程也是自上而下,不同的是ViewGroup先调用onLayout让自己布局,然后再让子View布局,而onMeasure是先测量子View的大小再确定自身大小。
(1).单视图不需要实现该方法,视图组需要实现onLayout()来对子view进行布局
(2).对于单视图确定位置是在基类layout()中方法确定的,对于视图组自身位置也是layout()方法确定。layout主要用来确定子view的位置
(3).layout方法中确定位置的方法是setFrame和setOpticalFrame
(4).在视图组中,复写onLayout方法。在其中遍历子view,对于每个view依次调用layout->onLayout
3、onDraw 绘制
作用:显示内容
- 绘制过程
绘制背景、绘制内容、绘制子view、绘制装饰器
draw -> drawBackground -> onDraw -> dispatchDraw -> onDrawScrollBars
自定义View可重写onDraw以绘制不同内容
4、invalidate/postInvalidate/requestLayout
invalidate\postInvalidate
作用:都是调用onDraw方法达到重新绘制的目的
区别:invalidate只能在主线程中调用,postInvalidate能在子线程中调用,postInvalidate内部使用了handler\message机制最终还是掉用invalidate方法。requestLayout
作用:调用onMeasure\onLayout重新测量和布局,有可能调用onDraw重新绘制。
5、ViewRoot/DecorView
- ViewRoot(实际是ViewRootImpl):连接WindowManagerService和DecorView(最层级的View)的桥梁。View的三大流程均是通过ViewRootImpl来实现的。
- DecorView:顶级View,本身是一个FrameLayout。分为标题栏和内容栏,内容栏的id是R.android.id.content。Activity中的setContentView()方法最终是通过window.setContentView()
添加到DecorView的内容栏中。
6、View的绘制流程
- View的绘制流程是从ViewRoot的performTraversals方法开始:
- performTraversals会依次调用performMeasure\performLayout\performDraw,分别完成顶层View的测量、布局、绘制。
过程中会对子View完成测量、布局、绘制。
7、getMeasuredHeight 和 getHeight 方法的区别(同理getMeasuredWith/getWith)
1、getMeasuredWidth是在onMeasure之后,getWidth是在onLayout之后。
2、getMeasuredHeight方法返回的是测量后View的高度,与屏幕无关。getHeight返回的是屏幕显示的高度。当View没有超出屏幕时,他们的值
是相等的,但当View超出屏幕显示时,getMeasuredHeight的值等于getHeight的值加上超出的高度。为什么有时候用getWidth()或者getMeasureWidth()得到0?
View的绘制周期和Activity的生命周期不一致,所以在onCreate\onStart\onResume中调用方法都无法保证View测量、布局完成,所以获取的结果为0.
相关资料文章:
Android 绘制原理浅析【干货】
十三.多线程编程
1、为何有多线程?
- 主线程(UI线程)
- 在Android当中, 当应用启动的时候,系统会给应用分配一个进程,顺便一提,大部分应用都是单进程的,不过也可以通过设置来使不同组件运行在不同的进程中,
在创建进程的同时会创建一个线程,应用的大部分操作都会在这个线程中运行。所以称为主线程,同时所有的UI控件相关的操作也要求在这个线程中操作,所以也称为UI线程。
- 在Android当中, 当应用启动的时候,系统会给应用分配一个进程,顺便一提,大部分应用都是单进程的,不过也可以通过设置来使不同组件运行在不同的进程中,
- 为何会有子线程
- 因为所有的UI控件的操作都在UI线程中执行,如果在UI线程中执行耗时操作,例如网络请求等,就会阻塞UI线程,导致系统报ANR(Application Not Response)错误。
因此对于耗时操作需要创建工作线程来执行而不能直接在UI线程中执行。这样就需要在应用中使用多线程,但是Android提供的UI工具包并不是线程安全的,也就是说不能直接在
工作线程中访问UI控件,否则会导致不能预测的问题, 因此需要额外的机制来进行线程交互,主要是让其他线程可以访问UI线程。
- 因为所有的UI控件的操作都在UI线程中执行,如果在UI线程中执行耗时操作,例如网络请求等,就会阻塞UI线程,导致系统报ANR(Application Not Response)错误。
2、AsyncTask
定义:轻量级的异步任务类,内部封装了线程池、handler
缺点:1、使用的是默认的线程池
2、与Activity的生命周期不一致,在执行完doInBackground后才完结。
3、容易造成内存泄漏,AsyncTask持有Activity的引用。
4、当Activity生命周期异常后,AsyncTask结果丢失
目前AsyncTask已被遗弃,推荐使用协程
3、ThreadPool
-
线程池规则:
- 当线程池中的核心线程数未达到最大时,启动一个核心线程去执行任务。
- 如果核心线程数达到最大,任务会安排到任务队列中等待。
- 核心线程达到最大、任务队列已满,启动一个非核心线程执行任务。
- 核心线程最大、任务队列已满、非核心线程达到最大,线程池拒绝执行任务。
-
优点:
- 降低线程创建和销毁的系统开销
- 线程复用,提高系统吞吐量
- 执行大量异步任务时,提高性能
- 提供了相关管理的API,使用方便
- execute :
service.execute(new Runnable() { public void run() { System.out.println("execute方式"); } });
- submit :
Future<Integer> future = service.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println("submit方式"); return 2; } }); try { Integer number = future.get(); } catch (ExecutionException e) { e.printStackTrace(); }
- 线程池关闭:
- 调用线程池的
shutdown()
或shutdownNow()
方法来关闭线程池 - shutdown原理:将线程池状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
- shutdownNow原理:将线程池的状态设置成STOP状态,然后中断所有任务(包括正在执行的)的线程,并返回等待执行任务的列表。
-
中断采用interrupt方法,所以无法响应中断的任务可能永远无法终止。
但调用上述的两个关闭之一,isShutdown()方法返回值为true,当所有任务都已关闭,表示线程池关闭完成,则isTerminated()方法返回值为true。
当需要立刻中断所有的线程,不一定需要执行完任务,可直接调用shutdownNow()方法。
- 调用线程池的
4、IntentService
定义:可以在内部开启子线程执行耗时任务的服务。
原理:继承至Service,内部封装了handler、looper、thread等用于子线程与主线程的交互。
5、TreadLocal
定义:
存储一个数据,对指定的线程可见。原理:
通过使用当前线程的TreadLocalMap对set(value)的value进行存储,key为当前的TreadLocal。get()方法通过当前TreadLocal为key,在当前线程的TreadLocalMap中取值。ThreadLocalMap
在Tread类中有TreadLocalMap的成员变量。TreadLocalMap是一种存储K-V的数据结构,内部使用的是哈希表结构。
6、java内存分区和java内存模型(JMM)
内存分区:
·程序计数器
·本地方法区
- 存储与本地native方法交互的字节码
·虚拟机栈
- 内部使用的 栈帧 结构:(1)局部变量表(2)操作数栈(3)动态连接(4)返回地址
·方法区
- 存储类信息、元数据、常量池
·堆
- 存储实例对象,GC作用的主要区域JMM:
规定所有的变量都存储在主内存中,每条线程还有自己的工作内存。线程中的工作内存保存了被线程使用的变量的主内存副本,线程对变量的操作都必须在工作内存中进行,
不能直接读写主内存中的变量,不同线程之间也无法直接访问对方工作内存中的变量副本。线程间变量值的传递需要通过主内存来完成。
7、volatile
- 轻量级线程同步,保证操作数据的可见性、有序性,不保证原子性
8、synchronized
- 修饰普通方法、静态方法、代码块(this\object)
9、ReentrantLock
- reentrantLock 可重入锁
对比Synchronized那有些区别和优点?
·在语法上Synchronized是原生语法提供。在用法上可修饰方法、代码块。而reentrantLock需配合try-catch使用,并且需要手动释放锁。
·reentrantLock 等待可中断,是乐观锁,而Synchronized是独占锁,悲观锁。
·reentrantLock 可以添加多个锁条件
·reentrantLock 公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁。reentrantLock也可通过构造方法设置为非公平锁。
10、Atomic原子类
- 定义:适用于单个元素,能够保证一个基本数据类型、对象、或者数组的原子性。
原子更新基本类型:AtomicInteger、AtomicBoolean、AtomicLong
原子更新引用类型:AtomicReference、AtomicStampedReference、AtomicMarkableReference
原子更新数组类型:AtomicLongArray、AtomicIntegerArray、AtomicReferenceArray
原子更新对象属性:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
- 实现原理:
- CAS(compare and swap):比较并交换 compareAndSwap(v,n,e)
相关资料文章:
ThreadLocal原理其实很简单
Java并发包中的Atomic原子类
十四.跨进程通信(IPC inter-process communication)
- linux系统中使用到的IPC机制有:管道、共享内存、socket、binder(android)等。
1、Binder机制
Android系统为什么要选用Binder机制作为进程通信?
答:
·binder数据传递只需要拷贝一次,效率较高
·安全性能高,通过给应用分配UID来鉴别应用的身份
Android中的binder机制是一种高效率、安全性能高的进程通信方式-
实现原理:
· 进程隔离:系统为确保自身的安全稳定,将系统内核空间和用户空间分离开来。用户空间的进程要进行交互需要通过内核空间来驱动整个过程。
· C/S结构:Binder作为一个Service的实体,对象提供一系列的方法来实现服务端和客户端之间的请求,只要client拿到这个引用就可以进行通信。
(binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布系统的各个进程中)
· 通信模型:
· Server : 跨进程服务端,运行在某个进程,通过Binder驱动在ServiceManager中注册
· Client : 跨进程客户端,运行在某个进程,通过Binder驱动获取ServiceManager中的服务
· ServiceManager : 提供服务的注册和查询,运行在SystemServer进程
· Binder驱动:前三者位于用户空间,binder驱动位于内核空间,其实现方式和驱动差不多,负责进程之间Binder通信的建立,Binder在进程中的传递,Binder引用计数管理,
数据包在进程之间的传递和交互。
· 内存映射:Memory Map,将用户空间的一块内存地址映射到内核空间,映射关系建立后,用户对这块内存的修改可以直接反应到内核空间中。
减少了数据拷贝的次数,实现用户空间和内核空间的高效互动。(1)Binder驱动在内核空间中创建了一个数据接收缓存区
(2)并在内核空间开辟了一个内核缓存区,建立内核缓存区和数据接收缓存区的映射关系,以及数据接收缓存区和用户空间地址的映射关系。
(3)发送方进程通过copy_to_user函数将数据发送到内核缓存区,由于内核缓存区与数据接收区存在映射,而数据接收缓存区和用户空间地址映射,所以相当于把数据发送到
接收方的用户空间。
2、AIDL(android interface definition language) Android接口定义语言
- 定义:Android接口定义语言,是一套模板代码,让某个service与多个应用程序组件之间跨进程通信。
相关资料文章:
Android Binder原理(一)
十五.图片相关(bitmap加载、处理、缓存)
1、Bitmap加载(本地、网络)
- 原生方法加载网络图片
private Bitmap returnBitmapFormUrl(String url) {
long l1 = System.currentTimeMillis();
Bitmap bitmap = null;
HttpURLConnection connection = null;
InputStream inputStream = null;
URL myUrl = null;
try {
myUrl = new URL(url);
connection = (HttpURLConnection) myUrl.openConnection();
connection.setReadTimeout(6000);
connection.setConnectTimeout(6000);
connection.setDoInput(true);
connection.connect();
inputStream = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(inputStream);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
l1 = System.currentTimeMillis();
}
return bitmap;
}
- 使用三方插件Glide
2、Bitmap处理(保存、压缩)
- 保存图片至相册
private void saveImageToFile(Context context, Bitmap bitmap) {
File appDir = new File(Environment.getExternalStorageState(), "test");
if (!appDir.exists()) {
appDir.mkdir();
}
String fileName = System.currentTimeMillis() + ".jpg";
File file = new File(appDir, fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
MediaStore.Images.Media.insertImage(context.getContentResolver(), file.getAbsolutePath(), fileName, null);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Intent intent = new Intent();
intent.setAction(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.parse("file://"+file.getAbsolutePath()));
context.sendBroadcast(intent);
}
- 图片压缩
- Bitmap.compress() 质量压缩,不会对内存产生影响
- BitmapFactory.Options.inSampleSize 内存压缩
3、大图加载
BitmapFactory.decodeStream(): 获取图片
BitmapFactory.Option(): 设置图片相关参数(设置显示大小、缩放比例等)
option.inSimpleSize = 2BitmapRegionDecoder: 图片局部展示
4、图片缓存
- LurCache
内部采用LinkedHashMap存储数据,其最重要的方法trimToSize是用来移除最少使用的缓存和使用最久的缓存,并添加最新的缓存到队列中。
5、图片占用内存
getByteCount/getAllocationByteCount
with * height * 单个像素内存大小
6、如何避免加载图片出现OOM?
- 加载图片前,获取图片占用内存大小,决定是否压缩
- 对比源图片宽高和控件宽高,决定是否缩放 BitmapFactory.Options的inJustDecodeBounds属性设置为true,解析一次图片获取图片宽高
- 采用局部加载BitmapRegionDecoder
- 多张图片的采用弱引用