Android悬浮窗基础: Window 和 WindowManager

引言

  • Window 是类似悬浮窗的东西
  • WM 参与 WindowCreate 和 管理, WMSWM 共同完成 WindowIPC交互
  • WindowView 直接 管理者
  • ActivitysetContenView 底层是由 PhoneWindowinstallDecor绘制的.

1. 如何使用 WM 添加一个 Window?

// 将一个Button添加到屏幕位置(100,300)的位置
Button button = new Button(this);
button.setText("CrazyDailyQuestion");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 00, PixelFormat.TRANSLUCENT);

// 通过Flags控制Window的显示特性
layoutParams.flags =

// 此模式下,系统会将当前Window区域外的单击事件,传递给底层Window,当前Window区域外的点击事件传递给底层Window,当前Window区域以内的事件则自己处理,如果不开启该事件可能出现其他Window无法捕捉到单击事件
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |

// Window 不需要获取任何焦点,也不需要接收各种输入事件,此标记会同时开启 FLAG_NOT_TOUCH_MODAL
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN |

// Window可以显示在锁屏的界面上
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
        layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
        layoutParams.x = 100;
        layoutParams.y = 300;

// 获取Window的对象
WindowManager mManager = (WindowManager) getApplicationContext()
                .getSystemService(Context.WINDOW_SERVICE);

// 通过WM将View绘制到指定位置
mManager.addView(button, layoutParams);

2. layoutParams.type

layoutParams.typeexampz-orderered注意事项
ApplicationActivity(0,100)/
ChildDialog(999,2000)不能单独存在,需要附属在特定的父 Window
SystemToast,StatusBar ,软键盘(1999,3000)需要声明权限

3. Method

public interface ViewManager{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
addView

概念: 创建 Window添加View

  1.检查参数是否合法,如果子 Window,那么还需要调整一些布局参数
addJustLayoutParamsForSubWindow(wparams)
  2.创建 ViewRootImpl 并将 View 添加到列表中
// 存储所有Window对应的View
private final ArrayList<View> mViews = new ArrayList<View>();
// 存储所有Window对应的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
// 存储所有Window所对应的布局参数
private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
// 存储了正在删除还未完成的 Window 对象
private final ArraySet<View> mDyingViews = new ArraySet<View>();

// 将 Window 对象添加到列表里面
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
  3.通过 ViewRootImpl 来更新界面并完成 Window 的添加过程

  Window 的绘制 由 ViewRootImplsetView 来完成,setView 方法会通过 requestLayout 来完成异步请求.

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            // View的绘制入口
            scheduleTraversals();
        }
    }

  WindowSession 最终完成 Window 的添加过程,真正实现类是 Session,也是Window 添加过程的一次 IPC 调用.然后底层是Seesion通过WMS 实现Window 添加.

updateViewLayout

  概念: 更新 Window中的View

removeView

  概念: 删除一个 Window

4. Window 事件操作

// 将 View 设置 OnTouchListener
mButton.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
            // 只需根据手指设置 mLayoutParams 的x,y值更改Window的位置
                int rawX = (int) event.getRawX();
                int rawY = (int) event.getRawY();

                switch (event.getAction()) {
                    case MotionEvent.ACTION_MOVE:

                        mLayoutParams.x = rawX;
                        mLayoutParams.y = rawY;

                        // onTouch 方法不断更新 View的位置
                        manager.updateViewLayout(mButton, mLayoutParams);
                        break;
                    default:
                        break;
                }

                return false;

            }
        });

5. Activity && Window

  ViewAndroid 中视图的程序方式,View 不能单独存在,必须依附 Window 这个抽象概念上,因此有视图的地方就有Window,下面我就带大家来看一下,ActivtyWindwow 创建过程.
  Activity 启动过程很复杂,最终是交给ActivityThreadperformLauchActivity() 来完成整个启动过程,在整个方法内部会通过类加载器创建 Activity 的实例对象,并通过 attach 方法关联一切所需要的上下文环境.

  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  java.lang.ClassLoader cl = appContext.getClassLoader();
  activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
