当前位置:首页 » 安卓系统 » android文章

android文章

发布时间: 2022-10-29 17:40:27

❶ Android 之 Project Butter 详细介绍

UI 优化系列专题,来聊一聊 Android 渲染相关知识,主要涉及 UI 渲染背景知识 如何优化 UI 渲染 两部分内容。

《 View 绘制流程之 setContentView() 到底做了什么? 》
《 View 绘制流程之 DecorView 添加至窗口的过程 》
《 深入 Activity 三部曲(3)View 绘制流程 》
《 Android 之 LayoutInflater 全面解析 》
《 关于渲染,你需要了解什么? 》
《 Android 之 Choreographer 详细分析 》

《 Android 之如何优化 UI 渲染(上) 》
《 Android 之如何优化 UI 渲染(下) 》

现在我们已经很少能够听到关于 Android UI 卡顿的话题了,这得益于 Google 长期以来对 Android 渲染性能的重视,基本每次 Google I/O 都会花很多篇幅讲这一块。随着时间的推移,Android 系统一直在不断进化、壮大,并且日趋完善。

其中,Google 在 2012 年的 I/O 大会上宣布了 Project Butter 黄油计划,那个曾经严重影响 Android 口碑的 UI 流程性问题,首先在这得到有效的控制,并且在 Android 4.1 中正式开启了这个机制。

Project Butter 对 Android Display 系统进行了重构,引入了三个核心元素,即 VSYNC Triple Buffer Choreographer

其中 VSYNC 是理解 Project Butter 的核心。接下来,我们就围绕 VSYNC 开始介绍 Project Butter 对 Android Display 系统做了哪些优化。

VSYNC 最初是由 GPU 厂商开发的一种,用于防止屏幕撕裂的技术方案,全称 Vertical Synchronization,该方案很早就已经被广泛应用于 PC 上。我们可以把它理解为一种时钟中断。

VSYNC 是一种图形技术,它可以同步 GPU 的 帧速率 和显示器的 刷新频率 ,所以在理解 VSYNC 产生的原因及其作用之前,我们有必要先来了解下这两个概念。

表示屏幕在一秒内刷新画面的次数, 刷新频率取决于硬件的固定参数,单位 Hz(赫兹)。例如常见的 60 Hz、144 Hz,即每秒钟刷新 60 次或 144 次。

逐行扫描

显示器并不是一次性将画面显示到屏幕上,而是从左到右边,从上到下逐行扫描显示,不过这一过程快到人眼无法察觉到变化。以 60 Hz 刷新率的屏幕为例,即 1000 / 60 ≈ 16ms。

表示 GPU 在一秒内绘制操作的帧数,单位 fps。例如在电影界采用 24 帧的速度足够使画面运行的非常流畅。而 Android 系统则采用更加流程的 60 fps,即每秒钟绘制 60 帧画面。更多内容参考《 Why 60 fps 》。

现在,刷新频率和帧率需要一起合作,才能使图形内容呈现在屏幕上,GPU 会获取图形数据进行绘制, 然后硬件负责把图像内容呈现到屏幕上,这一过程在应用程序的生命周期内一遍又一遍的发生。

如上图,CPU / GPU 生成图像的 Buffer 数据,屏幕从 Buffer 中读取数据刷新后显示。理想情况下帧率和刷新频率保持一致,即每绘制完成一帧,显示器显示一帧。不幸的是,刷新频率和帧率并不总是能够保持相对同步,如果帧速率实际比刷新率快,例如帧速率是 120 fps,显示器的刷新频率为 60 Hz。此时将会发生一些视觉上的问题。

当 GPU 利用一块内存区域写入一帧数据时,从顶部开始新一帧覆盖前一帧,并立刻输出一行内容。当屏幕刷新时,此时它并不知道图像缓冲区的状态,因此从缓冲区抓取的帧并不是完整的一帧画面(绘制和屏幕读取使用同一个缓冲区)。此时屏幕显示的图像会出现上半部分和下半部分明显偏差的现象,这种情况被称之为 “tearing”(屏幕撕裂)。

那如何防止 “tearing” 现象的发生呢?由于图像绘制和读取使用的是同一个缓冲区,所以屏幕刷新时可能读取到的是不完整的一帧画面。解决方案是采用 Double Buffer。

Double Buffer(双缓冲)背后的思想是让绘制和显示器拥有各自的图像缓冲区。GPU 始终将完成的一帧图像数据写入到 Back Buffer ,而显示器使用 Frame Buffer ,当屏幕刷新时,Frame Buffer 并不会发生变化,Back Buffer 根据屏幕的刷新将图形数据 到 Frame Buffer,这便是 VSYNC 的用武之地。

在 Android 4.1 之前,Android 便使用的双缓冲机制。怎么理解呢?一般来说,在同一个 View Hierarchy 内的不同 View 共用一个 Window,也就是共用同一个 Surface。

每个 Surface 都会有一个 BufferQueue 缓存队列,但是这个队列会由 SurfaceFlinger 管理,通过匿名共享内存机制与 App 应用层交互。

整个流程如下:

但是 UI 绘制任务可能会因为 CPU 在忙别的事情,导致没来得及处理。所以 从 Android 4.1 开始, VSYNC 则更进一步,现在 VSYNC 脉冲信号开始用于下一帧的所有处理

Project Butter 首先对 Android Display 系统的 SurfaceFlinger 进行了改造,目标是提供 VSYNC 中断。每收到 VSYNC 中断后,CPU 会立即准备 Buffer 数据,由于大部分显示设备刷新频率都是 60 Hz(一秒刷新 60 次),也就是说一帧数据的准备工作都要在 16ms 内完成。

这样应用总是在 VSYNC 边界上开始绘制,而 SurfaceFlinger 总是在 VSYNC 边界上进行合成。这样可以消除卡顿,并提升图形的视觉表现。

如果理解了双缓冲机制的原理,那就非常容易理解什么是三缓冲区了。如果只有两个 Graphic Buffer 缓冲区 A 和 B,如果 CPU / GPU 绘制过程较长,超过一个 VSYNC 信号周期。

由上图可知:

为什么 CPU 不能在第二个 16ms 处理绘制工作呢?原因是只有两个 Buffer,缓冲区 B 中的数据还没有准备完成,所以只能继续展示 A 缓冲区的内容,这样缓冲区 A 和 B 都分别被显示设备和 GPU 占用,CPU 则无法准备下一帧的数据。如果再提供一个缓冲区,CPU、GPU 和显示设备都能使用各自的缓冲区工作,互不影响。

简单来说,三重缓冲机制就是在双缓冲机制基础上增加了一个 Graphic Buffer 缓冲区,这样可以最大限度的利用空闲时间,带来的坏处是多使用的一个 Graphic Buffer 所占用的内存。

由上图可知:

Choreographer 也是 Project Butter 计划新增的机制,用于配合系统的 VSYNC 中断信号。它本质是一个 java 类,如果直译的话为舞蹈指导,这是一个极富诗意的表达,看到这个词不得不赞叹设计者除了 Coding 之外的广泛视野。舞蹈是有节奏的,节奏使舞蹈的每个动作更加协调和连贯;视图刷新也是如此。

Choreographer 可以接收系统的 VSYNC 信号,统一管理应用的输入、动画和绘制等任务的执行时机。Android 的 UI 绘制任务将在它的统一指挥下,井然有序的完成。业界一般通过它来监控应用的帧率。

Choreographer 的构造方法:

优先级的高低和处理顺序有关。当收到 VSYNC 信号时,Choreographer 将首先处理 INPUT 类型的回调,然后 ANIMATION 类型,最后才是 TRAVERSAL 类型。

另外,Android 在 4.1 还对 Handler 机制进行了略微改造,使之支持 Asynchronous Message(异步消息) 和 Synchronization Barrier(同步屏障)。一般情况下同步消息和异步消息的处理方式并没有什么区别,只有在设置了 同步屏障 时才会出现差异。 同步屏障为 Handler 消息机制增加了一种简单的优先级关系,异步消息的优先级要高于同步消息 。简单点说,设置了同步屏障之后,Handler 只会处理异步消息。

以 View 的绘制流程为例:

scheleTraversals 首先禁止了后续消息的处理能力,一旦设置了消息队列的 postSyncBarrier,所有非 Asynchronous 的消息将被停止派发。

UI 绘制任务设置了 CALLBACK 类型为 TRAVERSAL 类型的任务,即 mTraversalRunnable:

Choreographer 的 postCallback 方法将会申请一次 VSYNC 中断信号,通过 DisplayEventReceiver 的 scheleVsync 方法。当 VSYNC 信号到达时,便会回调 Choreographer 的 doFrame 方法,内部会触发已经添加的回调任务:

此时 UI 绘制任务 doTraversal 方法被回调,即在 Android 4.1 之后, UI 绘制任务被放置到了 VSYNC 中断处理中了。Choreographer 确实做到了统一协调管理 UI 的绘制工作。有关 Choreographer 更详细的分析,可以参考《 Android 之 Choreographer 详细分析 》。

在从根本解决 Android UI 不流畅的问题上,Project Butter 黄油计划率先迈出了最重要一步,Android 的渲染性能也确实有了很大改善。

不过优化是无止境的,Google 在后续版本中又引入了一些比较大的改变,例如 Android 5.0 的 RenderThread,Android 将所有的绘制任务都放到了该线程,这样即便主线程有耗时的操作也可以保证动画流畅性。

关于 UI 渲染所涉及的内容非常多,而且 Android 渲染框架演进的非常快,文章最后也会附上一些(1)android文章扩展阅读,便于更好的学习理解。

文中如有不妥或有更好的分析结果,欢迎您的留言或指正。文章如果对你有帮助,请留个赞吧。

其他系列专题

❷ Android invalidate/postInvalidate/requestLayout-彻底厘清

系列文章:
Android Activity创建到View的显示过程
Android Activity 与View 的互动思考
Android invalidate/postInvalidate/requestLayout-彻底厘清
Android 容易遗漏的刷新小细节

前几篇分析了Measure、Layout、Draw 过程,这三个过程在第一次展示View的时候都会调用。那之后更改了View的属性呢?比如更改颜色、更换文字内容、更换图片等,还会走这三个过程吗?循着这个思路,来分析Invalidate/RequestLayout流程。
通过本篇文章,你将了解到:

MyView 默认展示一块红色的矩形区域,暴露给外界的方法:setColor
用以改变绘制的颜色。颜色改变后,需要重新执行onDraw(xx)才能看到改变后的效果,通过invalidate()方法触发onDraw(xx)调用。
接下来看看invalidate()方法是怎么触发onDraw(xx)方法执行的。

invalidate顾名思义:使某个东西无效。在这里表示使当前绘制内容无效,需要重新绘制。当然,一般来说常常简单称作:刷新。
invalidate()是View.java 里的方法。

从上可知,当前要刷新的View确定了刷新区域后即调用了父布局的invalidateChild(xx)方法。该方法为ViewGroup里的final方法。

由上可知,在该方法里区分了硬件加速绘制与软件绘制,分别来看看两者区别:

硬件加速绘制分支
如果该Window支持硬件加速,则走下边流程:

onDescendantInvalidated 方法的目的是不断向上寻找其父布局,并将父布局PFLAG_DRAWING_CACHE_VALID 标记清空,也就是绘制缓存清空。
而我们知道,根View的mParent指向ViewRootImpl对象,因此来看看它里面的onDescendantInvalidated()方法:

做个小结:

用图表示硬件加速绘制的invaldiate流程:

软件绘制分支
如果该Window不支持硬件加速,那么走软件绘制分支:
parent.invalidateChildInParent(location, dirty) 返回mParent,只要mParent不为空那么一直调用invalidateChildInParent(xx),实际上这也是遍历ViewTree过程,来看看关键invalidateChildInParent(xx):

与硬件加速绘制一致,最终调用ViewRootImpl invalidateChildInParent(xx),来看看实现:

做个小结:

用图表示软件绘制invalidate流程:

上述分析了硬件加速绘制与软件绘制时invalidate的不同,它们的最终目的都是为了重走Draw过程。重走Draw过程通过调用scheleTraversals() 触发的,来看看是如何触发的。

想了解更多硬件加速绘制请移步:
Android 自定义View之Draw过程(中)

触发Draw过程
scheleTraversals 详细分析在这篇文章:
Android Activity创建到View的显示过程

三大流程真正开启在ViewRootImpl->performTraversals(),在该方法里根据一定的条件执行了Measure(测量)、Layout(摆放)、Draw(绘制)。
本次着重分析如何触发Draw过程。

可以看出,invalidate 最终触发了Draw过程。

可以看出,启用硬件加速绘制可以避免不必要的绘制。
关于硬件加速绘制与软件绘制详细区别,请移步系列文章:
Android 自定义View之Draw过程(上)

最后,用图表示invalidate流程:

顾名思义,重新请求布局。
来看看View.requestLayout()方法:

可以看出,这个递归调用和invalidate一样的套路,向上寻找其父布局,一直到ViewRootImpl为止,给每个布局设置PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED标记。
查看ViewRootImpl requestLayout()

很明显,requestLayout目的很单纯:

和invalidate一样的配方,当刷新信号来到之时,调用doTraversal()->performTraversals(),而在performTraversals()里真正执行三大流程。

由此可见:

之前设置的PFLAG_FORCE_LAYOUT标记有啥用呢?
回忆一下measure 过程:

PFLAG_FORCE_LAYOUT 标记打上之后,会触发onMeasure()测量自身及其子布局。

试想一下,假设View的尺寸改变了,变大了,那么调用了requestLayout后因为走了Measure、Layout 过程,测量、摆放倒是重新设置了,但是不调用Draw出不来效果啊。实际上,View layout时候已经考虑到了。
在View.layout(xx)->setFrame(xx)里

也就是说:

关于measure、layout 过程更深入的分析,请移步:

用图表示requestLayout过程:

结合requestLayout和invalidate与View三大流程关系,有如下图:

总结一下:

上面仅仅说明了单个布局Invalidate/RequestLayout联系,那么如果父布局调用了invalidate,那么子布局会走重绘过程吗?接下来列举这些关系。

子布局Invalidate
如果是软件绘制或者父布局开启了软件缓存绘制,父布局会走重绘过程(前提是WILL_NOT_DRAW标记没设置)。

子布局RequestLayout
父布局会重走Measure、Layout过程。

父布局Invalidate
如果是软件绘制,则子布局会走重绘过程。

父布局RequestLayout
如果父布局尺寸发生了改变,则会触发子布局Measure过程、Layout过程。

在Activity onCreate里创建子线程并展示对话框:

答案是可以的,接下来分析为什么可以。

在分析ViewRootImpl里requestLayout/invalidate过程中,发现其内部调用了checkThread()方法:

问题的关键是mThread是什么?从哪里来?

而创建ViewRootImpl对象是在调用WindowManager.addView(xx)过程中创建的。
关于WindowManager/Window 请移步: Window/WindowManager 不可不知之事

现在回过头来看Dialog创建就比较明朗了:

实际上,"子线程不能更新ui" 更合理的表述应为:View只能被构建了ViewTree的线程操作。只是通常来说,Activity 构建ViewTree的线程被称作UI(主)线程,因此才会有上述说法。

既然invalidate()只能主线程调用(硬件加速条件下,不调用checkThread()),那如果想在子线程调用呢?当然想到的是先通过Handler切换到主线程,再执行invalidate(),但是每次这么写有点冗余,幸好,View里提供了postInvalidate:

切到ViewRootImpl.java

发现了真相:

本文基于Android 10.0

❸ Android:一篇文章带你完全梳理自定义View工作流程!

了解自定义View流程前,需了解一定的自定义View基础,具体请看文章: (1)自定义View基础 - 最易懂的自定义View原理系列

下面,我将详细讲解 View 绘制的三大流程: measure 过程、 layout 过程、 draw 过程

请看文章: 自定义View Layout过程 - 最易懂的自定义View原理系列(3)

至此,关于自定义 View 的工作流程讲解完毕。

结合原理 & 实现步骤,若需实现1个自定义View,请看文章: 手把手教你写一个完整的自定义View

❹ 《深入理解Android卷I》epub下载在线阅读,求百度网盘云资源

《深入理解Android:Wi-Fi、NFC和GPS卷》(邓凡平)电子书网盘下载免费在线阅读

资源链接:

链接: https://pan..com/s/1Ys1OZJ-Gt-NHzDbbr1P8WA

提取码: edhg

书名:深入理解Android:Wi-Fi、NFC和GPS卷

作者:邓凡平

豆瓣评分:8.7

出版社:机械工业出版社

出版年份:2014-4-15

页数:575

内容简介:

本书是经典畅销书“深入理解Android”系列的新作,由资深Android系统专家邓凡平先生撰写,全志和高通等公司资深专家担任技术审校并强烈推荐。从通信专业知识和Android系统代码实现的角度,对Netd、Wi-Fi、NFC和GPS等模块的代码进行深入的剖析,旨在深刻揭示其实现原理和工作流程。其中涉及大量通信相关的专业知识,因此特意邀请全志和高通等着名芯片公司的资深专家担任技术审校。本书从实际应用的需求出发,适合所有Android系统工程师、Android应用开发工程师和BSP开发工程师阅读。

全书共9章。第1章介绍本书的内容组成、工具使用以及参考源码的下载方法。第2章介绍Netd及相关的背景知识。第3~5章介绍Wi-Fi基础知识,重点分析了wpa_supplicant的实现,以及Android平台中特有的Wi-Fi服务模块WifiService。第6~7章讲解了Wi-Fi联盟推出的两项重要技术Wi-Fi Simple Configuration和Wi-Fi P2P,以及它们在Android平台中的代码实现。第8章详细介绍了NFC基础知识,以及NFC在Android平台中的代码实现。第9章讲解了GPS原理及Android平台中的位置管理服务架构。

本书主要内容及特色:

本书所讲解的Wi-Fi、NFC以及GPS模块的背后都涉及非常多的专业知识,例如与Wi-Fi相关的802.11协议、Wi-Fi Alliance(Wi-Fi联盟)定义的Wi-Fi Simple Configuration和Wi-Fi P2P协议、NFC Forum定义的一整套与NFC相关的协议、与GPS相关的卫星导航原理、AGPS和OMA-SUPL协议等。显然,如果不了解这些专业知识,就不可能真正掌握它们在Android平台中的代码实现。

考虑到这些专业知识的重要性,本书在讲解Android平台中Wi-Fi、NFC和GPS模块的实现之前,先重点介绍与代码相关的专业知识。当然,这些专业知识内容如此丰富,在一本书中无法全部涵盖。为了方便读者进一步深入学习,本书每章的最后都会列举笔者在撰写各章时所阅读的参考资料。

以下是本书的内容概述。

第1章介绍本书的内容组成、使用的工具以及参考源码的下载方法。

第2章介绍Netd以及相关的背景知识。

第3章介绍Wi-Fi基础知识。Wi-Fi是本章的重点,而且也是当下最热门的技术。

第4章介绍wpa_supplicant,它是Wi-Fi领域中最核心的软件实现。

第5章介绍WifiService,它是Android平台中特有的Wi-Fi服务模块。

第6章和第7章介绍Wi-Fi Alliance推出的两项重要技术——Wi-Fi Simple Configuration和Wi-Fi P2P,以及它们在Android平台中的代码实现。

第8章介绍NFC背景知识以及NFC在Android平台中的代码实现。NFC也是历史比较悠久的技术,希望它能随着Android的普及而走向大众。

第9章介绍GPS原理及Android平台中的位置管理服务架构。

附录为笔者和审稿专家之一的吴劲良先生关于本书定位、学习方法等方面的讨论。相信这些讨论内容能引起读者的共鸣。

本书通过理论和代码相结合的方式进行讲解,旨在引领读者一步步了解Wi-Fi、NFC和GPS模块的工作原理。总之,笔者希望读者在阅读完本书后能有以下收获。

初步掌握Wi-Fi、NFC和GPS的专业知识。

根据其实现代码,进一步加深对这些专业知识的理解。

读者对象:

适合阅读本书的读者包括:

Android系统开发工程师

系统开发工程师常常需要深入理解系统的运转过程,而本书所涉及的内容正是他们在工作和学习中最想了解的。对具体模块感兴趣的读者也可单刀直入,阅读相关章节。

Wi-Fi、NFC或GPS的BSP开发工程师

BSP开发工程师更需要对Android平台中这些模块的工作原理及背景知识有深入的理解。虽然本书没有介绍这些模块在linux Kernel层的实现,但了解它们在用户空间的工作流程也将极大帮助BSP开发工程师拓展自己的知识面。

对Wi-Fi、NFC和GPS感兴趣的在校高年级本科生、研究生和其他读者

在掌握理论的基础上,如何在实际代码中来实现或使用它们也许是众多学子最想知道的。希望这本理论与代码实现深度结合的书籍会助您一臂之力。

作者简介:

邓凡平 资深Android系统工程师,对Android系统的设计与实现有非常深入的研究,曾担任Tieto公司高级软件架构师。畅销书“深入理解Android”系列的总策划和主笔,出版有畅销书《深入理解Android:卷I》和《深入理解Android:卷II》。喜欢钻研,乐于分享,活跃于CSDN、51CTO和开源中国等专业技术社区,撰写的Android Framework源码分析的系列文章深受读者欢迎。2013年荣获51CTO读书频道评选的“最受读者喜爱的IT图书作者奖”。

❺ Android 自定义View之Draw过程(上)

Draw 过程系列文章

Android 展示之三部曲:

前边我们已经分析了:

这俩最主要的任务是: 确定View/ViewGroup可绘制的矩形区域。
接下来将会分析,如何在这给定的区域内绘制想要的图形。

通过本篇文章,你将了解到:

Android 提供了关于View最基础的两个类:

然而ViewGroup 并没有约定其内部的子View是如何布局的,是叠加在一起呢?还是横向摆放、纵向摆放等。同样的View 也没有约定其展示的内容是啥样,是矩形、圆形、三角形、一张图片、一段文字抑或是不规则的形状?这些都要我们自己去实现吗?
不尽然,值得高兴的是Android已经考虑到上述需求了,为了开发方便已经预制了一些常用的ViewGroup、View。
如:
继承自ViewGroup的子类

继承自View的子类

虽然以上衍生的View/ViewGroup子类已经大大为我们提供了便利,但也仅仅是通用场景下的通用控件,我们想实现一些较为复杂的效果,比如波浪形状进度条、会发光的球体等,这些系统控件就无能为力了,也没必要去预制千奇百怪的控件。想要达到此效果,我们需要自定义View/ViewGroup。
通常来说自定义View/ViewGroup有以下几种:

3 一般不怎么用,除非布局比较特殊。1、2、4 是我们常用的手段,对于我们常说的"自定义View" 一般指的是 4。
接下来我们来看看 4是怎么实现的。

在xml里引用MyView

效果如下:

黑色部分为其父布局背景。
红色矩形+黄色圆形即是MyView绘制的内容。
以上是最简单的自定义View的实现,我们提取重点归纳如下:

由上述Demo可知,我们只需要在重写的onDraw(xx)方法里绘制想要的图形即可。
来看看View 默认的onDraw(xx)方法:

发现是个空实现,因此继承自View的类必须重写onDraw(xx)方法才能实现绘制。该方法传入参数为:Canvas类型。
Canvas翻译过来一般叫做画布,在重写的onDraw(xx)里拿到Canvas对象后,有了画布我们还需要一支笔,这只笔即为Paint,翻译过来一般称作画笔。两者结合,就可以愉快的作画(绘制)了。
你可能发现了,在Demo里调用

并没有传入Paint啊,是不是Paint不是必须的?实际上调用该方法后,底层会自动生成Paint对象。

可以看到,底层初始化了Paint,并且给其设置的颜色为在Java层设置的颜色。

onDraw(xx)比较简单,开局一个Canvas,效果全靠画。
试想,这个Canvas怎么来的呢,换句话说是谁调用了onDraw(xx)。发挥一下联想功能,在Measure、Layout 过程有提到过两者套路很像:

那么Draw过程是否也是如此套路呢?看见了onDraw(xx),那么draw(xx)还远吗?
没错,还真有draw(xx)方法:

可以看出,draw(xx)主要分为两个部分:

不管是A分支还是B分支,都进行了好几步的绘制。
通常来说,单一一个View的层次分为:

后面绘制的可能会遮挡前边绘制的。
对于一个ViewGroup来说,层次分为:

来看看A分支标注的4个点:
(1)
onDraw(canvas)
前面分析过,对于单一的View,onDraw(xx)是空实现,需要由我们自定义绘制。
而对于ViewGroup,也并没有具体实现,如果在自定义ViewGroup里重写onDraw(xx),它会执行吗?默认是不会执行的,相关分析请移步:
Android ViewGroup onDraw为什么没调用

(2)
dispatchDraw(canvas),来看看在View.java里的实现:

发现是个空实现,再看看ViewGroup.java里的实现:

也即是说,对于单一View,因为没有子布局,因此没必要再分发Draw,而对于ViewGroup来说,需要触发其子布局发起Draw过程(此过程后续分析),可以类比事件分发过程View、ViewGroup的处理。感兴趣的请移步:
Android 输入事件一撸到底之View接盘侠(3)

(3)
OverLay,顾名思义就是"盖在某个东西上面",此处是在绘制内容之后,绘制前景之前。怎么用呢?

以上是给一个ViewGroup设置overLay,效果如下:

你可能发现了,这和设置overLay差不多的嘛,实际还是有差别的。在onDrawForeground(xx)里会重新调整Drawable的尺寸,该尺寸与View大小一致,之前给Drawable设置的尺寸会失效。运行效果如下:

可以看出,ViewGroup都被前景盖住了。
再来看看B分支的重点:边缘渐变效果
先来看看TextView 边缘渐变效果:

加上这俩参数。
实际上系统自带的一些控件也使用了该效果,如NumberPicker、YearPickerView

以上是NumberPicker 的效果,可以看出是垂直方向渐变的。

对于View.java 里的onDraw(xx)、draw(xx),ViewGroup.java里并没有重写。
而对于dispatchDraw(xx),在View.java里是空实现。在ViewGroup.java里发起对子布局的绘制。

来看看标记的2点:
(1)
设置padding的目的是为了让子布局留出一定的空隙出来,因此当设置了padding后,子布局的canvas需要根据padding进行裁减。判断标记为:

FLAG_CLIP_TO_PADDING 默认设置为true
FLAG_PADDING_NOT_NULL 只要有padding不为0,该标记就会打上。
也就是说:只要设置了padding 不为0,子布局显示区域需要裁减。
能不能不让子布局裁减显示区域呢?
答案是可以的。
考虑到一种场景:使用RecyclerView的时候,我们需要设置paddingTop = 20px,效果是:RecyclerView Item展示时离顶部有20px,但是滚动的时候永远滚不到顶部,看起来不是那么友好。这就是上述的裁减起作用了,需要将此动作禁止。通过设置:

当然也可以在xml里设置:

(2)
drawChild(xx)

从方法名上看是调用子布局进行绘制。
child.draw(x1,x2,x3)里分两种情况:

这两者具体作用与区别会在下篇文章分析,不管是硬件加速绘制还是软件加速绘制,最终都会调用View.draw(xx)方法,该方法上面已经分析过。
注意,draw(x1,x2,x3)与draw(xx)并不一样,不要搞混了。

用图表示:

View/ViewGroup Draw过程的联系:

一般来说,我们通常会自定义View,并且重写其onDraw(xx)方法,有没有绘制内容的ViewGroup需求呢?
是有的,举个例子,大家可以去看看RecyclerView ItemDecoration 的绘制,其中运用到了ViewGroup draw(xx)、ViewGroup onDraw(xx) 、View onDraw(xx)绘制的先后顺序来实现分割线,分组头部悬停等功能的。

本篇文章基于 Android 10.0

❻ android 系统签名

