安卓视图层级大揭秘
最近接了一个语音控制的功能,UI上的具体实现就是在应用上遮盖一个透明防触层,在语音状态下阻止用户点击,但不能影响物理返回键的Dialog呼出即控制,同时对于非物理返回键呼出的Dialog也要阻止操作。功能看起来很绕,我们用一张图片来具体说明一下。
创新互联长期为数千家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为莫力达企业提供专业的成都网站设计、成都做网站,莫力达网站改版等技术服务。拥有十余年丰富建站经验和众多成功案例,为您定制开发。
通过图片不难看出,我们要实现的语音控制层其实是介于应用视图与视图内部提示框之上,同时又在Back返回键弹窗之下的一个层级。因为一直以来对安卓视图层级的探究不是很深入,所以借着做这个功能对安卓视图层级这一块的知识进行了一下总结梳理。
首先让我们通过一张层级图来明确几个重要的概念Window,DecorView和mContentParent。
在Android中不管是Activity、Toast、ActionBar还是Dialog,他们的视图都是附加到Window上,其实基本上所有的view同时通过Window来呈现的,因此Window可以理解为是view的承载者和管理者。Window 有三种类型,分别是应用 Window、子 Window 和系统 Window。应用类 Window 对应一个 Acitivity,子 Window 不能单独存在,需要依附在特定的父 Window 中,比如常见的一些 Dialog 就是一个子 Window。系统 Window是需要声明权限才能创建的 Window,比如 Toast 和系统状态栏都是系统 Window。
DecorView是Windows中的View的最顶层View。其实DecorView是FrameLayout的子类,它里面包含了一个存有ActionBar以及mContentParent的LinearLayout。
mContentParent这个名字可能会有些陌生,其实他就是我们经常使用的应用根布局,即android.R.id.content。Activity中的setContentView其实就是通过LayoutInflater将XML布局转换成View并添加到mContentParent中。
每个Activity都会持有一个Window,而在安卓中,Window只有唯一的一个实现类PhoneWindow ,所以每个Activity都会持有一个PhoneWindow,在PhoneWindow中会持有顶层视图DecorView。那么Activity是怎么建立与PhoneWindow的联系的呢,让我们通过源码来探究一下:
在Activity的启动过程中会执行ActivityThread的performLaunchActivity方法,其中调用Activity的attach。在attach()方法中实例化Activity持有的mWindow。由于 Activity 实现了 Window 的 Callback 接口,因此当 Window 接受到外界的状态改变时就会回调 Activity 的方法。
可以看到,在PhoneWindow里面,出现了成员变量DecorView。而这里,DecorView则是PhoneWindow里面的一个内部类,它是继承于FrameLayout。
这是我们每次写Activity都会调用的setContentView方法,它的内部调用了getWindow()的setContentView,这个mWindow就是PhoneWindow。
我们看到在PhoneWindow中有三个setContentView的重载方法。在setContentView(int layoutResID)中,首先判断了mContentParent ,如果mContentParent 为空即为第一次调用的时候,就执行installDecor()方法,创建DecorView,并添加到mContentParent上。如果mContentParent不为空,那么将mContentParent中的view移除。接着通过mLayoutInflater将XML转换为View树,并且添加至mContentParent视图中。 添加完成后回调通知onContentChanged,表示完成界面加载。
首先判断mDecor是否为空,如果为空则通过generateDecor创建一个DecorView,紧接着设置DecorView的获取焦点能力为FOCUS_AFTER_DESCENDANTS,即先分发给Child View进行处理,如果所有的Child View都没有处理,则自己再处理。第一次DecorView未加载到mContentParent,所以mContentParent为空,调用generateLayout将setContentView内容添加到mContentParent。
定制过Acitivity的Actionbar或是Fullscreen的同学一定都知道,requesetFeature方法需要在setContentView之前调用,这就是原因。setContentView的实质显示是触发了Activity的resume状态,也就是触发了makeVisible方法。
这里将getWindow().getAttributes()作为了LayoutParams,在WindowManager中:
可以看到Activity的窗口类型是TYPE_APPLICATION,这个TYPE类型决定了在Window层的显示层级,TYPE类型总览如下:
Dialog不属于View,他是应用的子window,所以这也是为什么我们通过给mContentParent添加view无法实现遮挡Dialog的原因。Dialog 中 Window 同样是通过 PolicyManager 的 makeNewWindow 方法来完成的,普通的 Dialog 必须采用 Activity 的 Context,如果采用 Application 的 Context 就会报错。这是因为没有应用 token 导致的,而应用 token 一般只有 Activity 拥有。常规Dialog的TYPE为TYPE_APPLICATION_ATTACHED_DIALOG,通过不同的TYPE层级划分我们可以找到置于常规Dialog之上的WindowManager LayoutParams 属性,例如TYPE_SYSTEM_ALERT与TYPE_TOAST,设置了这两个属性的布局是可以将常规Dialog完全遮盖的。他们的区别在于一个是系统级别的Dialog一个是Toast,系统Dialog需要申请权限。所以我们的第一个方案就是可遮挡的Dialog使用常规Dialog,语音提示框采用TYPE_SYSTEM_ALERT。但是都知道安卓有一个无法逃避的问题,就是厂商定制,在MUI的framework层,出于对“安全”的考虑,默认为用户关闭了悬浮窗权限,也就是是说设置了TYPE_SYSTEM_ALERT属性的视图默认是无法显示的,需要用户手动开启权限以后方可显示。
虽然可以在用户启动的时候根据用户机型选择跳转开启权限页,但作为一个有情怀的开发这种不完美的体验还是不能接受的。根据之前对安卓视图层级的学习,我们有了第二套方案。应用视图是存放于mContentParent他与Activity同属TYPE_APPLICATION Window层级属于最下层,常规Dialog的层级是TYPE_APPLICATION_ATTACHED_DIALOG,所以我们将常规Dialog作为最上层不可遮挡的提示框,下面只需考虑可遮挡的弹窗与语音控制两层即可。因为语音控制层需要能够遮挡提示弹窗,所以需要语音控制层在弹窗的上层,经过之前的学习,我们把弹窗加入到mContentParent,把语音控制层添加到DecorView层即可完美的解决问题。mContentParent为一个FrameLayout,应用视图通过sentContentView率先添加到mContentParent中,作为提示弹窗,添加顺序一定相对应用视图置后,所以当提示弹窗再次向mContentParent添加的时候,即会添加到应用视图之上。而DecorView是mContentParent的父容器,也是一个FrameLayout,添加语音提示框的时候mContentParent一定已经存在,所以添加的时候一定会在mContentParent之上。
就这样,一个看似复杂的需求通过对安卓源码的探究完美的解决了,很多时候当我们遇到难以解决的问题,不妨试试回到问题的原点,思考一下问题的本质,很多时候都会有不一样的发现。
Android app视图查看工具
由于android提供的uiautomatorviewer有许多信息我们无法看到,比如控件的id和类名,这些都是我们用来分析的重要因素。所以利用业余的时间我做了一个手机App视图查看工具,利用的是python的PyQt和Android注入去实现的,源码都在我的Git上。
注入项目:
python Windows界面:
安卓开发之在当前Activity获取视图View
一般来说,获取当前活动中的某一个视图还是很方便的,我们在使用onClick函数的时候经常会见到这样的用法:
我们注意到,make函数的第一个参数是v,这可以是当前布局的任意一个View,Snackbar会使用这个View来找到最外层的布局从而展示Snackbar。但是我们有的时候并不是使用onClick函数来调用Snackbar。例如我们会在onOptionsItemSelected函数中使用Snackbar。此时,我们可以借用下列方法获取View视图:
getWindow().getDecorView().findViewById(Android.R.id.content)
就上述例子来说,可以修改为这样:
就是这样啦,蟹蟹大家的阅读!
android 视图变黑
1、解决android视图变黑问题的最简单方法是可以通过同时按住“主页”和“电源”按钮10秒钟,然后释放两个按钮并按住“电源”按钮直到屏幕打开来硬重启android设备。
2、也可以等待android在电池耗尽时自动关闭。然后为android充电,然后按“电源”按钮将其打开。
Android进阶 - 视图层级实时分析
在App运行过程中,我们的视图层级可能会由于用户的操作一直在发生改变,甚至可能会有一些出乎预料的变化,本文将会介绍 如何进行Android视图实时分析,分析View的视图层级及属性变化。
首先,笔者先来一个简单的Demo实例。我们使用Android Studio新建一个Empty Android工程,跑一下程序,界面如下图所示:
接下来,我们要对视图层级进行分析,但分析之前先给各位介绍两个视图分析工具。
1. Android SDK 中 tools 包下的 hierarchyviewer ,最终展现的视图效果如下:
2. Android Studio 也有自带的视图分析工具 Layout Inspector(布局检查器) ,打开方式如下图所示:
可以看到Layout Inspector最右侧的属性栏可以查看 每一个View的所附带的属性及属性值 。
从根视图开始分析视图层级,如下图所示:
DecorView的第一个子View(LinearLayout), 如下图所示:
DecorView的第二个子View(View),如下图所示:
DecorView的第三个子View(View),如下图所示:
至此,DecorView的最外层View全部分析完毕。
接下来,分析DecorView的第一个子View(LinearLayout),如下图所示:
ViewStub的属性信息,如下图所示:
FrameLayout的属性信息,如下图所示:
接下来,继续分析FrameLayout的子View,如下图所示:
ContentFrameLayout的视图属性,如下图所示:
ActionBarContainer的视图属性,如下图所示:
不过,还有个问题需要提醒一下, 不同机型,不同系统主题设置 生成的视图结构可能会不一样,举两个例子:
例一:笔者把使用的模拟器换成自己的手机(360N5 Android 6.0.1) ,运行后视图布局如下:
可以看到 笔者的手机是没有NavigationBar(底部导航栏)的 。
例二:笔者把Activity的主题"Theme.AppCompat.Light.DarkActionBar"换成无标题栏主题"Theme.AppCompat.Light.NoActionBar" ,运行后视图布局如下:
可以看到视图结构与我们之前分析的相比,发生了一些变化。
最后,还有个细节给各位补充下: Layout Inspector 只能分析出Android Studio当前 “正在运行的APP” 的视图布局结构,其他应用的视图布局结构是无法显示的。
如果我们想要分析一个第三方应用(如:微信、QQ)的视图结构可以使用 Android Device Monitor(安卓设备监视器) ,具体打开步骤如下图所示:
以QQ为例,我们先打开手机QQ,显示出QQ主界面,然后按照下图的 "红色圈选" ,依次点击,当前的视图结构就出来了,但是相比于 Layout Inspector 工具,视图属性信息提供的较少...
视图层级分析 到此结束,有时间再补篇源码,分析一下布局加载的流程。
写这篇文章的时候被IOS同事嘲讽了,它们吐槽Android的视图分析工具太渣,最后对比看了下,Android的视图分析工具确实没有IOS的高大上......╮(╯▽╰)╭
最后,秀一下IOS的视图分析工具 Reveal ,如下图所示:
Android自定义视图清空画布
执行方法:
默认情况下,视图会在onDraw前会清空画布内容,详细见ViewRootImpl中的drawSoftware方法:
但是如果在其他位置或使用其他方式获取Canvas时可能导致之前的内容还留在画布上,例如:
此时在绘制新的内容前需要先清空画布。
2020-11-16
分享名称:android的视图,app视图
文章网址:http://scpingwu.com/article/hoohjj.html