背景:
项目中UI层有SurfaceView,其渲染展示的是摄像机等采集画面,但是测试提了一个问题单,如果在当前页面中跳出到其他页面,会crash,经过log分析,是由于surfaceview 在失去焦点的时候会走到onDestroy方法,也就是surfaceview会失效。
解决思路:surfaceview不失去焦点就可以了,改用悬浮窗实现。

UI层 最主要的页面结构如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity">// 摄像机画面<SurfaceViewandroid:id="@+id/surfaceView"android:layout_width="match_parent"android:layout_height="match_parent" />//叠在摄像机画面上的fragment<FrameLayoutandroid:id="@+id/fragment_layout"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#20000000" />

页面底层是摄像机画面,叠在上面的是fragment,用 framelayout 作为容器去承载,surfaceview既然要作为悬浮窗中去展示,因为悬浮窗的层级比Activity页面高,所以fragment层页面当然也要放到悬浮窗,否则页面就没法操作了。

1、先检查悬浮窗权限。
       if (!Settings.canDrawOverlays(this)) {//启动权限页面startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 0);} else {//添加悬浮窗addWindowSurfaceView();}

2、创建悬浮窗。

// add window private void addWindowSurfaceView() {WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);mRootViewLayout = new RootViewLayout(this);mRootViewLayout.setOrientation(LinearLayout.VERTICAL);LayoutInflater.from(this).inflate(R.layout.activity_main, mRootViewLayout);WindowManager.LayoutParams layoutParams = createDefaultWindowLayoutParams();windowManager.addView(mRootViewLayout, layoutParams);}// 添加默认的悬浮窗参数private WindowManager.LayoutParams createDefaultWindowLayoutParams() {WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;layoutParams.height = ScreenUtils.getScreenHeight(this);layoutParams.width = ScreenUtils.getScreenWidth(this);return layoutParams;}

这块代码还是比较简单的,在跳到其他页面的时候,将悬浮窗设置成1x1 px的大小,再次回来又恢复成默认大小。

 // 退到后台private void updateWindowSufaceViewOnStop() {WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;layoutParams.height = 1;layoutParams.width = 1;//设置成不可获取焦点layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;windowManager.updateViewLayout(mRootViewLayout, layoutParams);}//再次回来private void updateWindowSufaceViewOnResume() {WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);WindowManager.LayoutParams layoutParams = createDefaultWindowLayoutParams();windowManager.updateViewLayout(mRootViewLayout, layoutParams);}

不过需要注意的是,在退到后台的时候,需要将虚浮窗设置成不可获取焦点

以为就这么简单就结束了?No,绝非那么simple,重点来了。

【问题一】run 一下项目,crash掉了,报如下的错误。

No view found for id 0x7f080088 (com.xxxxxx:id/fragment_layout) for fragment LauncherFragment{ab9831c #0 id=0x7f080088 LauncherFragment}at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1413)at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740)at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1809)at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:799)at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2580)

从日志中看,是没有找到承载fragment的layout的资源,我们知道fragment是依附于Activity的,所以这个view没有被找到,是不是用Activity上下文findviewbyId没有获取到?下面继续我一贯的源码分析,当然这次比较不贴很多代码~~.

FragmentManager中moveState方法中关键添加fragment 视图的代码如下:

           // 1、寻找装载fragment的容器container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);f.mContainer = container;// XX 省略了无用的代码// 2、创建fragmentViewf.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), container, f.mSavedFragmentState);// 3、添加到容器中if (container != null) {container.addView(f.mView);}
从这段源码以及日志中看到,container 是通过mContainer(Activity)去找的,日志报的错,说明findViewbyId没找到,我们去Activity中看下这个方法
  @Nullable@Overridepublic View onFindViewById(int id) {return Activity.this.findViewById(id);}/*** Finds a view that was identified by the id attribute from the XML that* was processed in {@link #onCreate}.** @return The view if found or null otherwise.*/@Nullablepublic View findViewById(@IdRes int id) {return getWindow().findViewById(id);}

从这段源码中,可以清楚的看到,是从Activity的setContentView中加载资源的,由于我们这里采用了悬浮窗,所以自然没有办法从中获取到资源,那怎么改?

既然我们知道了原因,那就好入手了啊,实现 override findViewById方法,然后从我们的悬浮窗根view中去加载。

 @Overridepublic <T extends View> T findViewById(int id) {return mRootViewLayout.findViewById(id);}

mRootViewLayout 就是悬浮窗根layout。

好了,第一个棘手的问题解决了,但是其他的问题又来了,我们现在的方案是悬浮窗,key 事件分发就不走Activity了,而我们按返回键本应该会弹出fragment的

【问题二】按返回键等,不走onBackPressed,fragment没有被弹出去。

原因我已经讲了,原生的back事件,是会经过Activity的onBackPressed方法,继而调用fragmentManager的popFragment方法,但是现在悬浮窗的层级在Activity之上,它优先获取到了焦点事件,key事件等都处理给它。

 /*** Called when the activity has detected the user's press of the back* key.  The default implementation simply finishes the current activity,* but you can override this to do whatever you want.*/public void onBackPressed() {if (mActionBar != null && mActionBar.collapseActionView()) {return;}//弹出fragmentif (!mFragments.getFragmentManager().popBackStackImmediate()) {finishAfterTransition();}}

那怎么解决?

写一个封装的layout,作为悬浮窗的根viewGroup。重写其dispatchKeyEvent方法,在这个方法中根据其keyCode进行判断处理。

  @Overridepublic boolean dispatchKeyEvent(KeyEvent event) {if(event.getAction()==KeyEvent.ACTION_DOWN && event.getKeyCode()==KeyEvent.KEYCODE_BACK){mFragmentManager.popBackStack();}return super.dispatchKeyEvent(event);}

搞定!,当然这里面还可以继续进行其他key事件判断。

心得:这个方案运行了几个版本,crash也解决了,也没有引入新的bug,所以遇到问题,我们要敢想思路和方案,遇到新方案带来的问题,我们要多思考,必要的时候,还是得多从源码入手。

项目中的一个技术方案替换历程(surfaceview+fragment 变成悬浮窗window)相关推荐

  1. 解构华为云HE2E项目中的容器技术应用

    本文分享自华为云社区<解构华为云HE2E项目中的容器技术应用>,作者: 敏捷小智. 华为云DevCloud HE2E DevOps实践当中,项目采用Docker技术进行构建部署. 容器技术 ...

  2. 万万没想到,一个技术方案帮实习生追到了运营妹子!

    上回说到,公司的新业务增长速度放缓,运营部门提出要发展短视频来促进更快的业务增长,而我也因为提前准备好了技术预案再一次得到老板的赞赏(了解详情请看上集:一个技术预案,让老板当场喊出了奥利给 ). 既然 ...

  3. 主数据项目中的历史数据切换方案

    前言:主数据项目中的历史数据切换方案需要结合应用演进策略和业务给出的切换需求,出具对应的切换方案. 历史数据切换注意点: 一.选择整体切换原则 需根据实际业务情况,制定历史数据切换原则 切换原则介绍 ...

  4. 最近实际项目中遇到的技术问题与解决思路

    最近实际项目中遇到的技术问题与解决思路 参考文章: (1)最近实际项目中遇到的技术问题与解决思路 (2)https://www.cnblogs.com/lunlunshiwo/p/9222456.ht ...

  5. Entity Framework 的小实例:在项目中添加一个实体类,并做插入操作

    Entity Framework 的小实例:在项目中添加一个实体类,并做插入操作 Entity Framework 的小实例:在项目中添加一个实体类,并做插入操作 1>. 创建一个控制台程序 2 ...

  6. php 中 t怎么打开,怎么在PHP项目中实现一个explort() 功能

    怎么在PHP项目中实现一个explort() 功能 发布时间:2020-12-28 16:36:06 来源:亿速云 阅读:108 作者:Leah 这篇文章给大家介绍怎么在PHP项目中实现一个explo ...

  7. php排序地区,怎么在php项目中实现一个地区分类排序算法

    怎么在php项目中实现一个地区分类排序算法 发布时间:2020-12-30 16:11:30 来源:亿速云 阅读:86 作者:Leah 怎么在php项目中实现一个地区分类排序算法?相信很多没有经验的人 ...

  8. php7开发的项目怎么样,如何在PHP7项目中搭建一个多线程

    如何在PHP7项目中搭建一个多线程 发布时间:2021-03-05 15:31:22 来源:亿速云 阅读:93 作者:Leah 这期内容当中小编将会给大家带来有关如何在PHP7项目中搭建一个多线程,文 ...

  9. vue 打开html流_在vue项目中添加一个html页面,开启本地服务器

    在vue项目里新增一个不需要登录的页面,那么我只能新增一个html页面了,不经过路由,直接在浏览器输入路径打开,那么就需要用到本地服务器, 1.vue里面的html页面最好放过在public文件夹里面 ...

最新文章

  1. ViewPager -- Fragment 切换卡顿 性能优化
  2. 面向新闻媒体的命名实体识别技术
  3. 监控系统安装配置文档(Nagios+Cacti+Nconf)
  4. mysql redo原子写_InnoDB如何保证redolog的完整性?
  5. Spring 3.2矩阵变量是什么? - 第1部分
  6. WCF RIA优缺点
  7. 周鸿祎:不得不说的话
  8. asp 退出登录修改cookie能进入后台_某logCMS的代码审计:越权到后台getshell
  9. Eclipse下Hibernate使用学习
  10. 黑色的cms商城网站后台管理模板——后台
  11. 同一无线络下电脑会打不开个别的网站网页,而手机却可以打开。
  12. 什么是MIMO(多输入多输出),以及MIMO的分类和测试(一)
  13. 基于arm-linux-gcc版本,音乐视频播放器mplayer
  14. 单链表———带头结点跟不带头结点的区别
  15. 自动化测试实施的前提条件
  16. 编程资料 -C# 多线程
  17. 软件外包如何正确定价
  18. 从15亿到5000亿 eBay收购PayPal成硅谷传奇
  19. Resource not found问题
  20. linux发音,官方标准

热门文章

  1. STM32/M3/M0关于开关总中断的问题
  2. MangoTrainingCourse课程hands-on lab-1
  3. 我死了,你会娶别的女人吗?
  4. 如何从一个php文件向另一个地址post数据,不用表单和隐藏的变量
  5. 鼓励生娃!携程创始人梁建章:生1个孩子应重奖100万
  6. 背叛乔布斯,库克做对了
  7. 梅耶·马斯克对话邓文迪 直播首秀将上线今日头条、抖音
  8. iOS 14惊现iPhone 12 Pro设计图细节:大失所望!
  9. iPhone 9真机谍照曝光:真没有什么悬念了
  10. 太惨了!卖一个月不如小米卖一天,手机一哥仍不甘心