也有提到怎么单独给一个apk签名,这里补充一下android的签名权限控制机制。

android的标准签名key有:

     testkey

     media

    latform

    hared

    以上的四种,可以在源码的/build/target/proct/security里面看到对应的密钥,其中shared.pk8代表私钥,shared.x509.pem公钥,一定是成对出现的。

    其中testkey是作为android编译的时候默认的签名key,如果系统中的apk的android.mk中没有设置LOCAL_CERTIFICATE的值,就默认使用testkey。

   而如果设置成:

   LOCAL_CERTIFICATE := platform

    就代表使用platform来签名,这样的话这个apk就拥有了和system相同的签名,因为系统级别的签名也是使用的platform来签名,此时使用android:sharedUserId="android.uid.system"才有用!

     在/build/target/proct/security目录下有个README,里面有说怎么制作这些key以及使用问题(android4.2):

     从上面可以看出来在源码下的/development/tools目录下有个make_key的脚本,通过传入两个参数就可以生成一对签名用的key。

    其中第一个为key的名字,一般都默认成android本身有的,因为很多地方都默认使用了这些名字,我们自定义的话只需要对第二个参数动手脚,定义如下:

    C ---> Country Name (2 letter code) ST ---> State or Province Name (full name) L ---> Locality Name (eg, city) O ---> Organization Name (eg, company) OU ---> Organizational Unit Name (eg, section) CN ---> Common Name (eg, your name or your server’s hostname) emailAddress ---> Contact email addre

    另外在使用上面的make_key脚本生成key的过程中会提示输入password,我的处理是不输入,直接enter,不要密码!后面解释,用自定义的key替换/security下面的。

    可以看到android源码里面的key使用的第二个参数就是上面README里面的,是公开的,所以要release版本的系统的话,肯定要有自己的签名key才能起到一个安全控制作用。

    在上面提到如果apk中的编译选项LOCAL_CERTIFICATE没有设置的话,就会使用默认的testkey作为签名key,我们可以修改成自己想要的key,按照上面的步骤制作一个releasekey,修改android配置在/build/core/config.mk中定义变量:

在主makefile文件里面:

ifeq ($(DEFAULT_SYSTEM_DEV_CERTIFICATE),build/target/proct/security/releasekey)

  BUILD_VERSION_TAGS += release-key

这样的话默认的所有签名将会使用releasekey。

修改完之后就要编译了,如果上面的这些key在制作的时候输入了password就会出现如下错误:

我在网上找到了合理的解释:

其实会出现这个错误的最根本的原因是多线程的问题。在编译的时候为了加速一般都会执行make -jxxx,这样本来需要手动输入密码的时候,由于其它线程的运行,就会导致影响当前的输入终端,所以就会导致密码无法输入的情况!

再编译完成之后也可以在build.prop中查看到变量:

这样处理了之后编译出来的都是签名过的了,系统才算是release版本

我发现我这样处理之后,整个系统的算是全部按照我的要求签名了。

网上看到还有另外的签名release办法,但是应该是针对另外的版本的,借用学习一下:

make -j4 PRODUCT-proct_mol-user dist

这个怎么跟平时的编译不一样,后面多了两个参数PRODUCT-proct_mol-user 和 dist. 编译完成之后回在源码/out/dist/目录内生成个proct_mol-target_files开头的zip文件.这就是我们需要进行签名的文件系统.

我的proct_mol 是full_gotechcn,后面加“-user”代表的是最终用户版本,关于这个命名以及proct_mol等可参考http://blog.csdn.net/jscese/article/details/23931159

编译出需要签名的zip压缩包之后,就是利用/security下面的准备的key进行签名了:

./build/tools/releasetools/sign_target_files_apks -d /build/target/proct/security  out/dist/full_gotechcn-target_files.zip   out/dist/signed_target_files.zi

签名目标文件 输出成signed_target_files.zi

如果出现某些apk出错,可以通过在full_gotechcn-target_files.zip前面加参数"-e =" 来过滤这些apk.

然后再通过image的脚本生成imag的zip文件,这种方式不适用与我目前的工程源码,没有做过多验证!

Android签名机制可划分为两部分:(1)ROM签名机制;(2)第三方APK签名机制。

Android APK实际上是一个jar包,而jar包又是一个zip包。APK包的签名实际上使用的是jar包的签名机制:在zip中添加一个META的子目录,其中存放签名信息;而签名方法是为zip包中的每个文件计算其HASH值,得到签名文件(*.sf),然后对签名文件(.sf)进行签名并把签名保存在签名块文件(*.dsa)中。

在编译Android源码生成ROM的过程中,会使用build/target/proct/security目录中的4个key(media, platform, shared, testkey)来对apk进行签名。其中,*.pk8是二进制形式(DER)的私钥,*.x509.pem是对应的X509公钥证书(BASE64编码)。build/target/proct/security目录中的这几个默认key是没有密码保护的,只能用于debug版本的ROM。

要生成Release版本的ROM,可先生成TargetFiles,再使用带密码的key对TargetFiles重新签名,最后由重签名的TargetFiles来生成最终的ROM。

可以使用Android源码树中自带的工具“development/tools/make_key”来生成带密码的RSA公私钥对(实际上是通过openssl来生成的): $ development/tools/make_key media ‘/C=CN/ST=Sichuan/L=Cheng/O=MyOrg/OU=MyDepartment/CN=MyName’ 上面的命令将生成一个二进制形式(DER)的私钥文件“media.pk8”和一个对应的X509公钥证书文件“media.x509.pem”。其中,/C表示“Country Code”,/ST表示“State or Province”,/L表示“City or Locality”,/O表示“Organization”,/OU表示“Organizational Unit”,/CN表示“Name”。前面的命令生成的RSA公钥的e值为3,可以修改development/tools/make_key脚本来使用F4 (0×10001)作为e值(openssl genrsa的-3参数改为-f4)。

