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类的定义: