編輯:關於Android編程
Window表示的是一個窗口的概念,它是一個抽象類,它的具體實現是PhoneWindow。創建一個Window需要通過WindowManger來完成。WindowManger是外界訪問Window的入口,Window的具體實現位於WindowMangerService,WindowManger與WindowMangerService的交互是一個IPC過程。Android中所有的View都是Window來呈現的,不管是Activity、Toast還是Dialog,它們的視圖都是附加到Window上的,因此Window是View的直接管理者。
View的事件分發機制中的事件傳遞:單擊事件由Activity內部的Window -> Decor View -> View
使用WindowManger添加一個view到Window
自定義浮窗 需要權限android.permission.SYSTEM_ALERT_WINDOW
/** * 顯示浮窗 * @param content 要填充的文本內容 * @param layoutId 用於創建窗體View的布局 */ public void show(String content,int layoutId) { wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); params = new WindowManager.LayoutParams(); screenHeight = AppInfoUtils.getScreenSize(context).height; screenWidth = AppInfoUtils.getScreenSize(context).width; // 加載布局 view = View.inflate(mContext, layoutId, null); // 設置浮窗params屬性 params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; params.type = WindowManager.LayoutParams.TYPE_PHONE; params.gravity = Gravity.TOP+Gravity.LEFT; // 將重心設置為左上方 params.format = PixelFormat.TRANSLUCENT; // 半透明 params.x = sp.getInt("startX", 0); // 設置顯示位置 params.y = sp.getInt("startY", 0); TextView tvLocation = (TextView) view.findViewById(R.id.tv_toast_location); tvLocation.setText(content); // 將View添加到窗體管理器 wm.addView(view, params); }
1、LayoutParams.Flags參數表示Window的屬性,通過設置它的選項可以控制Window的顯示特性。如下幾種常見選項:
FLAG_NOT_FOCUSABLE
不許獲得焦點
FLAG_NOT_TOUCHABLE
不接受觸摸屏事件
FLAG_NOT_TOUCH_MODAL
當窗口可以獲得焦點(沒有設置 FLAG_NOT_FOCUSALBE 選項)時,仍然將窗口范圍之外的點設備事件(鼠標、觸摸屏)發送給後面的窗口處理。否則它將獨占所有的點設備事件,而不管它們是不是發生在窗口范圍內。
FLAG_SHOW_WHEN_LOCKED
當屏幕鎖定時,窗口可以被看到。這使得應用程序窗口優先於鎖屏界面。可配合FLAG_KEEP_SCREEN_ON選項點亮屏幕並直接顯示在鎖屏界面之前。可使用FLAG_DISMISS_KEYGUARD選項直接解除非加鎖的鎖屏狀態。此選項只用於最頂層的全屏幕窗口。
FLAG_DIM_BEHIND。
窗口之後的內容變暗
FLAG_BLUR_BEHIND
窗口之後的內容變模糊。
2、Type參數表示Window的類型,有3種主要類型:
1)Application_windows (應用Window):
值在 FIRST_APPLICATION_WINDOW 和 LAST_APPLICATION_WINDOW 之間。 是通常的、頂層的應用程序窗口。必須將 token 設置成 activity 的 token 。
2)Sub_windows (子Window):
取值在 FIRST_SUB_WINDOW 和 LAST_SUB_WINDOW 之間。與頂層窗口相關聯,token 必須設置為它所附著的宿主窗口的 token。
3)System_windows (系統Window):
取值在 FIRST_SYSTEM_WINDOW 和 LAST_SYSTEM_WINDOW 之間。
3、Window的層次
每個Window都有對應的z-ordered,層次大的會覆蓋到層次小的Window上面。在三類Window中應用Window的層級范圍在1~99之間,子Window的范圍在1000~1999之間,系統Window的層級范圍在2000~2999之間。
要使Window位於所有Window的最頂層,采用較大的層級即可,系統Window的層級是最大的,一般選用TYPE_SYSTEM_OVERLAY或TYPE_SYSTEM_ERROR,同時要聲明權限android.permission.SYSTEM_ALERT_WINDOW。如下示例
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
4、WindowManger提供的常用方法
WindowManger繼承自ViewManager,提供了添加view、刪除view和更新view,這三個方法都是定義在ViewManager。
public interface ViewManager { /** * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }
Window是一個抽象的概念,每一個Window都對應著一個view和ViewRootImpl,Window與View通過ViewRootImpl建立起聯系,Window是以View作為實體存在,實際使用WindowMager訪問來Window,外部無法直接訪問Window。WindowManger提供了三個針對View的接口方法addView、updateViewLayout和removeView,分析Window的內部機制從Window的添加、更新和刪除開始。
Window的添加依賴於WindowManger,而WindowManger是一個接口,它的具體實現類是WindowMangerImpl,在WindowMangerImpl中實現了如下幾個操作view的方法
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, 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); } @Override public void removeViewImmediate(View view) { mGlobal.removeView(view, true); }
由上可知,WindowMangerImpl將操作view的實現都委托給了WindowManagerGlobal(即mGlobal),下面來看一下WindowManagerGlobal的addView方法,完整代碼如下
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { // 1、--- 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) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { // If there's no parent, then hardware acceleration for this view is // set from the application's hardware acceleration setting. 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; // 2、--- 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); } // 3、--- 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. } // 4、--- // 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); } } } // 5、--- root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } // 6、--- // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } }
上面addView方法大概做了如下幾件事:
1、檢查參數是否合法,並判斷當前添加的是否為子Window(parentWindow是否為空),若為子Window則為其做相關調整,否則為其開啟硬件加速
2、監視系統屬性的變化
3、通過findViewLocked獲取mViews中view的索引,看添加的view是否在mViews的集合裡,如果獲取的index>=0,此view存在,接著判斷要刪除的集合是否包含此view,若包含則直接執行doDie()刪除當前view,若不包含則會拋出異常(此view正在被刪除,還沒有完成)
4、判斷添加的是否為panel window,若是則找出以備後查
5、將Window的一系列參數添加到集合中,幾種集合如下:
mViews:存儲了所有Window所對應的View mRoots:存儲了所有Window所對應的ViewRootImpl mParams:存儲了所有Window所對應的布局參數 mDyingViews:存儲的是即將被刪除的View對象或正在被刪除的View對象
6、通過ViewRootImpl的setView方法來完成界面的更新,並完成Window的添加。
在setView內部會通過requestLayout方法來完成異步刷新請求,scheduleTraversals實際是View的繪制入口。
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
然後會接著執行如下代碼,WindowSession最終完成Window的添加,mWindowSession的類型是IWindowSession,它是一個Binder對象,真正的實現類是Session,因此Window的添加的過程是一個IPC調用
try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } 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(); } }
在Session內部會調用WindowManagerService的addWindow方法進行Window方法添加,具體的過程在WindowManagerService中實現了。WindowManagerService會為每個應用保留一個單獨的Session。
@Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outInputChannel); }
到此Window的添加就完成了。大致走了如下流程:
WindowManger -> WindowMangerImpl -> WindowManagerGlobal>addView -> ViewRootImpl>setView>requestLayout -> (IPC)Session>addToDisplay -> WindowMangerService>addWindow
刪除的過程與添加類似,通過WindowManagerGlobal來實現刪除,下面看它的removeView方法
public void removeView(View view, boolean immediate) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } synchronized (mLock) { int index = findViewLocked(view, true); View curView = mRoots.get(index).getView(); removeViewLocked(index, immediate); if (curView == view) { return; } throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView); } }
上述方法在要移除的view不為空的情況下,通過findViewLocked查找view在mViews(上述)中的索引,然後通過removeViewLocked進行刪除。看一下這兩個方法:
private int findViewLocked(View view, boolean required) { final int index = mViews.indexOf(view); if (required && index < 0) { throw new IllegalArgumentException("View=" + view + " not attached to window manager"); } return index; }
private void removeViewLocked(int index, boolean immediate) { ViewRootImpl root = mRoots.get(index); View view = root.getView(); if (view != null) { InputMethodManager imm = InputMethodManager.getInstance(); if (imm != null) { imm.windowDismissed(mViews.get(index).getWindowToken()); } } boolean deferred = root.die(immediate); if (view != null) { view.assignParent(null); if (deferred) { mDyingViews.add(view); } } }
從removeViewLocked方法可以看出,刪除操作是由ViewRootImpl來完成的,刪除分為兩種,分別為同步刪除(removeViewImmediate)和異步刪除(removeView),在ViewRootImpl的die(immediate)方法中進行判斷。如果為同步則直接調用doDie方法進行刪除,否則會發送一個消息進行異步處理,同時執行mDyingViews.add(view)
** * @param immediate True, do now if not in traversal. False, put on queue and do later. * @return True, request has been queued. False, request has been completed. */ boolean die(boolean immediate) { // Make sure we do execute immediately if we are in the middle of a traversal or the damage // done by dispatchDetachedFromWindow will cause havoc on return. if (immediate && !mIsInTraversal) { doDie(); return false; } if (!mIsDrawing) { destroyHardwareRenderer(); } else { Log.e(TAG, "Attempting to destroy the window while drawing!\n" + " window=" + this + ", title=" + mWindowAttributes.getTitle()); } mHandler.sendEmptyMessage(MSG_DIE); return true; }
在doDie方法內部調用dispatchDetachedFromWindow()方法刪除Window,最後調用WindowManagerGlobal的doRemoveView方法進行數據刷新,包括mRoots,mViews,mParams和mDyingViews,需要將當前Window所關聯的這三類對象從集合中刪除
void doDie() { checkThread(); synchronized (this) { if (mRemoved) { return; } mRemoved = true; if (mAdded) { dispatchDetachedFromWindow(); } ... WindowManagerGlobal.getInstance().doRemoveView(this); }
在dispatchDetachedFromWindow方法中真正執行刪除操作,內部作了如下幾件事:
1、垃圾回收的相關工作,如清理數據和消息、移除回調和監聽。 2、調用Wiew的dispatchDetachedFromWindow方法,它的方法內部會調用onDetachedFromWindow()方法,當view從Window被移除,此方法就會被調用,可以在此方法中做一些資源回收工作,諸如終止動畫、線程 3、通過Session的remove方法移除Window:mWindowSession.remove(mWindow),此過程是一個IPC過程,最終會調用WindowMangerService的removeWindow方法。
到此,Window的刪除過程就已經完成了,大致流程
WindowManger -> (實現類)WindowMangerImpl ->(委托類) WindowManagerGlobal>removeView>removeViewLocked -> ViewRootImpl>doDie>dispatchDetachedFromWindow -> (IPC) Session>remove -> WindowMangerService>removeWindow
同創建、刪除Window類似,更新Window的實施者依然是WindowManagerGlobal,下面看它的updateViewLayout方法
public void updateViewLayout(View view, ViewGroup.LayoutParams params) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; view.setLayoutParams(wparams); synchronized (mLock) { int index = findViewLocked(view, true); ViewRootImpl root = mRoots.get(index); mParams.remove(index); mParams.add(index, wparams); root.setLayoutParams(wparams, false); } }
更新過程首先要替換舊的params,接著通過ViewRootImpl的setLayoutParams方法進行更新ViewRootImpl中的params,在setLayoutParams方法內通scheduleTraversals進行Vew的重新布局(測量、布局、繪制),並會通過如下流程來更新Window的視圖
scheduleTraversals-> doTraversal -> performTraversals-> relayoutWindow-> mWindowSession.relayout -> mService.relayoutWindow
到此Window的更新就完成了,大致流程如下:
WindowManger -> WindowMangerImpl -> WindowManagerGlobal>updateViewLayout -> ViewRootImpl>setLayoutParams>scheduleTraversals>doTraversal>performTraversals>relayoutWindow -> (IPC)Session>relayout -> WindowMangerService>relayoutWindow
後記:此篇參考了安卓開發藝術探索,融入個人總結所成,如有錯誤請不吝賜教。特此說明。更多細節可查閱android源碼。
記錄下一個很實用的小控件EditTextWithDel,就是在Android系統的輸入框右邊加入一個小圖標,點擊小圖標可以清除輸入框裡面的內容,由於Android原生Ed
本Demo中所含功能1:定位,顯示當前位置2:地圖多覆蓋物(地圖描點,彈出該點的詳細信息)3:坐標地址互相換算4:POI興趣點檢索5:線路查詢(步行,駕車,公交)6:繪制
今天簡單講解一下PackageInstaller 文件路徑: 下面開始講解: 首先,我們說一下安裝apk的幾種方式,整體上可以分為2類,一類是有界面安裝,一
手機中保存並記錄著很多我們個人數據,比如浏覽器記錄、微信賬號、聊天記錄等,這些信息如果被有心人盯上自然後後患無窮。有時候我們會將手機借給好友,或是購新機後在