也可以使用JDK中的keytool来生成公私钥对,第三方APK签名一般都是通过keytool来生成公私钥对的。

可以使用openssl x509命令来查看公钥证书的详细信息: $ openssl x509 -in media.x509.pem -text -noout or, $ openssl x509 -in media.x509.pem -inform PEM -text -noout

还可以使用JDK中的keytool来查看公钥证书内容,但其输出内容没有openssl x509全面: $ keytool -printcert -v -file media.x509.pem

有了key之后,可以使用工具“build/tools/releasetools/sign_target_files”来对TargetFiles重新签名: $ build/tools/releasetools/sign_target_files_apks -d new_keys_dir -o target_files.zip target_files_resigned.zip 其中,new_keys_dir目录中需要有四个key(media, platform, shared, releasekey)。注意:这里的releasekey将代替默认的testkey(请参考build/tools/releasetools/sign_target_files脚本实现),也就是说,如果某个apk的Android.mk文件中的LOCAL_CERTIFICATE为testkey,那么在生成TargetFiles时是使用的build/target/proct/security/testkey来签名的,这里重新签名时将使用new_keys_dir/releasekey来签名。

uild/tools/releasetools/sign_target_files_apks是通过host/linux-x86/framework/signapk.jar来完成签名的。也可以直接使用host/linux-x86/framework/signapk.jar来对某个apk进行签名: $ java -jar signapk [-w] publickey.x509[.pem] privatekey.pk8 input.jar output.jar 其中,”-w”表示还对整个apk包(zip包)进行签名,并把签名放在zip包的comment中。

对于第三方应用开发者而言,对APK签名相对要简单得多。第三方应用开发一般采用JDK中的keytool和jarsigner来完成签名密钥的管理和APK的签名。

使用keytool来生成存储有公私钥对的keystore: $ keytool -genkey -v -keystore my-release-key.keystore -alias mykey -keyalg RSA -keysize 2048 -validity 10000

查看生成的密钥信息: $ keytool -list -keystore my-release-key.keystore -alias mykey -v or, $ keytool -list -keystore my-release-key.keystore -alias mykey -rfc (注:获取Base64格式的公钥证书,RFC 1421)

导出公钥证书: $ keytool -export -keystore mystore -alias mykey -file my.der (注:二进制格式公钥证书,DER) $ keytool -export -keystore mystore -alias mykey -file my.pem -rfc (注:Base64格式公钥证书,PEM)

对APK进行签名: $ jarsigner -verbose -keystore my-release-key.keystore my_application.apk mykey

验证签名: $ jarsigner -verify -verbose -certs my_application.apk

在进行Android二次开发时,有时需要把build/target/proct/security下面的公私钥对转换为keystore的形式,可以参考这篇文章:把Android源码中的密码对转换为keystore的方法。

❼ android一篇文章怎么显示

装个看文章的阅读软件,可以在APP商店里搜索个OFFICE看看

