16.源码阅读(View的绘制-android api-26)
今天带着一个问题来看Android View的绘制流程
View的绘制入口在哪?
很多时候,在进入到一个页面的时候,会需要动态的获取到布局中某一个view的宽度或者高度,但是我们发现如果直接在onCreate方法或者onResume方法中通过这种方式去取高度值得到的是0
int measuredHeight = mTextView.getMeasuredHeight();
而调用post方法才可以得到正确的值
mTextView.post(new Runnable() { @Override public void run() { int measuredHeight1 = mTextView.getMeasuredHeight(); System.out.println("post measuredHeight:"+measuredHeight1); } });
所以回到我们的第一个问题,view的绘制入口在哪里,只有view绘制完成经过测量才能得到宽高值,是否在onCreate和onResume方法中,view还没有完成绘制测量呢?
在Activity的启动流程中已经了解到,最终要启动Activity并且开始执行Activity生命周期的位置是在ActivityThread的handleLaunchActivity中,我们直接来到这里看,对Activity启动流程不熟悉可以看另一篇https://www.jianshu.com/p/bd5208574430
handleLaunchActivity
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) { ...... //这里会回调onCreate方法 Activity a = performLaunchActivity(r, customIntent); if (a != null) { r.createdConfig = new Configuration(mConfiguration); reportSizeConfigurations(r); Bundle oldState = r.state; //这里会回调onResume方法 handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason); if (!r.activity.mFinished && r.startsNotResumed) { // The activity manager actually wants this one to start out paused, because it // needs to be visible but isn't in the foreground. We accomplish this by going // through the normal startup (because activities expect to go through onResume() // the first time they run, before their window is displayed), and then pausing it. // However, in this case we do -not- need to do the full pause cycle (of freezing // and such) because the activity manager assumes it can just retain the current // state it has. performPauseActivityIfNeeded(r, reason); // We need to keep around the original state, in case we need to be created again. // But we only do this for pre-Honeycomb apps, which always save their state when // pausing, so we can not have them save their state when restarting from a paused // state. For HC and later, we want to (and can) let the state be saved as the // normal part of stopping the activity. if (r.isPreHoneycomb()) { r.state = oldState; } } } else { //如果启动Activity失败,交给ActivityManager处理 // If there was an error, for any reason, tell the activity manager to stop us. try { ActivityManager.getService() .finishActivity(r.token, Activity.RESULT_CANCELED, null, Activity.DONT_FINISH_TASK_WITH_ACTIVITY); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } }
performLaunchActivity
ClassLoader加载出Activity后执行了callActivityOnCreate,会回调onCreate方法
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ...... Activity activity = null; try { java.lang.ClassLoader cl = appContext.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); ...... activity.mCalled = false; if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); } return activity; } public void callActivityOnCreate(Activity activity, Bundle icicle) { prePerformCreate(activity); //调用activity中的performCreate,onCreate方法就在里面 activity.performCreate(icicle); postPerformCreate(activity); }
可以看到,activity创建出来后就开始回调onCreate,而onCreate中做了什么呢,调用了setContentView方法加载我们的布局文件,setContentView源码分析https://www.jianshu.com/p/2f87ebe77f4e
从源码中可以看到,setContentView做了以下的操作:
1.new了一个DecorView
2.加载了一个id为android.R.id.content的布局并把它加入DecorView
3.我们设置的布局文件被加载到id为android.R.id.content的ViewGroup中
也就是说,setContentView后只是加载了布局,但是还没有进行测量绘制,那么在onResum方法调用时是否完成了测量绘制呢?
performLaunchActivity之后系统开始执行handleResumeActivity
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { .... //回调onResum方法 r = performResumeActivity(token, clearHide, reason); if (r != null) { ...... //获取到WindowManager,将包含了我们我们自己的布局的 //DecorView加入到WindowManager中(ViewManager是一个 //接口,WindowManager实现了它) if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (r.mPreserveWindow) { a.mWindowAdded = true; r.mPreserveWindow = false; // Normally the ViewRoot sets up callbacks with the Activity // in addView->ViewRootImpl#setView. If we are instead reusing // the decor view we have to notify the view root that the // callbacks may have changed. ViewRootImpl impl = decor.getViewRootImpl(); if (impl != null) { impl.notifyChildRebuilt(); } } //如果Activity已经可见了,就将DecorView加入Window if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); } } ...... // The window is now visible if it has been added, we are not // simply finishing, and we are not starting another activity. if (!r.activity.mFinished && willBeVisible ...... WindowManager.LayoutParams l = r.window.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != forwardBit) { l.softInputMode = (l.softInputMode & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) | forwardBit; if (r.activity.mVisibleFromClient) { ViewManager wm = a.getWindowManager(); View decor = r.window.getDecorView(); //window可见之后,开始真正的绘制view了,这个方法很关键 wm.updateViewLayout(decor, l); } } ...... r.onlyLocalRequest = false; // Tell the activity manager we have resumed. if (reallyResume) { try { ActivityManager.getService().activityResumed(token); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } } }
也就是说在onCreate和onResume方法之心的时候,还没有完成View的测量,所以在这两个方法中无法得到实际的高度,具体绘制的代码先不看,我们看看为什么post方法中可以得到高度
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Postpone the runnable until we know on which thread it needs to run. // Assume that the runnable will be successfully placed after attach. //把Runnable加入了队列 getRunQueue().post(action); return true; } public void post(Runnable action) { postDelayed(action, 0); } public void postDelayed(Runnable action, long delayMillis) { final HandlerAction handlerAction = new HandlerAction(action, delayMillis); //看得出这是一个数组实现的队列,加入队列后也没有执行 //那么是什么时候执行的 synchronized (this) { if (mActions == null) { mActions = new HandlerAction[4]; } mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); mCount++; } } public static <T> T[] append(T[] array, int currentSize, T element) { assert currentSize <= array.length; if (currentSize + 1 > array.length) { T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(), growSize(currentSize)); System.arraycopy(array, 0, newArray, 0, currentSize); array = newArray; } array[currentSize] = element; return array; }
dispatchAttachToWindow,这个队列是在这个方法中执行的,后边我们会找到这个方法执行的时机,然后就会知道为什么这个方法中执行就可以得到高度了
void dispatchAttachedToWindow(AttachInfo info, int visibility) { ...... if (mRunQueue != null) { mRunQueue.executeActions(info.mHandler); mRunQueue = null; } performCollectViewAttributes(mAttachInfo, visibility); onAttachedToWindow(); ...... }
我们再回到上边ViewManager的addView和updateViewLayout方法,ViewManager和WindowManager都是接口,找到它的实现类WindowManagerImpl(可以通过PhoneWindow找到WindowManager的实现类是他)
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); } @Override public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.updateViewLayout(view, params); }
看一下WindowManagerGlobal这个类中做了什么,这里创建了一个ViewRootImpl,并把DecorView加入了这个View中
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ...... ViewRootImpl root; View panelParentView = null; ...... root = new ViewRootImpl(view.getContext(), display); ...... // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { } } } //这个方法是给view设置参数的,所以关键还是在addView方法 public void updateViewLayout(View view, ViewGroup.LayoutParams params) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; view.setLayoutParams(wparams); synchronized (mLock) { int index = findViewLocked(view, true); ViewRootImpl root = mRoots.get(index); mParams.remove(index); mParams.add(index, wparams); root.setLayoutParams(wparams, false); } }
setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { ...... requestLayout(); } }
这里又来到了曾经在invalidate方法中看到的源码,刷新view
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( //执行这个Runnable Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }
private void performTraversals() { ....省略大量代码 // Ask host how big it wants to be 回调到measure了 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); .... //回调到layout了 if (didLayout) { performLayout(lp, mWidth, mHeight); } ...... //回调draw了 performDraw(); }
总结一下:
1.onCreate和onResume方法调用时还没有进行view的测量
2.onResume方法调用之后将包含着当前布局文件的DecorView交给了WindowManager(实现类WindowManagerImpl)处理
3.WindowManager中将DecorView设置到ViewRootImpl中,在ViewRootImpl中调用requestLayout开始刷新View,执行measure layout 和 draw方法
4.post方法中能得到高度是因为post将runnable加入队列,在view attachToWindow时后才去获取高度,此时view已经完成了测量
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
【Android】解决微信调起支付接口没反应,调不起来微信的问题
原文: 【Android】解决微信调起支付接口没反应,调不起来微信的问题 //#前言 吐槽一下,微信支付的sdk真难用,文档混乱,坑不少。 正文:可能引起这种情况的问题 1. 最不能出现的 你的APPID和商户号写错了。appid是你open.weixin.qq.com里你设置的应用的id。 2. 最可能出现的 你应用的签名弄错了,这里坑比较多。 * 当你直接用AndroidStudio调试,在手机上直接Run的时候,用给的那个签名软件,获取到签名更新到你应用信息的这里: * 当你在另外一台电脑上调试的时候,你的应用的签名其实是会改变的,所以用那个签名软件,获取到签名后,再次更新到你的应用的信息里。 * 当你的APP开发的差不多了,需要发布(Generate Signed APK)时,先安装到一台手机上,然后获取签名,再次更新到你的应用的信息里。 3. 也有很大可能出现的 其实这个具体是怎么引起的我也不知道,有时候你重启下手机或者清理下微信的缓存,就会发现,竟然能调起来微信了。对,就是这么神奇!! 可能对你有些帮助的 在下面的代码中通过查看request.checkArgs())和ap...
- 下一篇
Android 中的线程池
为什么使用线程池? 1、重用线程,防止频繁的创建销毁线程所带来的时间和资源等性能损耗。 2、有效的控制最大线程并发数,防止大量线程抢夺系统资源引起卡顿,合理利用系统资源。 3、对线程进行简单管理、以及线程间更好的协作工作 Android 中有哪几种线程池? 实际意义上我们所说的几种常用线程池都是 Java 封装好,都在 Executors 这个工厂类里面了,笔者使用的是 JDK8 所以发现里面有六种线程池,接下来分别介绍下 1. FixedThreadPool 数量固定的核心线程池,当线程池处于空闲时线程也不会被回收,除非线程被关闭了。如果所有线程都在运行中,再有新任务添加时新任务会处于等在状态,知道有线程空闲在运行。处于等待的任务没有数量上限 2. CachedThreadPool 这是一种线程数量不定的线程池,他只有非核心线程,线程池如果没有空闲线程,会随时创建新的线程来工作,如果空下来的线程空闲时长超过 60 秒,则会被回收。他在长时间不工作的时候内部是没有任何线程的,也就是不消耗任何资源 3. SingleThreadExecutor 只有一个核心线程的线程池,所有任务都要排队...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS7设置SWAP分区,小内存服务器的救世主