标签:安卓

安卓开发

你真的会用View的post方法吗

安卓中在非UI线程中需要更新界面的时候,我们往往使用Handler的post方法或者View的post方法(当然还可以使用Activity的runOnUiThread),当我们已经有一个view对象的时候往往会选择后者,即使用该view对象调用post方法。但是你真的了解View的post方法吗?有没有遇到过View的post的Runnable对象中的代码在某些机型上未被执行?遇到之后深入探究过是什么原因吗? 最近在开发智能无感知SDK的时候遇到过一个很奇怪的坑,这里简单交代下背景,用户点击登录按钮后智能无感知SDK会使用一个WebView去加载一个url,然后进行java代码和js代码的相互通信,在java代码端的onReady回调中会去执行一个js端的函数captchaVerify()进行智能无感知校验,这个WebView控件是使用一个自定义对话框来承载的,是通过在自定义对话框的onCreate方法中调用setContentView将该WebView对象添加到对话框中的。在进行智能无感知验证的时候不会将该对话框显示出来。也即不会调用show函数。只有当captchaVerify()验证失败才会show出对话框展示传统验证码。 java端和js端相互通信的最开始的代码大致如下: [crayon-5f03ad1e10855947772086/] 熟悉WebView的同学都知道在java代码和js代码进行通信时java端的回调函数都是运行在一个叫做JavaBridge的线程中的,所以onReady()中的代码是运行在非UI线程中的,而mCaptchaWebView.loadUrl方法会对界面进行更新,因此该操作必须post到主线程中去,所以最开始我使用了如上的一段代码(注:最开始的代码无注释2处和注释3处对应的打印log代码,这里为了减小篇幅,将后面才添加的排查问题的日志代码也直接放上去了),即当java端的onReady方法被js代码回调时如果是智能无感知类型会调用callCaptchaVerify方法,在该方法中调用js端的captchaVerify()方法。逻辑上是完全符合业务逻辑的,没任何问题。但是在实际测试时发现上述代码在三星SM-C7100机型(安卓版本为7.1.1)上运行ok,可以正确调用js端的captchaVerify()函数完成智能无感知验证,但是在360的QiKU手机青春版(安卓版本为5.1)上智能无感知始终不能通过。最开始以为是在360的奇酷手机上不能使用WebView的loadUrl来调用js端代码,但是改为使用evaluateJavascript方法还是不行,于是添加了注释2和注释3处的打印log的日志,发现在三星那款测试机和奇酷测试机上输入日志分别如下: [crayon-5f03ad1e1085c249451901/] 看到对比结果震惊了,日志表明在奇酷手机上post的Runnable对象中的代码根本没执行!!!单解决这个问题很容易,使用Activity的runOnUiThread方法替换View的post就行了,但是作为一个优秀的工程师,保持好奇心是很重要的,为何View的post方法在奇酷手机上就不能work呢?是360的ROM修改了什么还是其他原因呢?于是用安卓5.1系统的模拟器运行最开始的代码,结果发现还是不行,说明和厂商自定义ROM修改无关,这个锅不能让360来背,那就只有一个可能View的post方法在不同版本的SDK中实现细节不一样,然后去研究了下发现还真是这样,View的post方法在API24(7.0系统)以上(含)和以下的实现逻辑区别较大。更准确的说可以认为是在7.0以下View的post方法在实现逻辑上存在缺陷。在7.0系统谷歌修正了这个缺陷。 所以可以看到从安卓7.0 系统(API24)开始View#post还是比较好理解的,简单来说就是当该View被attach到某个Window过才会执行该View post的Runnable中的代码。如果调用post的时候该View已经附加到某个Window上,那么会将Runnable post到UI线程中,如果调用post的时候该View还未被添加到任何Window中,那么会将其存储起来,等待附加的时候执行。所以如果post某个Runnable,但是从未将该View添加到某个窗口中,那么该Runable中的代码将永远不会被执行。注意这和7.0以下的永远不被执行的情况不同,这种情况是符合常理的,因为可以认为一个不依附任何Window的View是没有意义的,此时它只是一个简单的java类,不应该具备视图的功能。 对于7.0以下系统:当调用post时如果该View已经attach到了某个Window上,此时和7.0系统处理逻辑是一样的;如果调用post的时候该View还未被添加到任何Window中,那么会将Runnable存储起来,等待下一次执行performTraversals时执行,如果post是在子线程中调用的,那么其方法体中的代码将永远不会被执行,如果是在UI线程中调用的那么会在下一次performTraversals时执行。所以在7.0以下系统一定要注意确保View被attach到某个Window之后,在调用其post方法,否则post的Runnable中的代码很可能不会执行,尤其是如果post在子线程中调用那么可以肯定永远不会执行。 当View被attach到某个Window上后,其效果等同于Handler#post,此时2者可以认为等价    

