編輯:關於Android編程
上幾篇文章中我們分析了Dialog的加載繪制流程,也分析了Acvityi的加載繪制流程,說白了Android系統中窗口的展示都是通過Window對象控制,通過ViewRootImpl對象執行繪制操作來完成的,那麼窗口的取消繪制流程是怎麼樣的呢?這篇文章就以Dialog為例說明Window窗口是如何取消繪制的。
有的同學可能會問前幾篇文章介紹Activity的加載繪制流程的時候為何沒有講Activity的窗口取消流程,這裡說明一下。那是因為當時說明的重點是Activity的加載與繪制流程,而取消繪制流程由於混雜在Activity的生命周期管理,可能不太明顯,所以這裡將Window窗口的取消繪制流程放在Dialog中,其實他們的取消繪制流程都是相似的,看完Dialog的取消繪制流程之後,再看一下Activity的取消繪制流程就很簡單了。
還記得我們上一篇文章關於Dialog的例子麼?我們通過AlertDialog.Builder創建了一個AlertDialog,並通過Activity中的按鈕點擊事件來顯示這個AlertDialog,而在AlertDialog中定義了一個“知道了”按鈕,點擊這個按鈕就會觸發alertDialog.cancel方法,通過執行這個方法,我們的alertDialog就不在顯示了,很明顯的,cancel方法執行過程中就執行了取消繪制的邏輯,這裡我們先看一下我們的例子核心代碼:
title.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this.getApplication());
builder.setIcon(R.mipmap.ic_launcher);
builder.setMessage("this is the content view!!!");
builder.setTitle("this is the title view!!!");
builder.setView(R.layout.activity_second);
builder.setPositiveButton("知道了", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
alertDialog.cannel();
}
});
alertDialog = builder.create();
alertDialog.show();
}
});
這裡的title就是我們自己的Activity中的一個TextView,通過注冊這個TextView的點擊事件,來顯示一個AlertDialog,通過注冊AlertDialog中按鈕的點擊事件,執行alertDialog的cancel方法。
好吧,看一下Dialog的cannel方法的具體實現:
public void cancel() {
if (!mCanceled && mCancelMessage != null) {
mCanceled = true;
// Obtain a new message so this dialog can be re-used
Message.obtain(mCancelMessage).sendToTarget();
}
dismiss();
}
可以看到方法體中,若當前Dialog沒有取消,並且設置了取消message,則調用Message.obtain(mCancel).sendToTarget(),前面已經分析過這裡的sendToTarget方法會回調我們注冊的異步消息處理邏輯:
public void setOnCancelListener(final OnCancelListener listener) {
if (mCancelAndDismissTaken != null) {
throw new IllegalStateException(
"OnCancelListener is already taken by "
+ mCancelAndDismissTaken + " and can not be replaced.");
}
if (listener != null) {
mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
} else {
mCancelMessage = null;
}
}
可以看到如果我們在初始化AlertDialog.Builder時,設置了setOnCancelListener,那麼我們就會執行mListenersHandler的異步消息處理,好吧,這裡看一下mListenersHandler的定義:
private static final class ListenersHandler extends Handler {
private WeakReference mDialog;
public ListenersHandler(Dialog dialog) {
mDialog = new WeakReference(dialog);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DISMISS:
((OnDismissListener) msg.obj).onDismiss(mDialog.get());
break;
case CANCEL:
((OnCancelListener) msg.obj).onCancel(mDialog.get());
break;
case SHOW:
((OnShowListener) msg.obj).onShow(mDialog.get());
break;
}
}
}
好吧,這裡調用的是設置的OnCancelListener的onCancel方法,也就是說我們調用dialog.cancel方法時首先會判斷dialog是否調用了setOnCancelListener若設置了,則先調用OnCancelListener的onCancel方法,然後再次執行dismiss方法,若我們沒有為Dialog.Builder設置OnCancelListener那麼cancel方法和dismiss方法是等效的。
這樣,我們來看一下dismiss方法的實現邏輯:
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
可以看到,這裡首先判斷當前線程的Looper是否是主線程的Looper(由於mHandler是在主線程中創建的,所以mHandler.getLooper返回的是主線程中創建的Looper對象),若是的話,則直接執行dismissDialog()方法,否則的話,通過mHandler發送異步消息至主線程中,簡單來說就是判斷當前線程是否是主線程,若是主線程則執行dismissDialog方法否則發送異步消息,我們看一下mHandler對異步消息的處理機制,由於這裡的mDismissAction是一個Runnable對象,所以這裡直接看一下mDismissAction的定義:
private final Runnable mDismissAction = new Runnable() {
public void run() {
dismissDialog();
}
};
好吧,這裡的異步消息最終也是調用的dismissDialog方法。。。。
所以無論我們執行的cancel方法還是dismiss方法,無論我們方法是在主線程執行還是子線程中執行,最終調用的都是dismissDialog方法,那麼就看一下dismissDialog是怎麼個執行邏輯。
void dismissDialog() {
if (mDecor == null || !mShowing) {
return;
}
if (mWindow.isDestroyed()) {
Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
return;
}
try {
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}
好吧,看樣子代碼還不是特別多,方法體中,首先判斷當前的mDector是否為空,或者當前Dialog是否在顯示,若為空或者沒有在顯示,則直接return掉,也就是說當前我們的dialog已經不再顯示了,則我們不需要往下在執行。
然後我們調用了mWindow.isDestroyed()方法,判斷Window對象是否已經被銷毀,若已經被銷毀,則直接return,並打印錯誤日志。
然後我們調用了mWindowManager.removeViewImmediate(mDector),這裡的mDector是我們Dialog窗口的根布局,看這個方法的名字應該就是Dialog去除根布局的操作了,可以看一下這個方法的具體實現。前幾篇文章中我們已經分析過了這裡的mWindowManager其實是WindowManagerImpl的實例,所以這裡的removeViewImmediate方法應該是WindowManagerImpl中的方法,我們看一下它的具體實現:
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
可以發現,這裡它調用了mGlobal.removeView方法,而這裡的mGlobal是WindowManagerGlobal的實例,所以我們再看一下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);
}
}
可以發現,這裡在獲取了保存的mDector組件之後,又調用了removeViewLocked方法,我們在看一下這個方法的具體實現邏輯:
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);
}
}
}
看到了麼,我們獲取了mDector組件的ViewRootImpl,然後調用了其的die方法,通過這個方法實現Window組件的銷毀流程。
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方法,看名字應該就是真正執行window銷毀工作的方法了,我們在看一下doDie方法的具體實現:
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
dispatchDetachedFromWindow();
}
if (mAdded && !mFirst) {
destroyHardwareRenderer();
if (mView != null) {
int viewVisibility = mView.getVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
if (mWindowAttributesChanged || viewVisibilityChanged) {
// If layout params have been changed, first give them
// to the window manager to make sure it has the correct
// animation info.
try {
if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mWindowSession.finishDrawing(mWindow);
}
} catch (RemoteException e) {
}
}
mSurface.release();
}
}
mAdded = false;
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
可以看到方法體中,首先調用了checkThread方法,介紹Activity的繪制流程的時候有過介紹,判斷當前執行代碼的線程,若不是主線程,則拋出異常:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
我們順著doDie的方法往下看,又調用了dispatchDetachedFromWindow()方法,這個方法主要是銷毀Window中的各中成員變量,臨時變量等
void dispatchDetachedFromWindow() {
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
}
mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mAccessibilityManager.removeHighTextContrastStateChangeListener(
mHighContrastTextManager);
removeSendWindowContentChangedCallback();
destroyHardwareRenderer();
setAccessibilityFocus(null, null);
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
mSurface.release();
if (mInputQueueCallback != null && mInputQueue != null) {
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
mInputQueue.dispose();
mInputQueueCallback = null;
mInputQueue = null;
}
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
try {
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
// Dispose the input channel after removing the window so the Window Manager
// doesn't interpret the input channel being closed as an abnormal termination.
if (mInputChannel != null) {
mInputChannel.dispose();
mInputChannel = null;
}
mDisplayManager.unregisterDisplayListener(mDisplayListener);
unscheduleTraversals();
}
可以看到我們在方法中調用了mView.dispatchDetachedFromWindow方法,這個方法的作用就是將mView從Window中detach出來,我們可以看一下這個方法的具體實現:
void dispatchDetachedFromWindow() {
AttachInfo info = mAttachInfo;
if (info != null) {
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(GONE);
}
}
onDetachedFromWindow();
onDetachedFromWindowInternal();
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
imm.onViewDetachedFromWindow(this);
}
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewDetachedFromWindow(this);
}
}
if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
mAttachInfo.mScrollContainers.remove(this);
mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
}
mAttachInfo = null;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchDetachedFromWindow();
}
}
其中onDetachedFromWindow方法是一個空的回調方法,這裡我們重點看一下onDetachedFromWindowInternal方法:
protected void onDetachedFromWindowInternal() {
mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
removeUnsetPressCallback();
removeLongPressCallback();
removePerformClickCallback();
removeSendViewScrolledAccessibilityEventCallback();
stopNestedScroll();
// Anything that started animating right before detach should already
// be in its final state when re-attached.
jumpDrawablesToCurrentState();
destroyDrawingCache();
cleanupDraw();
mCurrentAnimation = null;
}
onDetachedFromWindowInternal方法的方法體也不是特別長,都是一些調用函數,這裡看一下destropDrawingCache方法,這個方法主要是銷毀View的緩存Drawing,我們來看一下具體實現:
public void destroyDrawingCache() {
if (mDrawingCache != null) {
mDrawingCache.recycle();
mDrawingCache = null;
}
if (mUnscaledDrawingCache != null) {
mUnscaledDrawingCache.recycle();
mUnscaledDrawingCache = null;
}
}
這裡的mDrawingCache其實就是一個Bitmap類型的成員變量,而這裡調用的recycler和置空操作其實就是把View中執行draw方法之後緩存的bitmap清空。
這裡需要說明的是,我們View組件的最終顯示落實是通過draw方法實現繪制的,而我們的draw方法的參數是一個Canvas,這是一個畫布的對象,通過draw方法就是操作這個對象並顯示出來,而Canvas對象之所以能夠實現顯示的效果是因為其內部保存著一個Bitmap對象,通過操作Canvas對象實質上是操作Canvas對象內部的Bitmap對象,而View組件的顯示也就是通過這裡的Bitmap來實現的。
而我們上文中置空了bitmap對象就相當於把View組件的顯示效果置空了,就是相當於我們取消了View的draw方法的執行效果,繼續回到我們的dispatchDetachedFromWindow方法,在執行了mView.dispatchDetachedFromWindow()方法之後,又調用了mView = null;方法,這裡設置mView為空,這樣我們有取消了View的meature和layouot的執行效果。
這樣經過一系列的操作之後我們的Dialog的取消繪制流程就結束了,現在我們來看一下Activity的取消繪制流程。還記得我們“Activity的銷毀流程”麼?
當我們調用activity的finish方法的時候回調用ActivityThread的handleDestroyActivity方法,我們來看一下這個方法的實現:
private void handleDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
...
wm.removeViewImmediate(v);
...
}
可以看到這裡調用了這裡調用了wm.removeViewImmediate方法,這個方法不就是我們剛剛分析Dialog銷毀繪制流程的起始方法麼?以後的邏輯都是詳細的,這樣我們就實現了Activity的取消繪制流程。
總結:
窗口的取消繪制流程是相似的,包括Activity和Dialog等;
通過調用WindowManager.removeViewImmediate方法,開始執行Window窗口的取消繪制流程;
Window窗口的取消繪制流程,通過清空bitma撤銷draw的執行效果,通過置空View撤銷meature和layout的執行效果;
前言:Activity生命周期是每一個Android開發者接觸Android之始就會學習的東西,每個人都會說出點什麼,我想任何一個經驗老道高級語言開發程序員談到生命周期這
Android APP 的運行環境Android 是一款基於 Linux 內核,面向移動終端的操作系統。為適應其作為移動平台操作系統的特殊需要,谷歌對其做了特
根據人眼視覺殘留現象,連續播放一些列的圖像,形成動畫效果。Android中的動畫:游戲:利用自定義View的繪制方法,開啟線程頻繁的刷新界面,形成動畫; Android
一:在Android程序開發中,我們經常會去用到Shape這個東西去定義各種各樣的形狀,首先我們了解一下Shape下面有哪些標簽,都代表什麼意思:(1).solid:填充