編輯:關於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源碼。
Android帶清除功能的輸入框控件EditTextWithDel
記錄下一個很實用的小控件EditTextWithDel,就是在Android系統的輸入框右邊加入一個小圖標,點擊小圖標可以清除輸入框裡面的內容,由於Android原生Ed
Android集成百度地圖SDK
本Demo中所含功能1:定位,顯示當前位置2:地圖多覆蓋物(地圖描點,彈出該點的詳細信息)3:坐標地址互相換算4:POI興趣點檢索5:線路查詢(步行,駕車,公交)6:繪制
android PackageInstaller那點事兒
今天簡單講解一下PackageInstaller 文件路徑: 下面開始講解: 首先,我們說一下安裝apk的幾種方式,整體上可以分為2類,一類是有界面安裝,一
如何徹底清除Android手機的使用痕跡
手機中保存並記錄著很多我們個人數據,比如浏覽器記錄、微信賬號、聊天記錄等,這些信息如果被有心人盯上自然後後患無窮。有時候我們會將手機借給好友,或是購新機後在