项目实战:WindowManager中removeView的那些坑-随心所欲removeView

本文介绍如何使用WindowManager在Android中创建浮动窗口,并探讨removeView与removeViewImmediate的区别,以避免窗体泄露问题。

转载自:https://blog.youkuaiyun.com/hxqneuq2012/article/details/52588536

正所谓没有遇到过类似于not attached to window manager、Android removeView view must not be null的开发者不是好工程师,今天我们就来看看WindowManager中removeView的那些坑。

其实也没必要细说它有多坑,只要避过这些坑即可。

经过这一段时间的项目开发,小结一条真理:

不要在像Activity这样有生命周期的东东里乱写东西,不然莫名其妙的被销毁,然后新建,但早已经是物是人非,最后就是状态异常、空指针各种Crash各种坑。

我们要实现的效果是:点击显示Button-addView,点击消除Button-removeView,

哈哈,不要喷,我想removeView可是想在哪就在哪,随心所欲,游离于Activity的生命周期之外~

直接上代码,将WindowManager抽离出来即可,一个小Demo:

首先添加相应权限:

[html]  view plain  copy
  1. <span style="font-size:18px;">    <!-- 显示顶层浮窗 -->  
  2.     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /></span>  


看一下布局文件,2个Button,十分简单:

[html]  view plain  copy
  1. <span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical" >  
  6.   
  7.     <Button  
  8.         android:id="@+id/show_btn_main"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="wrap_content"  
  11.         android:focusable="true"  
  12.         android:text="button_show"  
  13.         android:textSize="18sp" />  
  14.   
  15.     <Button  
  16.         android:id="@+id/remove_btn_main"  
  17.         android:layout_width="match_parent"  
  18.         android:layout_height="wrap_content"  
  19.         android:focusable="true"  
  20.         android:text="button_hide"  
  21.         android:textSize="18sp" />  
  22.   
  23. </LinearLayout></span>  


终于到了核心源代码部分,先看我们写的工具类WindowUtils,在这里写好WindowManager供外部调用。
[java]  view plain  copy
  1. <span style="font-size:18px;">/** 
  2.  * Created by 权兴权意 on 2016/9/19. 
  3.  */  
  4. import android.content.Context;  
  5. import android.graphics.PixelFormat;  
  6. import android.view.Gravity;  
  7. import android.view.WindowManager;  
  8. import android.view.WindowManager.LayoutParams;  
  9. import android.widget.ImageView;  
  10. import android.widget.Toast;  
  11.   
  12. public class WindowUtils {  
  13.   
  14.     private static ImageView mView = null;  
  15.     private static WindowManager mWindowManager = null;  
  16.     private static Context mContext = null;  
  17.   
  18.     public static void showWindow(final Context context) {  
  19.         Toast.makeText(context,"showPopupWindow",Toast.LENGTH_SHORT).show();  
  20.         // 获取应用的Context  
  21.         mContext = context.getApplicationContext();  
  22.         // 获取WindowManager  
  23.         mWindowManager = (WindowManager) mContext  
  24.                 .getSystemService(Context.WINDOW_SERVICE);  
  25.   
  26.         mView = new ImageView(context);  
  27.         mView.setImageResource(R.mipmap.ic_launcher);  
  28.   
  29.         final WindowManager.LayoutParams params = new WindowManager.LayoutParams();  
  30.   
  31.         // 类型  
  32.         params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;  
  33.   
  34.         // 设置flag  
  35.         // 如果设置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,弹出的View收不到Back键的事件  
  36.         params.flags = params.flags|WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;  
  37.         // 不设置这个弹出框的透明遮罩显示为黑色  
  38.         params.format = PixelFormat.TRANSLUCENT;  
  39.         // FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口  
  40.         // 设置 FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按  
  41.   
  42.         params.width = LayoutParams.WRAP_CONTENT;  
  43.         params.height = LayoutParams.WRAP_CONTENT;  
  44.   
  45.         params.gravity = Gravity.CENTER;  
  46.   
  47.         mWindowManager.addView(mView, params);  
  48.   
  49.     }  
  50.   
  51.     public static void removeWindow() {  
  52.         mWindowManager.removeViewImmediate(mView);  
  53.     }  
  54.   
  55. }  
  56. </span>  


接下来在MainActivity中调用即可,也不用重写onDestory方法哈:

[java]  view plain  copy
  1. /** 
  2.  * Created by 权兴权意 on 2016/9/19. 
  3.  */  
  4. import  
  5. import android.app.Activity;  
  6. import android.os.Bundle;  
  7. import android.view.View;  
  8. import android.view.View.OnClickListener;  
  9. import android.widget.Button;  
  10.   
  11. public class MainActivity extends Activity {  
  12.   
  13.     private Button show_btn_main;  
  14.     private Button remove_btn_main;  
  15.   
  16.     @Override  
  17.     protected void onCreate(Bundle savedInstanceState) {  
  18.         super.onCreate(savedInstanceState);  
  19.         setContentView(R.layout.activity_main);  
  20.   
  21.   
  22.         show_btn_main = (Button) findViewById(R.id.show_btn_main);  
  23.         show_btn_main.setOnClickListener(new OnClickListener() {  
  24.   
  25.             @Override  
  26.             public void onClick(View v) {  
  27.                 WindowUtils.showWindow(MainActivity.this);  
  28.             }  
  29.         });  
  30.   
  31.         remove_btn_main = (Button) findViewById(R.id.remove_btn_main);  
  32.         remove_btn_main.setOnClickListener(new OnClickListener() {  
  33.   
  34.             @Override  
  35.             public void onClick(View v) {  
  36.                 WindowUtils.removeWindow();  
  37.             }  
  38.         });  
  39.     }  
  40.   
  41. }  


ok,大功告成,看看效果吧。



最后,提一下removeViewremoveViewImmediate的区别:

问题及原因:

   我们在做UI相关的代码时有时候会碰到WindowLeak,也就是所谓的窗体泄露,泄露的原因是因为androidUI操作在主线程中操作,但是我们会需要在一些线程或者异步任务中操作UI界面元素的需求,那么这个时候可能会出现类似问题。我在做浮动窗口的时候碰到了这个问题,浮动窗口需要用到WindowManager,windowManger又是一个activity的一个变量,它依存于Activity,当横竖屏切换或者activity销毁的时候这个变量会销毁。销毁的时候导致windowmanager通过AddView()方法添加的View没有依存,导致窗体泄露。那么问题来了,为什么这里会泄露了?

解决方法:

  我在onDestroy()里面调用了removeView方法,想要避免窗体泄露,但是这个方法并不管用,后来换成removeViewImmediate()就解决了这个问题,原因就是两个方法设计到线程同步问题,removeViewImmediate()是通知View立刻调用View.onDetachWindow(),这说明这个方法是通过一个监听或者观察者来实现的,因为线程的同步跟异步问题导致activity销毁了,但view还没有被remove完,于是就产生了所谓的窗体泄露。说到这里,我想大家也能明白这两个方法的区别了。


话说回来,最好的方法还是抽离。


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值