原文链接:https://blog.csdn.net/zxd_Android/article/details/87642998
背景:
项目中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">
// 摄像机画面
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
//叠在摄像机画面上的fragment
<FrameLayout
android: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、创建fragmentView
f.mView = f.performCreateView(f.getLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
// 3、添加到容器中
if (container != null) {
container.addView(f.mView);
}
从这段源码以及日志中看到,container 是通过mContainer(Activity)去找的,日志报的错,说明findViewbyId没找到,我们去Activity中看下这个方法
@Nullable
@Override
public 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.
*/
@Nullable
public View findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
从这段源码中,可以清楚的看到,是从Activity的setContentView中加载资源的,由于我们这里采用了悬浮窗,所以自然没有办法从中获取到资源,那怎么改?
既然我们知道了原因,那就好入手了啊,实现 override findViewById方法,然后从我们的悬浮窗根view中去加载。
@Override
public <T extends View> T findViewById(int id) {
return mRootViewLayout.findViewById(id);
}
**mRootViewLayout 就是悬浮窗根layout**。