Android进阶 (布局绘制流程 二 setContentView源码解读) v1.0

Android进阶 布局绘制流程 一 setContentView源码解读

本文承接上文 view 绘制流程 一。上文跟随源码分析了 setContentVIew() 后的部分 关于 PhoneWindow 和 Layoutinfate 部分的源码解析。该篇文章本人将继续学习和总结 ,并简单提及到经典面试题。本文大概阅读时间:30分钟 Andorid SDK :28.0 。

上文 分析道 PhoneWindow 的 setContentView 进行了分部解读

@Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            // view绘制流程 一 第一节
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // view 绘制流程 一  第四节
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }

        // 该篇我们顺序从该方法继续研究学习。!!!!!!

        mContentParent.requestApplyInsets();

        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

我们正式开始!

ViewRootImpl

// view 类

public void requestApplyInsets() {
    requestFitSystemWindows();
}

public void requestFitSystemWindows() {
    if (mParent != null) {
        mParent.requestFitSystemWindows();
    }
}

这里的mParent是ViewParent的具体实现ViewRootImpl,所以调用的是ViewRootImpl里的requestFitSystemWindows方法。


// ViewRootImpl 类

@Override
    public void requestFitSystemWindows() {
        // 检查线程   是否是在主线程
        checkThread();
        mApplyInsetsRequested = true;
        // 视图绘制
        scheduleTraversals();
    }

ViewRootImpl.scheduleTraversals()


void scheduleTraversals() {
        // 没有正在绘制的 任务才能进入
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 发送一个消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            ...
        }
    }

// Runnable实现
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");
            }
            // 最重要的地方 执行view 的绘制流程
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

performTraversals()

private void performTraversals() {
    ...
    // 第一次加载View,需要调整窗口大小,需要适应系统窗口,视图显示状态改变,
    // 视图布局参数不为空,强制窗口重新布局。才可能执行测量
    if (mFirst || windowShouldResize || insetsChanged ||
            viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
       
        ...
        // 窗口没有停止,或者通知需要绘制
        if (!mStopped || mReportNextDraw) {
            ...
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                    || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                    updatedConfiguration) {
                ...
                // 1.第一步:测量
                // Ask host how big it wants to be
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                ...
            }
        }
    } else {
        ...
    }
    ...
    
    if (didLayout) {// 执行布局
        // 2.第二步:布局
        performLayout(lp, mWidth, mHeight);
        ...
    }
    ...
    
    // 如果没有取消绘制,并且不是新的Surface,那么执行绘制
    if (!cancelDraw && !newSurface) {
        ...
        // 3.第三步:绘制
        performDraw();
    } else {// 如果取消了绘制或者是新的Surface,那么要重新测量、布局和绘制
        ...
    }
    mIsInTraversal = false;
}

重要的方法就是在上述的代码中 通过 performMeasure(), performLayout() ,performDraw() 三个方法,方法内也分别调用了 View的 measure layout 和 draw 方法。这也印证了我们 在编写自定义View的时候 需要默认实现的三个方法的由来,也说明了三个方法的顺序。

View 绘制相关面试题

事件分发

前边的文章已经提到过,这里就放两张图片,一图胜千言
触摸事件分发 流程图参考文章

触摸事件分发 流程图

硬件到Android 触摸事件

硬件到Android 触摸事件 参考文章

Dialog 和 Activity 点击事件问题

案例:当Dialog 现实的时候点击 Activity 部分按钮是没有反映的,为什么呢?

我们先看下 Dialog的初始化源码

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == ResourceId.ID_NULL) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        //新建PhoneWindow
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        //将回调注入到window当中
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mListenersHandler = new ListenersHandler(this);
    }

这里创建好 phoneWindow 后setContentView() 设置布局,这里就绕到了 上篇文章的 源码顺序中,通过 installDecor() ,创建 DecorView 将我们传入的布局id 带入,通过 xml pull 方式迭代读取 view 最后返回一个view 对象,并将其添加到 DecorView中。

public void show() {
        ...
        mWindowManager.addView(mDecor, l);
        ...
    }

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
             ...
             //创建ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            try {
                // setView
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                ...
            }
        }
    }

Dialog 通过show() 的方式,将DecorView 传给 WindowManager实现类 。 经过底层会调用到ViewRootImpl的ViewPostImeInputStage里面来。但是因为ViewRootImpl存在着多个,InputManagerService 会根据Z轴的高度,获取最近一个窗口,然后执行对应ViewRootImpl里面ViewPostImeInputStage的监听方法->执行mView.dispatchPointerEvent(event)。所以点击事件便在最上层 的Dialog中消费了。

绘制卡顿源码追踪

由于这里 源码层级更多,不是一篇两篇能够说清楚的,推荐一个大佬的 博文

app卡顿 16ms vsync 的由来

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,487评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,621评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,611评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,659评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,509评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,380评论 1 274
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,799评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,443评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,739评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,781评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,554评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,400评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,811评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,043评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,330评论 1 253
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,775评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,978评论 2 337

推荐阅读更多精彩内容

  • 在中国的传统文化中,提起商人,首先想到的是无利不起早,无商不奸的这类词语。而江苏德鹏空调设备有限公司的朱炳江总经理...
    健康就是太阳阅读 880评论 2 2
  • 用了好几次了,每次用到还是要查找一下,决定自己记录一下当作笔记
    EmptyWalker阅读 3,645评论 7 1
  • 加入简书已经快三年了,可是现在自己的简书钻却寥寥无几,关注我的人也屈指可数。别说收到点赞,就连阅读量也少得可怜。 ...
    小清慧阅读 1,565评论 5 8
  • 不知为何想去随笔 也许是因为思念你 人漫无目的的聚散 渐渐地吞噬了世界 亦如互相追...
    顺子叔叔阅读 187评论 0 0