1. 问题描述

通过WindowManager往窗口里添加浮动按钮,在Android7.0时该按钮可以全局保留,直至进程被杀掉。而Android7.0以下(以Android4.4为例)浮动按钮随Activity的onStop()方法被覆盖。
以下为浮动按钮的实现代码:

WindowManager mWm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);WindowManager.LayoutParams params = new WindowManager.LayoutParams();params.type= LayoutParams.TYPE_PHONE;     // 系统提示类型,重要params.format=1;params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; params.flags = params.flags | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;params.flags = params.flags | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; params.width = dipToPixel(60);params.height = dipToPixel(60);params.x = dipToPixel(140);params.y = dipToPixel(230);View view = LayoutInflater.from(mContext).inflate(R.layout.float_view, null);mWm.addView(view, params);
FloatView floatView = new FloatView(this); //传入Activity Context

2.解决方案

考虑到可能由于传入的Context为Activity导致的问题,进行修改后发现浮动按钮在Android4.4下正常运行。
FloatView floatView = new FloatView(getApplicationContext());
以下则以两方面分析差异原因:
* 使用Activity表现不同的原因。
* 使用Application表现相同的原因。

3.原因分析

##### * 使用Activity表现不同的原因。
首先,可以了解到Activity对getSystemService方法进行了重写:

@Override    public Object getSystemService(@ServiceName @NonNull String name) {        if (getBaseContext() == null) {            throw new IllegalStateException(                    "System services not available to Activities before onCreate()");        }        if (WINDOW_SERVICE.equals(name)) {            return mWindowManager;        } else if (SEARCH_SERVICE.equals(name)) {            ensureSearchManager();            return mSearchManager;        }        return super.getSystemService(name);    }

WindowManager在Activity初始化的时候就已经被创建。mWindowManager变量在ActivityThread的handleLaunchedActivityactivity.attach()方法中赋值。最终该变量指向了WindowManagerImpl对象。

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {        return new WindowManagerImpl(mContext, parentWindow);    }

此处的parentWindow为当前Activity的Window
之后使用WindowManageraddView方法添加按钮,最终会调用WindowManagerGlobaladdView方法。其中该方法有如下语句:

 if (parentWindow != null) {            parentWindow.adjustLayoutParamsForSubWindow(wparams);        }

从该该方法开始,表现出现差异:

//Android 7.0 void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {        CharSequence curTitle = wp.getTitle();        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {            if (wp.token == null) {                View decor = peekDecorView();                if (decor != null) {                    wp.token = decor.getWindowToken();                }            }            //...        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {            if (curTitle == null || curTitle.length() == 0) {                final StringBuilder title = new StringBuilder(32);                title.append("Sys").append(wp.type);                if (mAppName != null) {                    title.append(":").append(mAppName);                }                wp.setTitle(title);            }        } else {            if (wp.token == null) {                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;            }            if ((curTitle == null || curTitle.length() == 0)                    && mAppName != null) {                wp.setTitle(mAppName);            }        }       //...    }
//Android 4.4 void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {        CharSequence curTitle = wp.getTitle();        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {            if (wp.token == null) {                View decor = peekDecorView();                if (decor != null) {                    wp.token = decor.getWindowToken();                }            }            //...        } else {            if (wp.token == null) {                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;            }            if ((curTitle == null || curTitle.length() == 0)                    && mAppName != null) {                wp.setTitle(mAppName);            }        }       //...    }

代码大概的逻辑是判断该LayoutParamstype是属于应用窗口级别,还是系统窗口级别。从而为LayoutParams添加对应的token
此处token可以用来绑定ActivityWindowManager。当视图发生变更需要重新遍历或者调用onStop覆盖掉当前Activity时,就可以通过该token找到对应的ViewRootImpl,从而进行View树的相关操作。
在Android7.0时,系统窗口没有设置token,4.4时,token设置为默认的mAppToken,即Activity启动时由AMS创建并传递过来的IBinder对象。
在Activity进入后台后,会调用Activity中的performStop方法,方法中执行以下语句:
WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
从而调用了setStoppedState方法:

public void setStoppedState(IBinder token, boolean stopped) {        synchronized (mLock) {            int count = mViews.size();            for (int i = 0; i < count; i++) {                //token是Activity的mToken 不为空                if (token == null || mParams.get(i).token == token) {                    ViewRootImpl root = mRoots.get(i);                    root.setWindowStopped(stopped);                }            }        }    }

当LayoutParams的token与Activity的token匹配时,则取出ViewRootImpl调用setWindowStopped方法,从而执行onStop过程的后续操作。
Android7.0 token为null,不匹配,无法进入判断,则不进行对浮动按钮的后续操作,所以会常驻留窗口。
Android4.4 token为Activity持有的mToken,匹配,会执行后续操作。
##### * 使用Application表现相同的原因。
Application没有对getSystemService进行重写,全部实现交由ContextImpl实现。

@Override    public Object getSystemService(String name) {        return SystemServiceRegistry.getSystemService(this, name);    }

上述从SystemServiceRegistry取出相应服务。SystemServiceRegistry在APP创建时会初始化各种服务。其中包括WindowManager

registerService(Context.WINDOW_SERVICE, WindowManager.class,                new CachedServiceFetcher() {            @Override            public WindowManager createService(ContextImpl ctx) {                return new WindowManagerImpl(ctx);}});

通过Application的getSystemService获得的WindowManager 仍然是WindowManagerImpl对象。Application和Activity创建的区别也在此处。

//Application调用public WindowManagerImpl(Context context) {        this(context, null);    }//Activity调用private WindowManagerImpl(Context context, Window parentWindow) {        mContext = context;        mParentWindow = parentWindow;}

Application获得的WindowManagerImplmParentWindow属性为空。这样会在addView时跳过token的赋值。

 if (parentWindow != null) {            parentWindow.adjustLayoutParamsForSubWindow(wparams); }

从而在setStopState方法判断mParams.get(i).token == token中不论是7.0还是4.4中token都为null,不匹配,无法进入后续的stop操作,浮动按钮就常驻窗口。

4. 个人理解

Android系统为每个Activity初始化时添加一个本地的WindowManager引用。由于Activity涉及频繁的View操作,对用WindowManager的使用频率较高,提前初始化对于效率会是一种提升。

更多相关文章

  1. Android(安卓)学习路线总结
  2. 网络请求框架AsyncHttpclient的简单使用
  3. Android(安卓)UI控件之RadioGroup、RadioButton
  4. Android(安卓)MediaPlayer类详解
  5. 关于Android如何禁止屏幕旋转刷新界面
  6. android studio更改module名字
  7. 获取Google Maps API 指纹证书时获取的是SHA1型的,而认证时需要的
  8. Android四大组件之 BroadcastReceiver
  9. android中BroadCastReceiver使用(广播的接受和发送)

随机推荐

  1. Android(安卓)- SharedPreferences共享数
  2. Android系列之Intent传递对象的几种实例
  3. [Android]在android虚拟机中安装apk手机
  4. Android中实现Runnable接口简单例子
  5. 嵌入式linux和嵌入式android系统有什么区
  6. 如何获取Android系统时间是24小时制还是1
  7. Android(安卓)ListView中动态显示和隐藏H
  8. Android(安卓)Fragment基本用法
  9. Android小程序-模拟小球平抛落地反弹到静
  10. android java数组应用与说明