项目开发中有使用到了悬浮窗,近期在往Android O 系统porting,发现悬浮窗不能正常使用,运行出错:
android.view.WindowManager$BadTokenException: Unable to add window — token android.os.BinderProxy@447a6748 is not valid; is your activity running?
或者
android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootlmpl$W@40ec8528 -- permission denied for this window
Android 8.0 除了提供诸多新特性和功能外,还对系统和 API 行为做出了各种变更。其中有些是针对Android 8.0 的应用。官方文档描述如下。
这些行为变更专门应用于针对 O 平台或更高平台版本的应用。针对 Android8.0 或更高平台版本进行编译,或将 targetSdkVersion设为 Android 8.0 或更高版本的应用开发者必须修改其应用以正确支持这些行为(如果适用)。
提醒窗口
使用SYSTEM_ALERT_WINDOW 权限的应用无法再使用以下窗口类型来在其他应用和系统窗口上方显示提醒窗口:
·TYPE_PHONE
·TYPE_PRIORITY_PHONE
·TYPE_SYSTEM_ALERT
·TYPE_SYSTEM_OVERLAY
·TYPE_SYSTEM_ERROR
相反,应用必须使用名为 TYPE_APPLICATION_OVERLAY 的新窗口类型。
使用TYPE_APPLICATION_OVERLAY 窗口类型显示应用的提醒窗口时,请记住新窗口类型的以下特性:
·应用的提醒窗口始终显示在状态栏和输入法等关键系统窗口的下面。
·系统可以移动使用 TYPE_APPLICATION_OVERLAY窗口类型的窗口或调整其大小,以改善屏幕显示效果。
·通过打开通知栏,用户可以访问设置来阻止应用显示使用 TYPE_APPLICATION_OVERLAY 窗口类型显示的提醒窗口。
……
因此,针对 Android 8.0 或更高平台版本进行编译,或将 targetSdkVersion
设为 Android 8.0 或更高版本的应用开发是,只要区别设置即可。
……
if (Build.VERSION.SDK_INT>=26) {//8.0新特性
mWindowParams.type= WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}else{
mWindowParams.type= WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
……
android:name="android.permission.SYSTEM_ALERT_WINDOW"/> android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
//权限判断 if (Build.VERSION.SDK_INT >= 23) { if(!Settings.canDrawOverlays(getApplicationContext())) { //启动Activity让用户授权 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,Uri.parse("package:" + getPackageName())); startActivity(intent); return; } else { //执行6.0以上绘制代码 } } else { //执行6.0以下绘制代码、 }或者:
//权限判断 if (Build.VERSION.SDK_INT >= 23) { if(!Settings.canDrawOverlays(getApplicationContext())) { //启动Activity让用户授权 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,Uri.parse("package:" + getPackageName())); startActivityForResult(intent,10); return; } else { //执行6.0以上绘制代码 } } else { //执行6.0以下绘制代码 }
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 10) { if (Build.VERSION.SDK_INT >= 23) { if (!Settings.canDrawOverlays(this)) { // SYSTEM_ALERT_WINDOW permission not granted... Toast.makeText(LoginActivity.this,"not granted",Toast.LENGTH_SHORT); } } } }
package ......; import android.app.Service; import android.content.Context; import android.graphics.PixelFormat; import android.os.Build; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import java.lang.reflect.Field; /** * Created by zhongxiang.huang on 2017/6/23. * */ public class FloatWindow implements View.OnTouchListener { private Context mContext; private WindowManager.LayoutParams mWindowParams; private WindowManager mWindowManager; private View mFloatLayout; private float mInViewX; private float mInViewY; private float mDownInScreenX; private float mDownInScreenY; private float mInScreenX; private float mInScreenY; public FloatWindow(Service context) { this.mContext = context; initFloatWindow(); } private void initFloatWindow(){ LayoutInflater inflater = LayoutInflater.from(mContext); if(inflater == null) return; mFloatLayout = (View) inflater.inflate(R.layout.layout_float, null); mFloatLayout.setOnTouchListener(this); mWindowParams = new WindowManager.LayoutParams(); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); if (Build.VERSION.SDK_INT >= 26) {//8.0新特性 mWindowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; }else{ mWindowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; } mWindowParams.format = PixelFormat.RGBA_8888; mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; mWindowParams.gravity = Gravity.START | Gravity.TOP; mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT; } @Override public boolean onTouch(View view, MotionEvent motionEvent) { return floatLayoutTouch(motionEvent); } private boolean floatLayoutTouch(MotionEvent motionEvent){ switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN: // 获取相对View的坐标,即以此View左上角为原点 mInViewX = motionEvent.getX(); mInViewY = motionEvent.getY(); // 获取相对屏幕的坐标,即以屏幕左上角为原点 mDownInScreenX = motionEvent.getRawX(); mDownInScreenY = motionEvent.getRawY() - getSysBarHeight(mContext); mInScreenX = motionEvent.getRawX(); mInScreenY = motionEvent.getRawY() - getSysBarHeight(mContext); break; case MotionEvent.ACTION_MOVE: // 更新浮动窗口位置参数 mInScreenX = motionEvent.getRawX(); mInScreenY = motionEvent.getRawY() - getSysBarHeight(mContext); mWindowParams.x = (int) (mInScreenX- mInViewX); mWindowParams.y = (int) (mInScreenY - mInViewY); // 手指移动的时候更新小悬浮窗的位置 mWindowManager.updateViewLayout(mFloatLayout, mWindowParams); break; case MotionEvent.ACTION_UP: // 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。 if (mDownInScreenX == mInScreenX && mDownInScreenY == mInScreenY){ } break; } return true; } public void showFloatWindow(){ if (mFloatLayout.getParent() == null){ DisplayMetrics metrics = new DisplayMetrics(); //默认固定位置,靠屏幕右边缘的中间 mWindowManager.getDefaultDisplay().getMetrics(metrics); mWindowParams.x = metrics.widthPixels; mWindowParams.y = metrics.heightPixels/2 - getSysBarHeight(mContext); mWindowManager.addView(mFloatLayout, mWindowParams); } } public void hideFloatWindow(){ if (mFloatLayout.getParent() != null) mWindowManager.removeView(mFloatLayout); } public void setFloatLayoutAlpha(boolean alpha){ if (alpha) mFloatLayout.setAlpha((float) 0.5); else mFloatLayout.setAlpha(1); } // 获取系统状态栏高度 public static int getSysBarHeight(Context contex) { Class c; Object obj; Field field; int x; int sbar = 0; try { c = Class.forName("com.android.internal.R$dimen"); obj = c.newInstance(); field = c.getField("status_bar_height"); x = Integer.parseInt(field.get(obj).toString()); sbar = contex.getResources().getDimensionPixelSize(x); } catch (Exception e1) { e1.printStackTrace(); } return sbar; } }