安卓开发

简单谈谈安卓中的窗口管理系统

安卓中的窗口管理系统是一个非常复杂的系统,涉及到的对象概念非常多,主要包括Activity,Window,DecorView,WindowManager,WindowManagerService等,这里不对窗口管理系统整体做分析,主要来看看Activity,Window,DecorView以及开发者在Activity中通过setContentView设置的xml文件中配置的ContentView(估且这么叫)之间的层级关系,因为这一块的内容既与开发相关又不太过于底层,但是又能帮助我们从宏观上了解安卓的窗口系统。 Window是Activity的一个成员,Window本身只是一个抽象类,具体的实现是PhoneWindow DecorView是PhoneWindow的一个内部类,本质是一个FrameLayout的ViewGroup ContentView是DecorView内部的一个子View,DecorView的子View除了ContentView外还包括与具体主题相关的标题栏等子View 开发者在xml中设置的界面会被添加到ContentView中作为子View 下面通过代码的方式来论证上述结论。 首先看看Activity,在Activity中有如下的代码片段: [crayon-5f03ad1e10da9307642707/] 从上述代码片段可以看到每一个Activity内部都持有一个mWindow的Window对象,然后我们看看Window的部分代码片段 [crayon-5f03ad1e10dad775057907/] 从这里可以看到Window仅仅只是一个抽象类而已,而且Activity中一些重要的方法实质上是通过调用Window类来实现的,如setContentView等,具体的实现是PhoneWindow类,我们看下PhoneWindow部分代码片段: [crayon-5f03ad1e10daf971443526/] 可以看到PhoneWindow内部持有一个DecorView对象,我们来看下DecorView的定义: [crayon-5f03ad1e10db0076581760/] 从这里可以看到DecorView本质是一个FrameLayout,即DecorView是一个ViewGroup容器。注意PhoneWindow不是View,PhoneWindow中关于View的操作很多都是通过DecorView来完成添加的。到这里Activity,Window,DecorView之间的层级关系就非常清楚了,但是开发者通过setContentView在xml文件中定义的界面与Activity,Window,DecorView之间关系是怎样的呢?这个就需要我们从setContentView的源码来分析了。首先看下Activity的setContentView函数 [crayon-5f03ad1e10db2318675628/] 从这里可以看到Activity的setContentView最终是通过PhoneWindow来完成的,因为getWindow()返回的是一个Window对象。所以我们接下来看下PhoneWindow的setContentView函数。 [crayon-5f03ad1e10db4820321179/] 注意这里的 installDecor()函数,这个函数非常重要,该函数做了两件非常重要的事,一个是DecorView的创建,另一个就是将android.R.id.content 的布局(本质为FrameLayout)与mContentParent关联起来。关于installDecor()函数的源码分析,大家可以参考这个,写的很不错:http://blog.csdn.net/yanbober/article/details/45970721/ 。注意另外一个很重要的语句 mLayoutInflater.inflate(layoutResID, mContentParent);该语句将id为layoutResID的布局文件,即开发者在xml中配置的文件,转化为View树然后添加到mContentParent中。 到这里Activity,Window,DecorView,ContentView,xml文件之间的层级关系就非常清楚了。即Window为Activity的成员,DecorView是PhoneWindow的内部类成员,ContentView是DecorView的子View,xml文件转化的View是ContentView的子View。了解它们之间的层级关系之后要获取xml文件中的根布局就很容易了。代码如下: [crayon-5f03ad1e10db6906939049/] 如果要获取根布局下的全部子View可以使用如下代码: [crayon-5f03ad1e10db8811072296/]   关于安卓窗口系统的详细讲解大家可以参看以下文章: http://blog.csdn.net/yanbober/article/details/45970721/ http://www.jianshu.com/p/851714f5e640 https://zhuanlan.zhihu.com/p/26834562