03.DialogFragment源碼分析android
05.PopupWindow源碼分析github
07.彈窗常見問題segmentfault
08.Builder模式markdown
Window是什麼?app
如何經過WindowManager添加Window(代碼實現)?異步
以下所示ide
//1. 控件 Button button = new Button(this); button.setText("Window Button"); //2. 佈局參數 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT); layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; layoutParams.gravity = Gravity.LEFT | Gravity.TOP; layoutParams.x = 100; layoutParams.y = 300; // 必需要有type否則會異常: the specified window type 0 is not valid layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; //3. 獲取WindowManager並添加控件到Window中 WindowManager windowManager = getWindowManager(); windowManager.addView(button, layoutParams);
WindowManager的主要功能是什麼?
添加、更新、刪除View
public interface ViewManager{ public void addView(View view, ViewGroup.LayoutParams params); //添加View public void updateViewLayout(View view, ViewGroup.LayoutParams params); //更新View public void removeView(View view); //刪除View }
Window概念解析?
WindowSession的建立過程是怎樣的?
WindowSession的做用?博客
Token的使用場景?
Token是什麼?
主要分兩種Token:
Activity中的Token
Activity、View、Window三者之間的關係
Window有哪幾種類型
Activity 與 PhoneWindow 與 DecorView 關係圖
Activity的啓動過程是怎樣的?
Activity的視圖加載的源碼分析
Dialog的Window建立過程
將DecorView添加到Window中顯示。和Activity同樣,都是在自身要出如今前臺時纔會將添加Window。
使用中遇到的問題
解決的辦法
建立工具類: /** * 吐司工具類 避免點擊屢次致使吐司屢次,最後致使Toast就長時間關閉不掉了 * @param context
*/ private static Toast toast; public static void showToast(Context context, String content) { if (toast == null) { toast = Toast.makeText(context.getApplicationContext(), content, Toast.LENGTH_SHORT); } else { toast.setText(content); } toast.show(); } ```
這樣用的原理
DecorView什麼時候才被WindowManager真正添加到Window中?
Window的addView源碼分析?
Window的remove源碼與解析
dispatchDetachedFromWindow:博客
Dialog的Window建立過程?
爲何Dialog不能用Application的Context?
什麼是DecorView
如何獲取到DecorView
ViewGroup content = (ViewGroup)findViewById(android.R.id.content); ViewGroup rootView = (ViewGroup) content.getChildAt(0);
DecorView的職責是什麼
DecorView如何被加載到Window中?博客
經過setContentView()設置的界面,爲何在onResume()以後纔對用戶可見呢?這就要從ActivityThread開始提及。
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { //就是在這裏調用了Activity.attach()呀,接着調用了Activity.onCreate()和Activity.onStart()生命週期, //可是因爲只是初始化了mDecor,添加了佈局文件,尚未把 //mDecor添加到負責UI顯示的PhoneWindow中,因此這時候對用戶來講,是不可見的 Activity a = performLaunchActivity(r, customIntent); ...... if (a != null) { //這裏面執行了Activity.onResume() handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed); if (!r.activity.mFinished && r.startsNotResumed) { try { r.activity.mCalled = false; //執行Activity.onPause() mInstrumentation.callActivityOnPause(r.activity); } } } }
重點看下handleResumeActivity(),在這其中,DecorView將會顯示出來,同時重要的一個角色:ViewRoot也將登場。
當咱們執行了Activity.makeVisible()方法以後,界面纔對咱們是可見的。博客
void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes());//將DecorView添加到WindowManager mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE);//DecorView可見 }
wm.addView(mDecor, getWindow().getAttributes());
起到了重要的做用,由於其內部建立了一個ViewRootImpl對象,負責繪製顯示各個子View。最後經過WindowManagerImpl的addView方法將DecorView加載出來
什麼是ViewRoot
ViewRoot屬於View樹的一份子嗎?
下面結構圖能夠清晰的揭示四者之間的關係:
吐司爲什麼會出現內存泄漏
在Toast構造方法中建立NT對象是幹什麼用的?
在構造方法中,建立了NT對象,那麼有人便會問,NT是什麼東西呢?看看NT的源碼,能夠發現NT實現了ITransientNotification.Stub,提到這個感受是否是很熟悉,沒錯,在aidl中就會用到這個。
public Toast(Context context) { mContext = context; mTN = new TN(); mTN.mY = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.toast_y_offset); mTN.mGravity = context.getResources().getInteger( com.android.internal.R.integer.config_toastDefaultGravity); }
在TN類中,能夠看到,實現了AIDL的show與hide方法
@Override public void show(IBinder windowToken) { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.obtainMessage(0, windowToken).sendToTarget(); } @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); }
接着看下這個ITransientNotification.aidl文件
/** @hide */ oneway interface ITransientNotification { void show(); void hide(); }
Toast是怎麼show出來的?
連續吐司是如何肯定吐司的前後順序?
主要是說一下showNextToastLocked()方法中的源代碼
爲何Toast執行show後過了一下子就自動銷燬?博客
public void handleHide() { if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); if (mView != null) { // note: checking parent() just to make sure the view has // been added... i have seen cases where we get here when // the view isn't yet added, so let's try not to crash. if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeViewImmediate(mView); } mView = null; } }
如何理解普通應用的Toast顯示數量是有限制的?
final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
private static boolean isUidSystem(int uid) { final int appid = UserHandle.getAppId(uid); return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); } private static boolean isCallerSystem() { return isUidSystem(Binder.getCallingUid()); }
爲何要判斷是不是系統吐司?
爲什麼Activity銷燬後Toast仍會顯示
爲何說Toast儘可能用全局上下文?
說一下Toast的顯示和隱藏重點邏輯,說下你的理解?博客
Toast偶爾報錯Unable to add window
報錯日誌,是否是有點眼熟呀?更多能夠看個人開源項目:https://github.com/yangchong211
android.view.WindowManager$BadTokenException Unable to add window -- token android.os.BinderProxy@7f652b2 is not valid; is your activity running?
查詢報錯日誌是從哪裏來的
發生該異常的緣由
Toast.makeText(this,"瀟湘劍雨-yc",Toast.LENGTH_SHORT).show(); try { Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); }
解決辦法,目前見過好幾種,思考一下那種比較好……
第二種,拋出異常增長try-catch,代碼以下所示,最後仍然沒法解決問題
哪些狀況會發生該問題?
Toast運行在子線程問題
new Thread(new Runnable() { @Override public void run() { ToastUtils.showRoundRectToast("瀟湘劍雨-楊充"); } }).start();
子線程中吐司的正確作法,代碼以下所示
new Thread(new Runnable() { @Override public void run() { Looper.prepare(); ToastUtils.showRoundRectToast("瀟湘劍雨-楊充"); Looper.loop(); } }).start();
得出的結論
爲何建議用DialogFragment替代Dialog
如何定義DialogFragment樣式
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (local == BOTTOM) { setStyle(DialogFragment.STYLE_NO_TITLE, R.style.BottomDialog); } else if (local == CENTER || local == TOP) { setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog); } }
建立theme主題樣式,而且進行設置
注意一下style常量,這裏只是展現經常使用的。
STYLE_NORMAL:會顯示一個普通的dialog STYLE_NO_TITLE:不帶標題的dialog STYLE_NO_FRAME:無框的dialog STYLE_NO_INPUT:沒法輸入內容的dialog,即不接收輸入的焦點,並且觸摸無效。
注意動畫設置以下所示
<style name="CenterDialog" parent="@android:style/Theme.Dialog"> <item name="android:windowTitleStyle">@null</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:colorBackgroundCacheHint">@null</item> <item name="android:windowAnimationStyle">@style/CenterDialogAnimationStyle</item> <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item> </style>
使用dialogFragment有何好處?
Dialog的Window建立過程是怎樣的?
爲何Dialog不能用Application的Context,說一下緣由?
Dialog和Window有什麼關係?
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (themeResId == 0) { final TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true); themeResId = outValue.resourceId; } //建立一個Context mContext = new ContextThemeWrapper(context, themeResId); } else { mContext = context; } //獲取一個WindowManager對象 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); //建立一個Window對象 final Window w = new PhoneWindow(mContext); //將Window對象w賦值給mWindow mWindow = w; //爲Windowd對象設置回調,而且它自己實現了這些回調函數 w.setCallback(this); w.setOnWindowDismissedCallback(this); //爲Window對象設置WindowManager對象 w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); //建立一個對話框監聽Handler mListenersHandler = new ListenersHandler(this); }
Dialog的dismiss和cancel()方法均可銷燬彈窗,它們有什麼區別?
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(); } public void setOnCancelListener(final OnCancelListener listener) { if (listener != null) { mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener); } else { mCancelMessage = null; } } private static final class ListenersHandler extends Handler { private WeakReference<DialogInterface> mDialog; public ListenersHandler(Dialog dialog) { mDialog = new WeakReference<DialogInterface>(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; } } }
dismiss方法主要是作了什麼?
public void dismiss() { if (Looper.myLooper() == mHandler.getLooper()) { dismissDialog(); } else { mHandler.post(mDismissAction); } }
PopupWindow中不設置爲何必須設置寬高?
先看問題代碼,下面這個不會出現彈窗,思考:爲何?
PopupWindow popupWindow = new PopupWindow(this); View inflate = LayoutInflater.from(this).inflate(R.layout.view_pop_custom, null); popupWindow.setContentView(inflate); popupWindow.setAnimationStyle(R.style.BottomDialog); popupWindow.showAsDropDown(mTv1);
注意:必須設置寬和高,不然不顯示任何東西
PopupWindow和Dialog有什麼區別?
說下建立和銷燬的大概流程?
爲什麼彈窗點擊一下就dismiss呢?
Snackbar與吐司有何區別
Snackbar控件show時爲什麼從下往上移出來?
爲何顯示在最下面?
Snackbar顯示會致使FloatingActionButton上移?
Snackbar負責顯示和消失,具體來講其實就是添加和移除View的過程。Snackbar和SnackbarManager的設計很巧妙,利用一個SnackbarRecord對象保存Snackbar的顯示時間以及SnackbarManager.Callback對象,前面說到每個Snackbar都有一個叫作mManagerCallback的SnackbarManager.Callback對象,下面看一下SnackRecord類的定義: