Window 表示一個窗口的概念,是一個抽象類,它的具體實現是 PhoneWindow。
8.1 Window 和 WindowManager
WindowManager windowManager = getWindowManager();
Button button = new Button(this);
button.setText("一個按鈕");
mLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
windowManager.addView(button,mLayoutParams);
FLAG_NOT_TOUCH_MODAL
表示 Window 不需要獲取焦點,也不需要輸入時間,會啟用 FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給具有焦點的 Window。FLAG_NOT_TOUCH_MODAL
在此模式下,系統會將當前 Window 區域以外的點擊事件傳遞給底層的 Window,區域以內的單擊事件則自己處理。FLAG_SHOW_WHEN_LOCKED
開啟此模式可以讓 Winodw 顯示在鎖屏的界面上。
Type 參數表示 Window 的類型,Window 有三種類型:
應用 Window:對應這一個 Activity,層級范圍:1~99;
子 Window:子 Window 不能單獨存在,它需要附屬在特定的父 Window 中,比如 Dialog。層級范圍:1000~1999;
系統 Window:需要聲明權限才能創建,比如 Toast 和系統狀態欄。層級范圍:2000~2999;
層級大的覆蓋層級小的,對應這 WindowManager.LayoutParams 的type 參數。系統的 WindowManager 層級是最大的,一般我們可以選用 TYPE_SYSTEM_OVERLAY 或者 TYPE_SYSTEM_ERROR
mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;//對應值:2006
使用 TYPE_SYSTEM_ERROR 必須聲明權限
mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;//對應值:2010
...
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
WindowManager 所提供的功能,只有三個常用方法:
public interface ViewManager
{
// 添加 View
public void addView(View view, ViewGroup.LayoutParams params);
// 更新 View
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
// 移除 View
public void removeView(View view);
}
8.2 Window 的內部機制
Window 是一個抽象的概念,每一個 Window 都對應著一個 View 和 ViewRootImpl,Window 和 View 通過 View RootIml來建立聯系,因此 View 并不是實際存的,它是以 View 的形式存在,對 Window 的訪問必須通過 WindowManager。
8.2.1 Window 的添加過程
Window 的添加過程需要通過 WIndowManager 的 addView 來實現。
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
WindowManager 是一個接口,它的真正實現是 WindowManagerImpl。
public interface WindowManager extends ViewManager {
...
WindowManagerImpl 類中并沒有直接實現 Window 的三大操作,而是交給了 WindowManagerGlobal。
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@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);
}
...
WindowManagerImpl 這種工作米時候是典型的橋接模式,將所有方法都委托給 WindowManagerGlpbal 來實現。
- 檢查參數是否合法,如果是子 Window 那么還需要調整一些布局參數
public final class WindowManagerGlobal {
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;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
...
- 創建 ViewRootImpl 并將 View 添加到列表中
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) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// 將一系列對象添加到列表中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
- mViews:儲存的是所有Window 所對應的 View;
- mRoots:存儲的是所有 Window 所對應的 ViewRootImpl;
- ** mParams:**存儲的是所有 Window 的布局參數;
- mDyingViews:存儲了那些正在被刪除的 View 對象,調用 removeView 但是刪除操作還未完成的 Window 對象;
-
通過 ViewTootImpl 來更新界面并完成 Window 的添加過程
這個步驟由 ViewRootImpl 的 setView 方法來完成,在 setView 內部會通過 requestLayout 來完成異步刷新請求。
public final class WindowManagerImpl implements WindowManager {
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...
requestLayout();
....
scheduleTraversals 實際是 View 繪制的入口;
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
接著會通過 WindowSession 來最終完成 Window 的添加過程,WindowSession 是一個 Binder 對象,真正的實現類是 Session,也就是 Window 的添加過程是一次 IPC 調用。在Session 內部會通過 WIndowManagerService 來實現 Window 的添加。 如此一來,Window 的添加請求就交給 WindowManagerService 去處理了。WindowManagerService 內部就不用去深入分析了。(此處看不到源碼。)
8.2.2 Window 的刪除過程
Window 的刪除過程和添加過程一樣,都是先通過 WIndowManagerImpl 后,在進一步通過 WindowManagerGlobal 來實現的。
public final class WindowManagerGlobal {
...
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);
}
}
removeView 的邏輯很清晰,首先通過 findViewLocked 來查找待刪除的 View 的索引,過程就是調用數組遍歷,再調用 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);
//異步刪除時,添加到 mDyingViews 中
if (deferred) {
mDyingViews.add(view);
}
}
}
removeViewLocked 是通過 ViewRootImpl 來完成刪除操作的,在 WindowManager 中提供兩種刪除接口:
removeView:異步刪除(常用)
在 die 方法中異步刪除,那么就發送一個 MSG_DIE 的消息,ViewRootImpl 中的 Handler 會處理此消息并調用 doDie 方法。removeViewImmediate:同步刪除(基本用不到)
在 die 方法中同步刪除,會直接就直接調用 doDie 方法。
這里主要說明異步刪除,具體的刪除操作由 ViewRootImpl 的 die 方法來完成,這里異步刪除會返回到 removeViewLocked 方法中繼續處理,而同步刪除則不會返回。
boolean die(boolean immediate) {
// 同步刪除
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
// 異步刪除
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
在 doDie 方法內部會調用 dispatchDetachedFromWindow 方法,真正的刪除 View 的邏輯在該方法內部實現,該方法主要做四件事:
- 垃圾回收相關的工作,比如清除數據和消息、移除回調。
- 通過 Session 的 remove 方法刪除 Window:mWindowSession.remove(mWindw),這同樣是一個 IPC 過程,最終會調用 WindowManagerService 的 removeWindow 方法。
- 調用 VIew 的dispatahDetachedFromWindow 方法,在內部會調用 View 的onDetachedFromWIndow()以及 onDetachedFromWindowInternal()。(onDetachedFromWIndow 當 View 被移除時會被調用)
- 調用 WindowManagerGlobal 的 doRemoveView 方法刷新數據,包括 mRoots、mParams、mDyingViews,需要將當前 Window 所關聯的這三類對象從列表中刪除。
8.2.3 Window 的更新過程
public final class WindowManagerGlobal {
...
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);
}
}
updateViewLayout 的步驟:
- 更新 View 的 LayoutParams 并替換掉老的 LayoutParams。
- 再更新 ViewRootImpl 中的 LayoutParanms,這一步通過 ViewRootImpl 的 setLayoutParams 方法來實現。
ViewRootImpl 的 setLayoutParams 方法會通過 scheduleTraversals 方法來對 View 重新布局,包括測量、布局、重繪這三個過程。ViewRootImpl 還會通過 WIndowSession 來更新 Window 的視圖,這過程由 WindowManagerService 的 relayoutWindow()來具體實現,是個 IPC 過程。
8.3 Window 的創建過程
有視圖的地方就有 Window
8.3.1 Activity 的 Window 創建過程
要分析 Activity 中的 Window 創建過程就必須立交 Activity 的啟動過程,這里先大概了解即可。Activity 的啟動過程很復雜,最終會由 ActivityThread 中的 performLaunchActivity來完成整個啟動過程,在這個方法內部會通過類加載器創建 Activity 的實例對象,并調用其 attach 方法為其關聯運行過程中所依賴的一系列上下文環境變量。
public final class ActivityThread {
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
if (activity != null) {
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window);
在 Activity 的 attch 方法里,系統會創建 Activity 所屬的 Window 對象并為其設置回調接口,Window 對象的創建是通過 PolicyManager 的makeNewWindow 方法實現的。由于 Activity 實現了 Window 的 Callback 接口,因此當 WIndow 接收到外界的狀態改變時就會回調 Activity 的方法。Callback 接口中的方法有很多,比如 onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent等待。
Activity 的 Window 是通過 PolicyManager 的一個工廠方法來創建的,但是從 PolicayManager 的類名可以看出,他不是一個普通的類,它是一個策略類。
public interface IPolicy {
public Window makeNewWindow(Context context);
public LayoutInflater makeNewLayoutInflater(Context context);
public WindowManagerPolicy makeNewWindowManager();
public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}
在實際的調用中,PolicyManager是如何關聯到Policy類中的mackNewWindow方法來實現如下,由此可以發現,window的具體實現的確就是phoneWindow
public static Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
關于PolicyManager是如何關聯到IPolicy中,這個無法從源碼中調用關系得到,這里的猜測可能是編譯環節動態控制的,到這里window已經創建完成了,下面分析activity的視圖是怎么依附在window上的由于activity的視圖是由setContentView開始的,所有我們先看下這個方法:
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow 的 setContentView 方法大致遵循如下幾個步驟:
-
如果沒有 DecorView,那么就創建它
DecorView 是一個 FrameLayout,是Activity 的頂級 View,包含標題欄和內容欄。DecorView 的創建過程由 installDecor 方法來完成,在方法內部會通過 generateDecor 方法來直接創建 DecorView,這時候 DecorView 還只是一個空白的 FrameLayout:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
...
protected DecorView generateDecor(int featureId) {
....
return new DecorView(context, featureId, this, getAttributes());
}
為了初始化 DecorView 的結構,PhoneWIndow還需要通過 generateLayout 方法來加載具體的布局文件到 DecorView 中,具體的布局文件和系統版本以及主題有關。
protected ViewGroup generateLayout(DecorView decor) {
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
其其中 ID_ANDROID_CONTENT 的定義如下,這個id對應的就是 ViewGroup 的 mContentParent。
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
-
將 View 添加到 DecorView 的 mContentParent 中
這一步直接將 Activity 的視圖添加到 DecorView 的 mContentParent 中即可:mLayoutInflater.inflate(layoutResID, mContentParent)。 -
回調 Activity 的 onCreateChanged 方法來通知 Activity 視圖已經發生改變
由于 Activity 實現了 Window 的 Callback 接口,這里表示 Activity 的布局文件以及被添加到 DecorVIew 的 mContentParent 中了,于是需要通知 Acitivity 可以做相應的處理。 Activity 的 onContentChanged 方法是個空實現,我們可以在子 Activity 中處理這個回調。
@Override
public void setContentView(int layoutResID) {
....
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
...
}
經過上面的三個步驟,到這里為止 DecorView 以及被創建并初始化完畢,Activity 的布局文件也以及成功添加到了 DecorView 的 mContentParent 中,但是這個時候DecorView還沒有被windowmanager添加到window中,這里需要正確的理解window的概念,window更多的是表示一種抽象的功能集合,雖然說早在activity的attch中window就已經被創建了,但是這個時候由于DecorView還沒有被windowmanager識別,所有還不能提供具體的功能,因為他還無法接收外界的輸入,在 ActivityThread 的 handleResumeActivity 方法,首先會調用 Acitivity 的onResume 方法沒接調用 makeVisible 方法 。 在activityThread的makeVisible中,DecorView 才正在地完成了添加和顯示兩個過程,到這里 Activity 的視圖才能被用戶看到:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
8.3.2 Dialog 的 Window 創建過程
Dialog 的 Window 創建過程和 Activity 類似,有幾個步驟:
- 創建 Window
public class Dialog implements....{
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
...
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//創建 PhoneWindow
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
- 初始化 DecorView 并將 Dialog 的視圖添加到 DecorView 中
public void setContentView(@NonNull View view) {
mWindow.setContentView(view);
}
-
將 DecorView 添加到 Window 中并顯示
在 Dialog 的 show 方法中,會通過 WindowManager 將 DecorView 添加到 Window 中。
public void show() {
...
mWindowManager.addView(mDecor, l);
mShowing = true;
當 Dialog 被關閉時,它會通過 WIndowManager 來移除 DecorView。
void dismissDialog() {
...
mWindowManager.removeViewImmediate(mDecor);
普通 Dialog 必須采用 Activity 的 Context,如果采用 Application 的Context,那么會報錯。
Dialog dialog = new Dialog(this.getApplicationContext());
TextView textView = new TextView(this);
textView.setText("this is a toast");
dialog.setContentView(textView);
dialog.show()
錯誤信息提示:是沒有應用 token 所導致,而應用 token 一般只有 Activity 擁有。另外系統 Window 它可以不需要 token,只需要設定一下 Window 的層級范圍為 2000~2999即可:
mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;//對應值:2010
...
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
8.3.3 Toast 的 Window 創建過程
Toast 也是基于 Window 來實現的,但是由于 Toast 具有定時取消這一功能,所以系統采用了 Handler。在 Toast 的內部有兩類 IPC 過程。
- 第一類:Toast 訪問 NotificaitonManagerService(簡稱:NMS)
- 第二類:NotificationManagerService 回調 Toast 里的 TN 接口
Toast 屬于系統 Window,它內部的視圖由兩種方式指定:
- 一種是系統默認
- 一種是通過 setView 方法來指定一個自定義 View
他們都對應 Toast 的一個View 類型的內部成員 mNewView。Toast 提供了 show 和cancel ,他們內部是一個 IPC 過程。
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
public void cancel() {
mTN.hide();
try {
getService().cancelToast(mContext.getPackageName(), mTN);
} catch (RemoteException e) {
// Empty
}
}
Toast 的 show 和 cancel 都要通過 NMS 來實現,NMS 運行在系統的進程中,所以只能通過遠程調用的方式來顯示和隱藏 Toast。TN 這個類是一個Binder 類,在 Toast 和 NMS 進行 IPC 的過程中,當 NMS 處理 Toast 的請求時會跨進程回調 TN 中的方法,由于 TN 運行在 Binder 線程池中,所以需要通過 Handler 將其切換當發送 Toast 請求所在的線程。這里使用 Handler ,意味著 Toast 無法在沒有 Looper 的線程中彈出,因為 Handler 需要 Looper 才能完成切換線程的功能。
Toast 的 show 過程。它調用了 enqueneToast 方法:
INotificationManager service = getService();
String pkg = mContext.getPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
// 參數 :包名、遠程回調、Toast 的時常
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
enqueneToast 首先將 Toast 請求封裝為 ToastRecord 對象并將其添加到一個名為 mToastQuene 的隊列中。mToastRecord 其實是一個 ArrayList。對于非系統應用來說,mToastQuene 中最多能同時存在 50 個 ToastRecord ,這樣做是防止 Dos(Denial of Service),防止其他應用沒有機會彈出 Toast。
此處無源碼(要翻墻),摘一條重要的
final ToastRecord r = mToastQueue.get(i);
正常情況下,一個應用不可能達到上限,當ToastRecord被添加到mToastQueue中后,NMS就會通過showNextToastLocked方法來顯示Toast,下面的代碼很好理解,需要注意的是,Toast的顯示是由ToastRecord的callback來完成的,這個callback實際上就是TN對象的遠程Binder,通過callback來訪問TN中的方法是需要跨進程來完成的,最終被調用的對象是TN中方法發起在Binder線程池中。
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
try {
record.callback.show(record.token);
scheduleTimeoutLocked(record);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to show notification " + record.callback
+ " in package " + record.pkg);
// remove it from the list and let the process die
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
keepProcessAliveIfNeededLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
Toast 顯示以后,NMS 還會通過 scheduleTimeoutLocked 方法來發送一個延時消息:
private void scheduleTimeoutLocked(ToastRecord r)
{
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
mHandler.sendMessageDelayed(m, delay);
}
LONG_DELAY 是3.5s,SHORT_DELAY 是 2s,NMS會通過 cancelToastLocked來隱藏 toast 并且清除隊列
通過上面的分析,大家知道toast的顯示隱藏實際上是toast的TN這個類來實現的,分別對應的show/hide,由于這兩個方法都是NMS以跨進程的方式調用的,因此他運行在Binder池中,為了切換,在他們內部使用了handler:
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = ()—>{ handleShow(); } };
final Runnable mHide = ()—> { handleHide();
mNextView = null;
}
};
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
上述代碼中,mShow 和 mHide 是兩個 Runnable,他們內部分別調用 handleShow 和 handleHide 方法。TN 的 handleShow 中會將 Toast 的視圖添加到 Window 中:
public void handleShow() {
...
if (mView != mNextView) {
handleHide();
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
...
mWM.addView(mView, mParams);
...
}
}
而 TN 的handleHide 中會將 Toast 的視圖從 Window 中移除:
public void handleHide() {
if (mView != null) {
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
mView = null;
}
}