android 如何获全屏幕view内容

呕心沥血总结了一篇tips!!!最近在做需求时,遇到需要在activity渲染完成后获取页面最终展示内容,并保存成图片至本地。第一种方式是截图,第二种是直接获取decorview的内容。综合考虑后决定采用第二种方式获取当前页面内容。

activity_view_structure.png

问题来了,在哪个时机获取当前绘制完成view内容呢?结合自己以及网络上的方法总结了如下几种方式,。分别对每种方式的做法、结果以及中间涉及到的原理做简要的归纳总结,目的是总结出tips让大家避坑。
1、当前想到的是在Activity执行到onresume时调用view的post方法,post一个runnable到主线程,在runnable里面获取当前页面具体内容。这种方式也是最先想到的,但实际上测试结果,并没有拿到页面最终渲染后的内容,仅拿到布局背景图,而上层自定义view的内容没有拿到。这也强化了activity生命周期到onresume时,视图可见,但这里的可见,实际上并不是指view渲染完成这二者的区别。经过测试,在view的post方法里面,我们也看到仅仅拿到view的宽和高。

activity_lifecycle.png
深入看下底层的原理: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时,获取当前页面内容。 结果是,获取到的当前内容并没有完全绘制完,不过只是这个回调方法时间点很接近了。

view绘制的流程
具体原因是:在方法一的分析流程里面,可以看到在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方法会回调多次。

日志打印时间
通过日志可以看到,在o’n onDrawListener里面的onDraw方法是会回调多次的,且早于自定义View的onDraw方法,所以在onDrawlistener回调的onDraw方法去获取页面内容,也不合理。 refresh_process.png

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函数。

这种方法和方法四类似,正常业务开发中写这样的代码(骚操作)会显得逻辑比较奇怪。

文末

要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

在这里插入图片描述

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

在这里插入图片描述

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

在这里插入图片描述

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

在这里插入图片描述

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
在这里插入图片描述

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

在这里插入图片描述

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

在这里插入图片描述

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

在这里插入图片描述

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取【保证100%免费】↓↓↓
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Android 上实现屏十字线,你可以使用自定义视图和触摸事件来实现。 首先,创建一个自定义视图类,并在其中实现 `onDraw()` 方法来绘制十字线。在这个方法中,你可以使用 `Canvas` 来绘制十字线。 接下来,你需要实现触摸事件处理。在自定义视图中,你可以重写 `onTouchEvent()` 方法来处理触摸事件。在这个方法中,你可以获取触摸点的坐标,并在视图中绘制十字线。 以下是一个简单的示例代码,演示如何在自定义视图中绘制屏十字线: ```java public class CrosshairView extends View { private Paint crosshairPaint; private float touchX; private float touchY; public CrosshairView(Context context) { super(context); init(); } public CrosshairView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { crosshairPaint = new Paint(); crosshairPaint.setColor(Color.RED); crosshairPaint.setStrokeWidth(2f); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 绘制横线 canvas.drawLine(0, touchY, getWidth(), touchY, crosshairPaint); // 绘制竖线 canvas.drawLine(touchX, 0, touchX, getHeight(), crosshairPaint); } @Override public boolean onTouchEvent(MotionEvent event) { touchX = event.getX(); touchY = event.getY(); invalidate(); return true; } } ``` 你可以在布局文件中添加这个自定义视图,然后在代码中设置它为屏大小。例如: ```xml <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.CrosshairView android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> ``` 在你的 Activity 类中,使用下面的代码来设置视图为屏大小: ```java getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_main); ``` 现在,当用户在屏幕上触摸滑动时,就会出现一个十字线。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值