Window表示一個懸浮視窗,在開發直接接觸的并不多,但是當我們需要使用在桌面上顯示一個懸浮窗的東西的時候Window就派上用場了。Window是一個抽象類,它的具體實作時PopupWindow類,建立Window很簡單,隻需要通過WindowManager即可完成。WindowManager是外界通路Window的入口。
Window和WindowManager之間的聯系
為了分析Window的工作機制,使用WindowManager來添加一個Window
floatingButton=new Button(this);
floatingButton.setText("123");
mLayoutParams=new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT,0,0,PixelFormat.TRANSPARENT);
mLayoutParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL|LayoutParams.FLAG_NOT_FOCUSABLE|LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.gravity=Gravity.LEFT|Gravity.TOP;
mLayoutParams.x=100;
mLayoutParams.y=300;
mWindowManager.addView(floatingButton,mLayoutParams);
在建構WindowManager.LayoutParams時,其中的type和flags是非常重要的.
flags參數有很多選項,用來控制Window的顯示特性
- FLAG_NOT_FOCUSABLE——表示此Window不需要擷取焦點,不接受各種輸入事件,此标記會同時啟用FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層具有焦點的Window
- FLAG_NOT_TOUCH_MODAL——自己Window區域内的事件自己處理,自己Window區域外的事件傳遞給底層Window處理。一般這個選項會預設開啟,否則其他Window無法收到事件
- FLAG_SHOW_WHEN_LOCKED——可以讓Window顯示在鎖屏上
type參數時int類型,表示Window的類型。Window有三種類型
- 應用Window——對應着一個Activity,層級範圍1~99;
- 子Window——不能獨立存在,需要附屬在特定的父Window種,比如Dialog就是一個子Window。層級範圍1000~1999;
- 系統WIndow——需要聲明權限才能建立,如Toast和系統狀态欄都是系統Window,層級範圍2000~2999;
Window是分層的,層級大的Window會覆寫在層級小的Window上面。
WindowManager提供的功能很簡單,常用的隻有三個方法,即添加View,更新View和删除View。這三個方法定義在ViewManager種,而WindowManager繼承自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);
}
應用懸浮窗執行個體:
public class MainActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {
private static final String TAG="TestActivity";
private Button addBtn,rmvBtn;
private ImageView imageView;
private WindowManager windowManager;
private WindowManager.LayoutParams layoutParams;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//當API大于23時需要在代碼中動态申請權限
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
Intent intent=new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivityForResult(intent,100);
}else {
initView();
}
}
private void initView() {
addBtn=findViewById(R.id.add_btn);
rmvBtn=findViewById(R.id.remocve_btn);
addBtn.setOnClickListener(this);
rmvBtn.setOnClickListener(this);
windowManager=(WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode==100){
initView();
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.add_btn:
addView();
break;
case R.id.remocve_btn:
removeView();
break;
}
}
private void removeView() {
windowManager.removeViewImmediate(imageView);
}
private void addView() {
imageView = new ImageView(this);
imageView.setBackgroundResource(R.mipmap.ic_launcher);
layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT
, WindowManager.LayoutParams.WRAP_CONTENT, 2099,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
, PixelFormat.TRANSPARENT);
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
layoutParams.x = 0;
layoutParams.y = 300;
imageView.setOnTouchListener(this);
windowManager.addView(imageView, layoutParams);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX=(int) event.getRawX();
int rawY=(int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_MOVE: {
layoutParams.x = rawX;
layoutParams.y = rawY;
windowManager.updateViewLayout(imageView, layoutParams);
break;
}
default:
break;
}
return true;
}
}
在XML中直接添加了兩個Button
實作效果:
Window的内部機制
window是一個抽象概念,每一個Window都對應着一個View和ViewRootImpl,Window和View通過ViewRootImpl來建立聯系。是以Window并不是實際存在的,而是以View的形式存在。 在實際使用中無法直接通路Window,對Window的通路必須同通過WindowManager。在WindowManager住要提供了三個接口方法,addView,updateViewLayout,removeView,是以我們就從Window的添加,更新,删除來看它的内部機制。
Window添加的過程
在WindowManager中真正實作三大操作的是WindowManagerImpl類,在WindowManagerImpl中Window的三大操作如下:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
在代碼中看到了一個mGlobal,發現這一些列操作都是由他來實作的,來看看mGlobal是何方神聖。
這個mGlobal是一個WindowManagerGlobal類的執行個體,那麼繼續檢視在這個類中是如何實作我們的三大操作的呢?
WindowManagerGlobal中的addView如下:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//檢查參數是否合法,如果是子Window那麼還需要調整一些布局參數
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;
if (parentWindow != null) {
//當是子Window的時候需要調整參數通過adjustLayoutParamsForSubWindow這個方法
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
//如果沒有父視圖,則此視圖的硬體加速将從應用程式的硬體加速設定中設定。
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
//下列操作将Window的一系列對象添加到清單中
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//mView存儲的是所有Window對應的View
mViews.add(view);
//mRoots存儲的是所有Window所對應的ViewRootImpl
mRoots.add(root);
//mParams存儲的是所有Window所對應的布局參數
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
//通過ViewRootImpl的執行個體root.setView來更新界面并完成Window的添加過程
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
通過ViewRootImpl将View添加到清單,并通過ViewRootImpl來更新界面并完成Window的添加,在ViewRootImpl的執行個體的setView方法中來完成,在setView的内部會通過requestLayout來完成異步重新整理請求。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//View的繪制入口
scheduleTraversals();
}
}
檢視scheduleTraversals的實作:
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
通過WindowSession最終來實作Window的添加過程,WindowSession是一個IWindowSession,是一個Binder對象,真正的實作類是Session,是以Window的添加過程就是一次IPC調用
session内部的會通過WindowManagerService來實作Window的添加
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams, attrs, int viewVisibility,
int displayId, Rect outContentInsets, InputChannel outInputChannel){
return mService.addWindow(this, window, seq, attrs, viewVisibility
, displayId, outContentInsets, outInputChannel);
}
最終,Window的添加請求移交給了WindowManagerService手上,在WindowManagerService内部會為每一個應用保留一個單獨的Session。Window的添加就到此為止了。
圖解:
對于Window的删除操作和更新操作其實都是類似的。