// -------为了避免浪费篇幅,省略无关代码--------
          if (activity != null) {
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
 // -------为了避免浪费篇幅,省略无关代码--------
 }
  final void attach(IBinder windowToken) {

        // window 对象通过 PolicyManager.makeNewWindow 创建
        mWindow = PolicyManager.makeNewWindow(this);
        // window 的 Callback接口里面有我们熟悉的dispaTouch方法以及onAttachToWindow方法
        mWindow.setCallback(this);
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
        mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));

        WindowManager.LayoutParams lp = mWindow.getAttributes();
        lp.type = WindowManager.LayoutParams.TYPE_DREAM;
        lp.token = windowToken;
        lp.windowAnimations = com.android.internal.R.style.Animation_Dream;

  }

  PolicyManager的具体实现接口方法都在策阅接口IPolicy里面完成.其中makeNewWindow实际上是创建了一个Window对象.

public interface IPolicy {
    public Window makeNewWindow(Context context);
    public LayoutInflater makeNewLayoutInflater(Context context);
    public WindowManagerPolicy makeNewWindowManager();
    public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}

   然后我们去找 makeNewWindow 的接口实现,我们发现,Window 具体实现实际上是 PhoneWindow.

 public PhoneWindow makeNewWindow(Context context) {
       return new PhoneWindow(context);
   }

   整个过程 Window就已经创建完毕了,接下来我们只要看 Activity 是怎样将具体实现交给Window处理,而Window的具体实现是PhoneWindow,所以我们来看setContentView的具体实现.

   借助孙群的源码结构图,整个setContentView源码结构如上,接下来我们来看一下PhoneWindowsetContentView源码

    @Override
   public void setContentView(int layoutResID) {
    if (mContentParent == null) {
    // installDecor()方法会调用generateDecor()和generateLayout()方法
           installDecor();
       } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
           mContentParent.removeAllViews();
       }

       if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
           final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                   getContext());
           transitionTo(newScene);
       } else {
           mLayoutInflater.inflate(layoutResID, mContentParent);
       }
       mContentParent.requestApplyInsets();
       final Callback cb = getCallback();
       if (cb != null && !isDestroyed()) {
       // 最后触发内容变化的回调
           cb.onContentChanged();
       }
       mContentParentExplicitlySet = true;
   }

1. DecorView 创建

   这里我们着重需要了解的是 generateDecor() 方法实现

    protected DecorView generateDecor(int featureId) {

       Context context;
       if (mUseDecorContext) {
           Context applicationContext = getContext().getApplicationContext();
           if (applicationContext == null) {
               context = getContext();
           } else {
               context = new DecorContext(applicationContext, getContext().getResources());
               if (mTheme != -1) {
                   context.setTheme(mTheme);
               }
           }
       } else {
           context = getContext();
       }
       return new DecorView(context, featureId, this, getAttributes());
   }

  DecorView其实就是一个FrameLayout,它的创建过程由 installDecor 完成,然后installDecor内部方法通过 generateDecor 来创建 DecorView,这个时候 DecorView 还是一个空白的 FrameLayout.

mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

  其中具体的布局文件和主题都在generateDecor完成,然后我们需要注意的是ID_ANDROID_CONTENT多对应的 id 其实就是ViewGroup 对应的MContentParent

    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
2. View 添加到 DecorViewmContenParnent
mLayoutInflate.inflate(layoutResID,mContenParnent);
3. 回调 Activity 的 onContentchanged 方法 通知 Activity 视图已经发生改变.
     if (cb != null && !isDestroyed()) {
        // 最后触发内容变化的回调
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

  DecorView 绘制完成,绘制主要是在 onResume 完成的,具体可以查看 ActivityThread -> handleResumeActivity -> makeVisible -> addView

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

6. Dialog && Window

   Dialog 的创建和 Activty 类似,需要注意两个事情.第一点, Dialog 使用的是 Activity 的 token,而不是 Application 的 token,所以上下文注意不要用错了,第二点,因为 Dilaog 是子 Window,所以需要申请系统 Window 权限.

7. Toast && Window

原理:
  • IPC 机制

    • Toast 访问 NMS
    • NMS 回调 Toast TN 接口
  • Handler 定时系统

8. View && Window

  任何一个 View 都是依附在 Window 上的,Window 是 View 直接 管理者

9. 注意事项

  • 华为手机需要手动赋权,才能开启 Toast.建议自定义 Toast
  • 频繁请求 Toast,系统有权拒绝 NMS,防止网络攻击
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值