##### 呕心沥血总结了一篇tips!!!最近在做需求时,遇到需要在activity渲染完成后获取页面最终展示内容,并保存成图片至本地。第一种方式是截图,第二种是直接获取decorview的内容。综合考虑后决定采用第二种方式获取当前页面内容。
![file](http://image.openwrite.cn/29750_A983786B626A4A87ACBE49A61DFB5351)
#### 问题来了,在哪个时机获取当前绘制完成view内容呢?结合自己以及网络上的方法总结了如下几种方式,。分别对每种方式的做法、结果以及中间涉及到的原理做简要的归纳总结,目的是总结出tips让大家避坑。
##### 1、当前想到的是在Activity执行到onresume时调用view的post方法,post一个runnable到主线程,在runnable里面获取当前页面具体内容。这种方式也是最先想到的,但实际上测试结果,并没有拿到页面最终渲染后的内容,仅拿到布局背景图,而上层自定义view的内容没有拿到。这也强化了activity生命周期到onresume时,视图可见,但这里的可见,实际上并不是指view渲染完成这二者的区别。经过测试,在view的post方法里面,我们也看到仅仅拿到view的宽和高。
![file](http://image.openwrite.cn/29750_66975670CFE04F14A24359C6CBEDB9BB)
 深入看下底层的原理:ActivityThread中执行handleResumeActivity方法并在里面执行了activity的onResume方法,这片段的源码如下:
 ```
 public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
           String reason) {
               ...
           final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
           ...
           final Activity a = r.activity;
           ...
           final Activity a = r.activity;
           ...
           //获取Window也就是PhoneWindow
            r.window = r.activity.getWindow();
            //获取PhoneWindow中的DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
           ViewManager wm = a.getWindowManager();
           //获取PhoneWindow的参数
           WindowManager.LayoutParams l = r.window.getAttributes();
           a.mDecor = decor;
           l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
           l.softInputMode |= forwardBit;
           ...
             a.mWindowAdded = true;
             wm.addView(decor, l);
           ...
            Looper.myQueue().addIdleHandler(new Idler());
           }
 ```
在执行activity的onResume方法后,创建了ViewManager,然后拿到LayoutParams,最后通过addView方法把DecorView和LayoutParams加入ViewManager.ViewManager其实就是一个WindowManagerImpl对象.跟进代码里面可以看到,WindowManagerImpl 调用的addView方法又调用了mGlobal.addView()方法,mGlobal是个WindowManagerGlobal对象在成员变量中直接通过单例创建WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance()。最终的addView的代码:
```
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
                ...

WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

...

ViewRootImpl root;
             View panelParentView = null;

...
          //创建一个ViewRootImpl并设置参数
          root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);
            //保存传过来的view,ViewRootImpl,LayoutParams
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

...
            root.setView(view, wparams, panelParentView);
            ...
            }
```
最后其实是创建了ViewRootImpl,给传过来的DecorView置LayoutParams参数,然后放到对应的集合中缓存,最后调用root.setView方法将他们关联起来
```
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            ...
           requestLayout();  
            ...
           view.assignParent(this);
        }
```
看了这部分逻辑后,其实明确了在执行到activity的onResume方法时,只是将view的内容以及相关参数提交至DecorView,View内容的渲染并未正在开始

##### 2、onWindowFocusChanged函数时,focus为true时,获取当前页面内容。 结果是,获取到的当前内容并没有完全绘制完,不过只是这个回调方法时间点很接近了。
![file](http://image.openwrite.cn/29750_8B873DF0A715467EA5683443B807D9B3)
具体原因是:在方法一的分析流程里面,可以看到在setView()方法里通过 requestLayout - scheduleTraversals 向 Choreographer 请求安排绘制任务。Choreographer收到VSYNC信号回调到ViewRootImpl的performTraversals对DecorView进行measure、layout、draw等view的绘制。requestLayout 之后具体的逻辑就是向WMS通过binder发起添加window的过程,WMS完成操作后会把windowFocusChanged的事件回调给应用进程,ViewRootImpl在把该事件分发给DecorView,而DecorView重载了View的 onWindowFocusChanged 方法,内部最终将消息通过接口回传给了Activity的onWindowFocusChanged。也就是当activity中收到了windowFocusChanged的方法回调时,表明view已经提交了绘制的步骤。回调onWindowFocusChanged 和执行Traversals之间是有先后顺序的,进程间通信通过子线程发消息到主线程,scheduleTraversals会向主线程消息队列插入一个屏障消息,并且在 performTraversals时才会移除该消息,期间所有抛向队列的同步消息都被阻塞,包括 windowFocusChanged 事件,所以focusChanged相对于讲在后面才被执行。
```
@UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            // 注释2
            mTraversalScheduled = true;
            // 注释3
            // 插入同步屏障syncBarrier到消息队列,挡住普通的同步消息,优先执行异步消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 注释4
            
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            ...
        }
    }

```

##### 3、对view添加onDrawListener,判断Decorview执行了OnDraw则认为第一帧绘制完成。onDraw执行完成时View并没有真正渲染完成,并且发现onDraw方法会回调多次。

![file](http://image.openwrite.cn/29750_671A9DF591434D5FAD62685E2E64CE02)
通过日志可以看到,在o'n
onDrawListener里面的onDraw方法是会回调多次的,且早于自定义View的onDraw方法,所以在onDrawlistener回调的onDraw方法去获取页面内容,也不合理。
![file](http://image.openwrite.cn/29750_5E1B271C939142B7979EACB84833E6B8)
##### 4、View执行完成onDraw后post个Message,当执行Message时认为第一帧绘制结束。
在最后一个渲染的view里面的onDraw方法里面去post一个message,这种办法验证是可以的。但其实并不是很准确,严格意义讲很难获取到页面绘制完成的准确时间,因为在view的oDraw方法执行完成后,所需要的资源提交到surfaceflinger等系统服务进行合成,这中间的时间耗时其实也是有的,在不同的机型上有所差异。
##### 5、DecorView添加一像素的View,在onDraw函数里监听下一个vsync事件认为渲染完成。这个方法参考了网上的解法,理由是该View是DecorView的最后一个子View, 因为安卓是深度优先递归measure、layout、draw,所以该View是最后一个执行onDraw函数。
这种方法和方法四类似,正常业务开发中写这样的代码(骚操作)会显得逻辑比较奇怪。

#### 参考文章:
https://wanandroid.com/wenda/show/12175  
https://zhuanlan.zhihu.com/p/194351632  
https://codeantenna.com/a/MEGo21sNJQ  
https://blog.csdn.net/my_csdnboke/article/details/106685736
[]()

android 如何获全屏幕view内容相关推荐

  1. android底部滑出view,Android CoordinatorLayout与NestedScrollView基于Behavior几行代码实现底部View滑入滑出...

    Android CoordinatorLayout与NestedScrollView基于Behavior几行代码实现底部View滑入滑出 在CoordinatorLayout的Behavior出现之前 ...

  2. android标尺自定义view,android尺子的自定义view——RulerView详解

    项目中用到自定义尺子的样式: 原效果为 因为跟自己要使用的view稍有不同 所以做了一些修改,修改的注释都放在代码中了,特此记录一下. 首先是一个自定义View: public class RuleV ...

  3. 系出名门Android(7) - 控件(View)之ZoomControls, Include, VideoView, WebView, RatingBar, Tab

    [索引页] [×××] 系出名门Android(7) - 控件(View)之ZoomControls, Include, VideoView, WebView, RatingBar, Tab, Spi ...

  4. android动画view上移,在Android开发中使用View制作一个引导动画

    在Android开发中使用View制作一个引导动画 发布时间:2020-11-20 16:46:16 来源:亿速云 阅读:98 作者:Leah 这篇文章将为大家详细讲解有关在Android开发中使用V ...

  5. android 控件总结,Android制霸控件View总结

    关于Android View控件 Android中控件大致被分为两类ViewGroup,View.ViewGroup作为容器管理View.Android视图,是类似于Dom树的架构.父视图负责测量定位 ...

  6. 系出名门Android(9) - 数据库支持(SQLite), 内容提供器(ContentProvider)

    [索引页] [×××] 系出名门Android(9) - 数据库支持(SQLite), 内容提供器(ContentProvider) 作者:webabcd 介绍 在 Android 中使用 SQLit ...

  7. 【转】高手速成android开源项目【View篇】

    主要介绍那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Gallery.GridView.ImageView.ProgressBar及其他如Dialo ...

  8. 高手速成android开源项目【View篇】(转)

    主要介绍那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Gallery.GridView.ImageView.ProgressBar及其他如Dialo ...

  9. 转:高手速成android开源项目【View篇】 .

    主要介绍那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Gallery.GridView.ImageView.ProgressBar及其他如Dialo ...

最新文章

  1. 【Flutter】Flutter 混合开发 ( Flutter 与 Native 通信 | Android 端实现 EventChannel 通信 )
  2. python重启程序代码_重启python程序
  3. id选择器、标签选择器、类选择器、交集选择器、并集选择器
  4. html怎么保存曲奇,自制曲奇饼能保存多久 这些存放方法你懂吗
  5. 炫酷科技感超前的电子产品发布广告海报psd分层模板,带给你炫酷的未来感
  6. 领域搜索算法java_多起点的局部搜索算法(multi-start local search)解决TSP问题(附Java代码及注释)...
  7. Linux新手入门教程
  8. javaWeb—9.Git
  9. 计算机改名字sql2008不能登录,Win7电脑修改计算机名称后SQL2008数据库无法登录提示无法连接到load怎么处理...
  10. “PE文件格式”1.9版 完整译文(附注释)
  11. 怎样将PPT文件进行压缩?这几步很简单
  12. Leetcode 1774. Closest Dessert Cost 枚举法 vector 中的insert方法
  13. 怎么设置服务器文件夹多权限管理,如何设置共享文件夹,小编告诉你如何设置共享文件夹权限...
  14. ceil在c 语言中的用法,在C中实现ceil()
  15. 假如编程是魔法之零基础看得懂的Python入门教程
  16. 分支限界法求解电路布线问题
  17. 驻极体麦克前置放大器
  18. 中华网游戏集团日均收入达11.8万美元
  19. 基于JAVA社区医疗服务管理系统计算机毕业设计源码+系统+mysql数据库+lw文档+部署
  20. 在H5中使用腾讯地图,实现定位,距离计算,实时搜索,地址逆解析

热门文章

  1. springboot实现转发和重定向
  2. 哔哩哔哩:JS 异步笔试题
  3. mysql计算余弦相似度_余弦相似度公式及推导案例
  4. iPhone4翻新机愈演愈烈:回收价格高达4400元
  5. linux怎么更新python环境_linux如何升级python
  6. 什么是总资产(亿)?
  7. vscode 更改 gopath
  8. Android逆向入门7——Smali语法学习(1)
  9. JVM HeapSize Permsize
  10. 创意提现APP(NABCD)