Window就是視窗,是一個抽象類,它的具體實作類是PhoneWindow。它的作用是
顯示和管理view,不管是activity,toast,dialog他們的view都是依附在Window上的,都是通過Window呈現的。DecorView的事件也是從Window傳遞過去的。平時如果想要顯示一個懸浮窗,就可以使用到Window。
建立一個Window的步驟如下:
1.首先要确定是要顯示系統視窗還是應用視窗,如果是要顯示系統視窗則要擷取權限,如果使用者未授權,則會報錯
a.在manifest檔案中聲明需要使用到的權限
//彈出系統類型的Window懸浮窗需要的權限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
b.在6.0系統上要進行動态授權
private fun checkWindowPermission(context:Activity): Boolean {
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
if(!Settings.canDrawOverlays(context)){
//還未授權,需要跳轉到權限設定頁面
ToastUtils.longToast(context,"目前無權限,請授權")
context.startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:"+context.packageName)),0)
return false
}
return true
}
return true
}
2.建立window
//6.0以上系統
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
btn = new Button(context);
btn.setText("懸浮窗");
btn.setGravity(Gravity.CENTER);
btn.setBackground(getResources().getDrawable(R.color.red));
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
if(Build.VERSION.SDK_INT>Build.VERSION_CODES.O){
//8.0以上的系統
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
//這裡如果指定window級别是應用級的,
// 那麼就會報Unable to add window -- token null is not valid; is your activity running? 這個異常
// layoutParams.type =1; running? 這個異常
}else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
//這裡如果指定window級别是應用級的,
// 那麼就會報Unable to add window -- token null is not valid; is your activity running? 這個異常
// layoutParams.type =1;running? 這個異常
}
//這裡falgs 如果是僅僅設定成FLAG_NOT_TOUCH_MODAL,那麼按傳回鍵,onDestory方法不會回調
layoutParams.flags = FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL;
layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.height = 300;
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
layoutParams.x = 300;
layoutParams.y = 300;
windowManager.addView(btn,layoutParams);
//擷取目前視窗可視區域大小
Rect rect = new Rect();
btn.getWindowVisibleDisplayFrame(rect);
LogUtil.i("rect.width()=${rect.width()} rect.height()= ${rect.height()}");
btn.post(new Runnable() {
@Override
public void run() {
WindowId windowId = btn.getWindowId();
LogUtil.e("windowId================ "+windowId);
ViewParent parent = btn.getParent();
LogUtil.e("parent================ "+parent);
}
});
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = (int) event.getRawX();
downY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getRawX();
int moveY = (int) event.getRawY();
layoutParams.x += moveX - downX;
layoutParams.y += moveY - downY;
windowManager.updateViewLayout(btn,layoutParams);
downX = moveX;
downY = moveY;
break;
case MotionEvent.ACTION_UP:
if(event.getRawX() == downX && event.getRawY() == downY){
LogUtil.e("單擊");
makeActivityFromBackgroundToForeground();
}
break;
}
return false;
}
});
}
上面的代碼中,設定了Window的flags是用于控制window的顯示特性的,這裡我們詳細的介紹下,Window常用的幾種flags
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
這種标記表示window不需要擷取焦點,也不需要接收各種輸入事件,此标記會同時啟用FLAG_NOT_TOUCH_MODAL,
最終事件會傳遞到下層的具有焦點的window。
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
此模式下,系統将目前window區域以外的單擊事件傳遞個底層的window,目前window區域的事件自己處理,一般需要開啟這個标記
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
這個标記表示window可以顯示在鎖屏的界面上.
WindowManger.LayoutParams.type是用于區分window的類型的,window的類型分為三類,
應用Window: type的取值範圍是1~99 比如 dialog
子Window : type的取值範圍是1000~1999 不能單獨存在,需要依附在父window上,比如 popwindow
系統Window: type的取值範圍是2000~2999 需要權限才能建立
type的取值越大,則現在的層次就更在上面。上面的window會覆寫下面的window。是以想要彈出的window不被應用的window遮蓋,就将type的值設定到2000~2999之間。這時的window就是系統級别的,需要在manifest檔案中使用權限,并且在6.0的版本中,動态擷取權限。
上面的代碼中,如果将WindowManger.LayoutParams.type的值設定為1,也就是将Window的類型設定為應用Window,那麼就會報錯,報錯的原因可以檢視這篇文章的分析。
介紹完基本使用後,我們來看看window的建立過程,
Window的添加過程:首先要看windowManager.addView()方法,這個方法是用于添加view的。
下面我們看看WindowManger的addView方法,點選這個方法,結果跳轉到了ViewManger接口中了,
下面看看ViewManager接口的源碼:
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
可以看到這個接口定義了三個方法,分别是添加、更新、删除view的方法,是以,WindowManger實際操的對象都是view。WindowManager其實也是一個接口,它繼承了ViewManager接口,是以WindowManager才擁有ViewManager的三個方法。但是WindowManager畢竟隻是一個接口,具體的addView的操作肯定是在其實作類中完成的,WindowManager的實作類就是WindowMangerImpl,下面我們看看WindowManagerImpl類的addView方法
/frameworks/base/core/java/android/view/WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
// ... 省略部分代碼
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
// ... 省略部分代碼
}
從這個方法中可以看到,它的addView的操作是交給了WindowManagerGlobal來完成的,這裡使用了橋接設計模式。
下面看看WindowManagerGlobal類的addView方法:
/frameworks/base/core/java/android/view/WindowManagerGlobal.java
public final class WindowManagerGlobal {
private static WindowManagerGlobal sDefaultWindowManager;
private static IWindowManager sWindowManagerService;
private static IWindowSession sWindowSession;
private final Object mLock = new Object();
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
// ... 省略部分代碼
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
// ... 省略部分代碼
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// ... 省略部分代碼
int index = findViewLocked(view, false); // 1
if (index >= 0) {
if (mDyingViews.contains(view)) {
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
}
// ... 省略部分代碼
root = new ViewRootImpl(view.getContext(), display); //2
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView); // 3
} catch (RuntimeException e) {
// ...省略部分代碼
}
}
}
}
這個方法中,前面個if判斷,是做參數檢查。
注釋1 處,會調用findViewLocked方法,查找view是否被添加,如果已經添加,繼續判斷這個view是否是在即将從Window移除的view,如果是,則将其移除,如果這個view不是即将被從Widow中移除的那個對象,則會報view+ " has already been added to the window manager.異常。在注釋2處,會建立ViewRootImpl對象,接着設定view的params,在将view添加到mViews這個集合中,這個mViews集合是專門存放所有Window對應的view的,接着将前面建立的ViewRootImpl對象root添加到mRoots集合中,這個mRoots集合是用于存放所有Window對應的ViewRootImpl對象的。接着将wparams添加到mParams集合中,這個mParams集合中,存儲了所有Window對應的params。完成了這些操作後, 在注釋3 處,調用ViewRootImpl的setView方法,下面看看ViewRootImpl的setView方法的源碼:
ViewRootImpl類中
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// ...省略部分代碼
int res; /* = WindowManagerImpl.ADD_OKAY; */
// 1 通過handler發送消息完成view的measure,layout,draw
requestLayout();
try {
// ...省略部分代碼
// 2 通過mWindowSession.addToDisplay方法内部通過WindowManagerService完成了window的添加
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
}
// ... 省略部分代碼
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
// ...省略部分代碼
// 關鍵代碼
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
// ...省略部分代碼
}
}
注釋1出,通過調用調用requestLayout方法,接着調用了scheduleTraversals()方法,這個方法中
通過mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);這個方法最終會回調到mTraversalRunnable這個Runnable的run方法。下面接着看mTraversalRunnable的run方法:
ViewRootImpl類中
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
// ...省略部分代碼
// 這個方法最終會執行performMeasure,performLayout,performDraw三個方法,完成view的測量、布局、繪制
performTraversals();
// ...省略部分代碼
}
}
private void performTraversals() {
// ... 省略部分代碼
try {
// ... 省略部分代碼
// 關鍵點 relayoutWindow方法内部通過WindowManagerService完成Window的更新
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
} catch (RemoteException e) {
}
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
// ... 省略部分代碼
if (!mStopped || mReportNextDraw) {
// ...省略部分代碼
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
// ...省略部分代碼
// 關鍵點
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// ...省略部分代碼
if (measureAgain) {
// 關鍵點
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// ...省略部分代碼
}
}
}
// ...省略部分代碼
if (didLayout) {
// 關鍵代碼
performLayout(lp, mWidth, mHeight);
// ... 省略部分代碼
}
// ...省略部分代碼
if (!cancelDraw && !newSurface) {
// ...省略部分代碼
// 關鍵代碼
performDraw();
}
// ...省略部分代碼
}
通過這一系列的方法的調用,ViewRootImpl的requestLayout方法方法其實就是完成了view的測量,布局和繪制過程。
接着繼續看ViewRootImpl的setView方法中的注釋2處
ViewRootImpl類中
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// ...省略部分代碼
int res; /* = WindowManagerImpl.ADD_OKAY; */
// 1 通過handler發送消息完成view的measure,layout,draw
requestLayout();
try {
// ...省略部分代碼
// 2 通過mWindowSession.addToDisplay方法了添加window
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
}
// ... 省略部分代碼
}
}
}
mWindowSession是WindowManagerSerivice的Session對象在用戶端的一個代理對象,它是IWindowSession的類型變量,它是IWindowSession也是一個Binder,是以mWindowSession也是一個binder,mWindowSession最終通過IPC和WindowManagerService進行通信,在SystemServer程序中,調用Session的addToDisplay方法,這個方法會調用到WindowManagerService的addWindow方法,完成對window的添加。
總結:window的添加過程,首先是通過WindowManager.addView方法發起的,由于WindowManager是個接口,實際
的addView方法是它的實作類WindowManagerImpl完成的,WindowManagerImpl的addView方法内部又通過WindowManagerGlobal類的addView方法來完成view的添加,WindowManagerGlobal内部又通過ViewRootImpl的setView方法來對view進行添加。setView方法内部,調用mWindowSession的relayoutWindow對view進行跟新,在接着完成view的測量,布局,和繪制。完成這個步驟後,在通過mWindowSession.addToDisplay()方法發起遠端調用,最終調用SystemServer程序的Session類的addToDisplay()方法,這個方法内部,有調用WindowManagerService的addWindow()方法來完成window的添加。這就是Window的整個添加過程。WindowManagerService内部會為每個應用保留一個Session。