android机制,安卓的机制
Android-消息机制
一共有四个角色,Handler消息处理者、Looper消息循环、MessageQueue消息队列、Message消息。当handler调用post或者sendMessage时,最后都会调用内部的sendMessageDelayed方法,再通过enqueueMessage方法,设置了msg.target并将消息加入MessageQueue,在MessageQueue中调用了nativeWake唤醒了next方法中的nativePollOnce。而Looper的loop方法此时因为MessageQueue的next方法被阻塞着,直到next方法返回这条msg,Looper的loop调用了msg.target.diapatchMessage。到达了Handler 的事件分发,进行消息处理。
成都创新互联是一家集网站建设,梁溪企业网站建设,梁溪品牌网站建设,网站定制,梁溪网站建设报价,网络营销,网络优化,梁溪网站推广为一体的创新建站企业,帮助传统企业提升企业形象加强企业竞争力。可充分满足这一群体相比中小企业更为丰富、高端、多元的互联网需求。同时我们时刻保持专业、时尚、前沿,时刻以成就客户成长自我,坚持不断学习、思考、沉淀、净化自己,让我们为更多的企业打造出实用型网站。
从线程中取出myLooper的过程(字节)
Looper.java
有一个static final变量sThreadLocal = new ThreadLocalLooper();
Looper.myLooper()是调用了sThreadLocal.get()
ThreadLocal.java
ThreadLocal.get()中获取currentThread,
拿到Thread中的变量ThreadLocalMap,
Thread.java
变量ThreadLocal.ThreadLocalMap,数据被存在了该类中的Entry[] table
ThreadLocal.java
map.getEntry(this)-用和HashMap一样的方式计算下标(hashCode(table.len-1))
两个变量合流,拿到了ThreadLocalMap.Entry,(Entry是ThreadLocal弱引用和value的键值对组合)
entry.value就是我们需要的Looper
在MessageQueue.next()里,如果头部的这个Message是有延迟而且延迟时间没到的(now msg.when),会计算一下时间(保存为变量nextPollTimeoutMillis),然后在循环开始的时候判断如果这个Message有延迟,就调用nativePollOnce(ptr, nextPollTimeoutMillis);进行阻塞。nativePollOnce()的作用类似与object.wait(),只不过是使用了Native的方法对这个线程精确时间的唤醒。
postDelay()一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;
紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;
MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;
Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;
直到阻塞时间到或者下一次有Message进队;
这样,基本上就能保证Handler.postDelayed()发布的消息能在相对精确的时间被传递给Looper进行处理而又不会阻塞队列了。
阻塞的。Looper.loop阻塞在MessageQueue的next方法中,有一个nativePollOnce的native方法,而在MessageQueue的enqueueMessage方法的最后nativeWake方法可以唤醒阻塞,使用了epoll机制
在next()方法内部,如果有阻塞(没有消息了或者只有Delay的消息),会把mBlocked这个变量标记为true,在下一个Message进队时会判断这个message的位置,如果在队首并且时间满足条件,会调用nativeWake()方法唤醒线程!
都是用sendMessageDelayed实现,postDelay设置了delay数值,而post的delay数值为0,接着调用sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
一个线程只能又一个Looper,可以有多个Handler,不能互相发送消息,因为dispatchMessage中通过msg.target记录了发送这个Message的Handler
直接使用HandlerThread
Looper.prepare();//Looper初始化
mHandler = new Handler(Looper.myLooper());
Looper.loop();//死循环
在主线程ActivityThread创建时进行了主线程Looper的初始化,handler依赖于Looper,通过构造函数建立联系,而MessageQueue的实例在Looper中,新建的线程需要我们自己调用Looper.prepare();通过构造函数传入handler,然后用Looper.loop();开启消息机制。Android规定访问UI只能在主线程中进行,否则抛出异常(UI控件不是线程安全的,加锁使逻辑复杂,访问效率降低),通过ViewRootImpl对UI操作做了验证,由checkThread方法完成。
自己新建了Message并且把Runnable赋值给了Message的callback,在loop中会调用msg.target.dispatchMsg()以一定顺序执行第一顺序就是Message中的callback
Q:IdleHandler 有什么用?
IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机;
当 MessageQueue 当前没有立即需要处理的消息时,会执行 IdleHandler;
Q:MessageQueue 提供了 add/remove IdleHandler 的方法,是否需要成对使用?
不是必须;
IdleHandler.queueIdle() 的返回值,可以移除加入 MessageQueue 的 IdleHandler;
Q:当 mIdleHanders 一直不为空时,为什么不会进入死循环?
只有在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;
pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;
Q:是否可以将一些不重要的启动服务,搬移到 IdleHandler 中去处理?
不建议;
IdleHandler 的处理时机不可控,如果 MessageQueue 一直有待处理的消息,那么 IdleHander 的执行时机会很靠后;
Q:IdleHandler 的 queueIdle() 运行在那个线程?
陷进问题,queueIdle() 运行的线程,只和当前 MessageQueue 的 Looper 所在的线程有关;
子线程一样可以构造 Looper,并添加 IdleHandler;
Android——消息分发机制
什么是 Handler 机制 ?
Handler 机制是 Android 中用于 线程间通信 的一套通信机制。
为什么是 Handler ?Handler 机制为什么被那么多次的提及 ?
从Android4.0开始,Android 中网络请求强制不允许在主线程中操作,而更新UI的操作则不允许在子线程中执行。当在子线程中执行网络请求,拿到服务器返回的数据之后,要更新UI。由于系统的要求,势必会产生一种矛盾:数据在子线程,更新UI要在主线程。此时我们必须要把数据返回到主线程中才行,Handler机制应运而生。
Android 中针对耗时的操作,放在主线程操作,轻者会造成 UI 卡顿,重则会直接无响应,造成 Force Close。同时在 Android 3.0 以后,禁止在主线程进行网络请求。
针对耗时或者网络操作,那就不能在主线程进行直接操作了,需要放在子线程或者是工作线程中进行操作,操作完成以后,再更新主线程即 UI 线程。这里就涉及到一个问题了,在子线程执行完成以后,怎么能更新到主线程即 UI 线程呢,针对以上问题,就需要用到 Android 的消息机制了,即: Handler, Message, MessageQueue, Looper 全家桶
Handler机制中最重要的四个对象
Handler的构造方法:
Looper :
Handler的使用:
MessageQueue:
Looper.loop()
Handler.dispatchMessage()
handler导致activity内存泄露的原因:
handler发送的消息在当前handler的消息队列中,如果此时activity finish掉了,那么消息队列的消息依旧会由handler进行处理,若此时handler声明为内部类(非静态内部类),我们知道内部类天然持有外部类的实例引用,这样在GC垃圾回收机制进行回收时发现这个Activity居然还有其他引用存在,因而就不会去回收这个Activity,进而导致activity泄露。
假如在子线程执行了耗时操作,这时用户操作进入了其他的 acitvity, 那么 MainActivity 就会被内存回收的,但是这个时候发现 Handler 还在引用着 MainActivity,内存无法及时回收,造成内存泄漏。
Handler 防止内存泄漏常见方法:
为什么通过 Handler 可以把子线程的结果通知或者携带给 UI 线程 ?
这里的 Handler 指的是主线程的 Handler ,同时与 Handler 配套的 Looper , MessageQueue 是在 UI 线程初始化的,所以在子线程中调用 Handler 发送消息可以更新 UI 线程。
Looper 在 UI 线程源码, 在 ActivityThread 类:
android的事件处理机制有两种
1.基于监听的事件处理机制,有一个关键就是事件注册。 但是我们在实践的时候并没有自己手动的为某个视图控件注册监听器。
解答: 我们会经常用到 诸如 setOnclickListener(),OnTouchListener()方法等。 从字面意义理解,它为设置...监听器。 但是,它 跟注册还是颇有一些区别的。 我想注册实践监听器,就是将它挂在在一个线程上,也就是说有一个事件监听线程,那么,有事件的视图,就至少是双线程的程序了。 不过很可惜,在去看set..Listener的源码的时候,是看不到它在java源码方面的具体实现的。 也就是说,要么它依赖操作系统实现,要么它依赖jni实现,并且,事件线程由jni管理。 换言之,实现注册监听是由ni实现的。
2.事件源的触发流程:
解答: 学习过操作系统朋友应该知道,操作系统的很多操作都是通过中断来完成。 同理,比如一个点击事件,android手机硬件中,包括了一个触摸屏的硬件,它分为内屏和外屏。 其中负责触发屏幕点击和触摸中断的为内屏。 内屏大概由五个层次构成,具体有什么用不知道,反正我拆过~~~ 从内屏上,当有电容屏感应的时候,会接收到你触摸的位置信息,甚至触摸力度!!! 这个消息经由系统中断(具有最高优先级,应该是由最高优先级的进程通知)发送给cpu,经由cpu通过进程间的消息机制传递给这个进程(当前正在用户界面运行的进程,这时候只有一个),也就是这个程序运行的内存空间的某个点。(或者说通过广播机制,将这个事件发送给所有的app也是有可能的)。
Android权限机制
我们知道 Android 应用程序是沙箱隔离的,每个应用都有一个只有自己具有读写权限的专用数据目录。但是如果应用要访问别人的组件或者一些设备上全局可访问的资源,这时候权限机制就能系统化地规范并强制各类应用程序的行为准则。
Android 安全性概览
在 Android 中,一个权限,本质上是一个字符串,一个可以表示执行特定操作的能力的字符串。比如说:访问 SD 卡的能力,访问通讯录的能力,启动或访问一个第三方应用中的组件的能力。 权限被授予了之后,首先会在内存和本地中有记录,这在调用系统binder服务和其他应用组件时做鉴权依据,比如调用系统binder服务时会通过Binder.getCallingUid()拿到调用者的Uid,而Uid一般都是与应用包名一一对应的,再拿这个Uid到PMS里去查这个应用对应的权限。 其次会按被授予的权限将应用分到某个组。 可以参考
自定义权限的应用场景在于限制其它应用对本应用四大组件的访问。具体用法可以参考
pm list permissions -f 命令可以详细查看 Android 所有预定义的权限。
更详细的权限信息参考
可以看到一个权限的信息包括:定义的包名、标签、描述、 权限组 和 保护级别 。
权限根据设备的功能或特性分为多个组。如果应用已在相同权限组中被授予另一危险权限,系统将立即授予该权限,如READ_CONTACTS和WRITE_CONTACTS。
SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 由于其特殊性,其申请方式与其它权限都不同。
其授予流程如下:
(关于 AppOpsManager 是什么可以参考: )
这里简要分析下ActivityCompat#requestPermissions的流程:
更详细的权限授予流程源码分析可以参考:
普通权限: 清单文件中声明即可。
危险权限: 方式一: pm grant application_package android.permission.CHANGE_CONFIGURATION 方式二:appops set application_package permission_num 0/1
appops可以授予的权限参考 android.app.AppOpsManager 中的声明
系统签名权限: 方式一:将app迁移到system/priv-app目录中。 方式二:看不懂,参考
android 4.4 访问sd卡需要申请权限。 您的应用在 Android 4.4 上运行时无法读取外部存储空间上的共享文件,除非您的应用具有 READ_EXTERNAL_STORAGE 权限。也就是说,没有此权限,您无法再访问 getExternalStoragePublicDirectory() 返回的目录中的文件。但是,如果您仅需要访问 getExternalFilesDir() 提供的您的应用特有目录,那么,您不需要 READ_EXTERNAL_STORAGE `权限。
android 6.0 运行时权限。 此版本引入了一种新的权限模式,如今,用户可直接在运行时管理应用权限。这种模式让用户能够更好地了解和控制权限,同时为应用开发者精简了安装和自动更新过程。用户可为所安装的各个应用分别授予或撤销权限。 对于以 Android 6.0(API 级别 23)或更高版本为目标平台的应用,请务必在运行时检查和请求权限。要确定您的应用是否已被授予权限,请调用新增的 checkSelfPermission() 方法。要请求权限,请调用新增的 requestPermissions() 方法。即使您的应用并不以 Android 6.0(API 级别 23)为目标平台,您也应该在新权限模式下测试您的应用。 如需了解有关在您的应用中支持新权限模式的详情,请参阅 使用系统权限 。如需了解有关如何评估新模式对应用的影响的提示,请参阅 权限最佳做法 。
android 7.+ 应用间共享文件要使用FileProvider。 对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。 要在应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider `类。如需了解有关权限和共享文件的详细信息,请参阅 共享文件 。
android 8.+
同一权限组的权限在被授予了之后也需要显式的再申请一次。
在 Android 8.0 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。 对于针对 Android 8.0 的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。 例如,假设某个应用在其清单中列出 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 。应用请求 READ_EXTERNAL_STORAGE ,并且用户授予了该权限。如果该应用针对的是 API 级别 24 或更低级别,系统还会同时授予 WRITE_EXTERNAL_STORAGE ,因为该权限也属于同一 STORAGE 权限组并且也在清单中注册过。如果该应用针对的是 Android 8.0,则系统此时仅会授予 READ_EXTERNAL_STORAGE ;不过,如果该应用后来又请求 WRITE_EXTERNAL_STORAGE ,则系统会立即授予该权限,而不会提示用户。
android 9
隐私权限变更。
为了增强用户隐私,Android 9 引入了若干行为变更,如限制后台应用访问设备传感器、限制通过 Wi-Fi 扫描检索到的信息,以及与通话、手机状态和 Wi-Fi 扫描相关的新权限规则和权限组。
android 10
隐私权变更。
外部存储访问权限范围限定为应用文件和媒体,在后台运行时访问设备位置信息需要权限,针对从后台启动 Activity 的限制等。
android 11
隐私权限变更。
更详细的版本变更请参考
Android 系统运行机制 【Looper】【Choreographer】篇
目录:
1 MessageQueue next()
2 Vsync
3 Choreographer doFrame
4 input
系统是一个无限循环的模型, Android也不例外,进程被创建后就陷入了无限循环的状态
系统运行最重要的两个概念:输入,输出。
Android 中输入 输出 的往复循环都是在 looper 中消息机制驱动下完成的
looper 的循环中, messageQueue next 取消息进行处理, 处理输入事件, 进行输出, 完成和用户交互
应用生命周期内会不断 产生 message 到 messageQueue 中, 有: java层 也有 native层
其中最核心的方法就是 messageQueue 的 next 方法, 其中会先处理 java 层消息, 当 java 层没有消息时候, 会执行 nativePollOnce 来处理 native 的消息 以及监听 fd 各种事件
从硬件来看, 屏幕不会一直刷新, 屏幕的刷新只需要符合人眼的视觉停留机制
24Hz , 连续刷新每一帧, 人眼就会认为画面是流畅的
所以我们只需要配合上这个频率, 在需要更新 UI 的时候执行绘制操作
如何以这个频率进行绘制每一帧: Android 的方案是 Vsync 信号驱动。
Vsync 信号的频率就是 24Hz , 也就是每隔 16.6667 ms 发送一次 Vsync 信号提示系统合成一帧。
监听屏幕刷新来发送 Vsync 信号的能力,应用层 是做不到的, 系统是通过 jni 回调到 Choreographer 中的 Vsync 监听, 将这个重要信号从 native 传递到 java 层。
总体来说 输入事件获取 Vsync信号获取 都是先由 native 捕获事件 然后 jni 到 java 层实现业务逻辑
执行的是 messageQueue 中的关键方法: next
next 主要的逻辑分为: java 部分 和 native 部分
java 上主要是取java层的 messageQueue msg 执行, 无 msg 就 idleHandler
java层 无 msg 会执行 native 的 pollOnce@Looper
native looper 中 fd 监听封装为 requestQueue, epoll_wait 将 fd 中的事件和对应 request 封装为 response 处理, 处理的时候会调用 fd 对应的 callback 的 handleEvent
native 层 pollOnce 主要做的事情是:
vsync 信号,输入事件, 都是通过这样的机制完成的。
epoll_wait 机制 拿到的 event , 都在 response pollOnce pollInner 处理了
这里的 dispatchVsync 从 native 回到 java 层
native:
java:
收到 Vsync 信号后, Choreographer 执行 doFrame
应用层重要的工作几乎都在 doFrame 中
首先看下 doFrame 执行了什么:
UI 线程的核心工作就在这几个方法中:
上述执行 callback 的过程就对应了图片中 依次处理 input animation traversal 这几个关键过程
执行的周期是 16.6ms, 实际可能因为一些 delay 造成一些延迟、丢帧
input 事件的整体逻辑和 vsync 类似
native handleEvent ,在 NativeInputEventReceiver 中处理事件, 区分不同事件会通过 JNI
走到 java 层,WindowInputEventReceiver 然后进行分发消费
native :
java:
input事件的处理流程:
输入event deliverInputEvent
deliver的 input 事件会来到 InputStage
InputStage 是一个责任链, 会分发消费这些 InputEvent
下面以滑动一下 recyclerView 为例子, 整体逻辑如下:
vsync 信号到来, 执行 doFrame,执行到 input 阶段
touchEvent 消费, recyclerView layout 一些 ViewHolder
scroll 中 fill 结束,会执行 一个 recyclerView viewProperty 变化, 触发了invalidate
invalidate 会走硬件加速, 一直到达 ViewRootImpl , 从而将 Traversal 的 callback post choreographer执行到 traversal 阶段就会执行
ViewRootImpl 执行 performTraversal , 会根据目前是否需要重新layout , 然后执行layout, draw 等流程
整个 input 到 traversal 结束,硬件绘制后, sync 任务到 GPU , 然后合成一帧。
交给 SurfaceFlinger 来显示。
SurfaceFlinger 是系统进程, 每一个应用进程是一个 client 端, 通过 IPC 机制,client 将图像显示工作交给 SurfaceFlinger
launch 一个 app:
文章名称:android机制,安卓的机制
浏览路径:http://myzitong.com/article/hospgh.html