❽ Android11 Notification功能解析

       我们知道,当手机有通知时,下拉通知中心中会显示所有的通知,该功能是在SystemUI中实现的,接着上篇文章 Android11 SystemUI解析 ,本文对通知相关的功能逻辑进行分析,基于Android11 CarSystemUI的通知功能逻辑展开分析。
       关于通知功能逻辑,简单来说,无非就是四步,注册、发送、接收、显示,那么接下来针对以上四步进行源码详细分析。

       关于CarSystemUI启动及相关逻辑可以参考文章 Android11 SystemUI解析 ,本文就不赘述了,直接以类为入口进行分析:

       从构造方法来看:

       可以看到,在创建以上实例时,会通过Inject的方式来创造对应参数的实例,该功能是通过dagger2来实现,具体对应的Mole为CarNotificationMole类,看一下CarNotificationListener实例创造时的实现,关于NotificationViewController后面再分析:

       可以看到,在()提供CarNotificationListener实例时,还执行了registerAsSystemService()方法,接下来看一下CarNotificationListener类:

       CarNotificationListener继承了NotificationListenerService类,该类继承了Service,是framework内部的组成部分,通过registerAsSystemService()来看一下该类的实现:

       该方法内部主要执行了两项操作:
       a.创建了NotificationListenerWrapper对象,该类继承了INotificationListener.Stub,主要用来接收回调,后面在显示环节进行详细分析;

       b.将以上对象作为参数通过INotificationManager的registerListener进行注册;

       通过getNotificationInterface()的是实现可以知道,registerListener()执行到了NotificationManagerService里面去了,接下来一起看一下:

       mListeners是NotificationListeners实例,是在init()中进行初始化的,NotificationListeners是其内部类,看一下具体实现:

       NotificationListeners继承了ManagedServices,registerSystemService方法是在ManagedServices里面实现的,看一下:

       根据调用关系,registerServiceImpl()方法内先将前面创建的INotificationListener(mWrapper)作为参数创建了ManagedServiceInfo实例info,然后执行linkToDeath进行死亡监测,最后将info加入mServices中进行管理,执行完后再执行onServiceAdded(info),该方法是在NotificationListeners类内部实现的,再回去看一下该方法,最终会调用到CarNotificationListener.java里面的onListenerConnected()方法。
       以上注册过程逻辑比较绕,用一张图来总结一下:

       发送过程比较简单,按照系统提供的方法来发送即可,主要涉及NotificationChannel、Notification、NotificationManager这三个类,简单看一下:
       首先某个应用在发送通知前需要创建该应用对应的NotificationChannel,然后在通知中传入对应channel ID就可以了,创建如下:

       在创建NotificationChannel时需要传入唯一的id、name和importance,创建如下:

       创建完NotificationChannel后,再创建Notification,Notification创建采用的是Builder模式,主要涉及的内容比较多,创建如下:

       Notification涉及的内容比较多,可以根据需要进行设定;

       创建完Notification后,通过NotificationManger来进行发送就可以了:

       执行完notify后续的逻辑处理过程,在接收环节进行分析;

       发送时会调用到notify()方法:

       跟随调用关系,会调用到notifyAsUser()方法:

       在notifyAsUser()会调用到NotificationManagerService中的enqueueNotificationWithTag()方法,先看一下fixNotification()方法:

       需要注意一下:当应用targetSdkVersion大于22时,在创建Notification时需要传入smallIcon,否则会抛异常导致发送不成功;接下来看一下enqueueNotificationWithTag()方法:

       NotificationManagerService继承了SystemService,在SystemServer中会进行启动,在start()方法内部会执行publishBinderService来进行Binder注册提供服务:

       可以看到,enqueueNotificationWithTag()会调用到enqueueNotificationInternal(),该方法是真正的逻辑实现:

       该方法中主要做了以下几件事:
       1.进行各种check来确保notification的合法性;
       2.将Notification作为参数创建StatusBarNotification;
       3.获取Notification对应的channel id,根据channel id 来获取应用对应的NotificationChannel,如果为空的话,就直接返回了,因此应用在发送notification前需要先创建对应NotificationChannel;
       4.通过Handler post EnqueueNotificationRunnable来执行后续逻辑;

       在EnqueueNotificationRunnable内部会将r(NotificationRecord)加入mEnqueuedNotifications进行管理,然后判断该NotificationRecord是否已经存在过,最后执行PostNotificationRunnable;

       PostNotificationRunnable的run()中主要处理逻辑如下:
       1.从mEnqueuedNotifications中找到跟key对应的NotificationRecord;
       2.通过indexOfNotificationLocked()看mNotificationList里面是否已经包含该NotificationRecord,如果不存在,说明是新的record,需要加入mNotificationList进行管理;否则的话,将mNotificationList中index对应的NotificationRecord进行更新;
       3.将要处理的NotificationRecord放入mNotificationsByKey进行管理;
       4.执行mListeners.notifyPostedLocked(r, old)来进行通知;
       5.在finally里面将要处理的NotificationRecord从mEnqueuedNotifications里面移除;
       6.如果在加入后有取消操作,需要立刻执行取消操作,并将NotificationRecord从mDelayedCancelations中移除;

       前面讲到,在PostNotificationRunnable中会执行mListeners.notifyPostedLocked(r, old)进行通知,mListeners是NotificationListeners实例:

       跟随调用关系:

       通过getServices()来找到已经注册过的ManagedServiceInfo列表,最后执行notifyPosted();

       在notifyPosted()内部通过info.service来找到对应的INotificationListener实例,对应NotificationListenerService内部的NotificationListenerWrapper,然后将StatusBarNotification封装成StatusBarNotificationHolder,最后执行NotificationListenerWrapper的onNotificationPosted()方法:

       最终会通过Handler来发送消息来进行处理;

       onNotificationPosted(sbn, rankingMap)是在CarNotificationListener内部实现的;

       在CarNotificationListener内部会将StatusBarNotification封装成AlertEntry,然后执行notifyNotificationPosted():

       一步一步调用:

       先将alertEntry存入mActiveNotifications进行管理;然后执行发送NOTIFY_NOTIFICATION_POSTED消息;

       该Handler是通过setHandler来赋值的,具体是在什么地方呢?
       这个需要回到最前面里面了,前面说到NotificationViewController是在显示环节进行分析,轮到NotificationViewController登场了;

       NotificationViewController是在实例化,并执行enable()方法,先看一下构造方法:

       在构造方法内部,会传入CarNotificationView、CarNotificationListener、PreprocessingManager等实例,都是跟显示有关的核心类;
       1.CarNotificationView:负责处理通知显示;
       2.PreprocessingManager:负责管理通知,通过CarNotificationListener来获取通知;
       3.CarNotificationListener:跟NotificationManagerService间接交互的类;

       可以看到Handler是在NotificationViewController里面实现的,当有消息到来时,如果CarNotificationView显示时执行updateNotifications()来直接显示通知;不显示时执行resetNotifications()来对通知进行管理;

       以上就是Notification的整个工作过程,最后用一张流程图来总结一下:

❾ 如果要在Android的activity中显示一篇文章,这篇文章存储在哪里

那最好放在数据库中,可以是本地数据库,也可以是后台数据库,需要的时候去取

❿ 介绍android的英文文章

Android is an open-source software stack created for mobile phones and other devices. The Android Open Source Project (AOSP), led by Google, is tasked with the maintenance and further development of Android. Many device manufacturers have brought to market devices running Android, and they are readibly available around the world.

热点内容
网站在文件夹 发布:2025-03-06 20:51:46 浏览:112
阿玛尼行李箱密码锁如何换密码 发布:2025-03-06 20:46:02 浏览:102
xp共享文件夹win7无法访问 发布:2025-03-06 20:35:40 浏览:586
oracle存储过程excel 发布:2025-03-06 20:35:10 浏览:889
lay源码 发布:2025-03-06 20:25:29 浏览:751
专家系统原理与编程 发布:2025-03-06 20:21:05 浏览:641
脚本召唤暴龙 发布:2025-03-06 20:19:29 浏览:81
访问学者邀请函英文 发布:2025-03-06 20:18:06 浏览:381
安卓对方已振铃是什么意思 发布:2025-03-06 20:14:59 浏览:395
迅雷怎么设置存储卡 发布:2025-03-06 20:14:47 浏览:294