目录介绍
https://segmentfault.com/a/1190000019111141
02.Toast源码深度分析
03.DialogFragment源码分析
04.Dialog源码分析
05.PopupWindow源码分析
06.Snackbar源码分析
07.弹窗常见问题
08.Builder模式
10.0.0.1 Window是什么?如何通过WindowManager添加Window(代码实现)?WindowManager的主要功能是什么?
Window是什么?
如何通过WindowManager添加Window(代码实现)?
如下所示
//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
}
10.0.0.2 Window概念解析?WindowSession的创建过程是怎样的?WindowSession的作用?Token的使用场景?
Window概念解析?
WindowSession的创建过程是怎样的?
WindowSession的作用?博客
Token的使用场景?
Token是什么?
主要分两种Token:
Activity中的Token
10.0.0.3 Activity、View、Window三者之间的关系,Window有哪几种类型?
Activity、View、Window三者之间的关系
Window有哪几种类型
Activity 与 PhoneWindow 与 DecorView 关系图
10.0.0.5 Activity的启动过程是怎样的?Activity的视图加载的源码分析?Activity创建和Dialog创建过程的异同?
Activity的启动过程是怎样的?
Activity的视图加载的源码分析
Dialog的Window创建过程
将DecorView添加到Window中显示。和Activity一样,都是在自身要出现在前台时才会将添加Window。
10.0.0.6 如何处理快速连续点击了多次按钮时Toast就触发了多次而关闭不掉?
使用中遇到的问题
解决的办法
创建工具类:
/**
* 吐司工具类 避免点击多次导致吐司多次,最后导致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();
}
```
这样用的原理
10.0.0.7 DecorView何时才被WindowManager真正添加到Window中?Window的addView源码分析?
DecorView何时才被WindowManager真正添加到Window中?
Window的addView源码分析?
Window的remove源码与解析
dispatchDetachedFromWindow:博客
10.0.0.8 Dialog的Window创建过程?为什么Dialog不能用Application的Context?
Dialog的Window创建过程?
为什么Dialog不能用Application的Context?
10.0.0.9 什么是DecorView?如何获取到DecorView?DecorView的职责是什么?DecorView如何被加载到Window中?
什么是DecorView
如何获取到DecorView
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
ViewGroup rootView = (ViewGroup) content.getChildAt(0);
DecorView的职责是什么
DecorView如何被加载到Window中?博客
10.0.1.0 DecorView如何显示出来,为什么setContentView()设置的界面,为什么在onResume()之后才对用户可见呢?
通过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加载出来
10.0.1.1 什么是ViewRoot?ViewRoot属于View树的一份子吗?ViewRoot的工作流程是怎么样的?
什么是ViewRoot
ViewRoot属于View树的一份子吗?
下面结构图可以清晰的揭示四者之间的关系:
10.0.1.2 吐司为何会出现内存泄漏?在Toast构造方法中创建NT对象是干什么用的?Toast是怎么show出来的?
吐司为何会出现内存泄漏
在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出来的?
10.0.1.3 连续吐司是如何确定吐司的先后顺序?为什么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;
}
}
10.0.1.4 如何理解普通应用的Toast显示数量是有限制的?为什么要判断是否是系统吐司?为何Activity销毁后Toast仍会显示?
如何理解普通应用的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仍会显示
10.0.1.5 为什么说Toast尽量用全局上下文?说一下Toast的显示和隐藏重点逻辑,说下你的理解?
为什么说Toast尽量用全局上下文?
说一下Toast的显示和隐藏重点逻辑,说下你的理解?博客
10.0.1.6 Toast报错Unable to add window是什么意思?Toast运行在子线程会问题,在子线程或者service中能运行吗?
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();
得出的结论
10.0.1.7 为什么建议用DialogFragment替代Dialog?如何定义DialogFragment样式?使用dialogFragment有何好处?
为什么建议用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,即不接收输入的焦点,而且触摸无效。
注意动画设置如下所示
使用dialogFragment有何好处?
10.0.1.8 Dialog的Window创建过程是怎样的?为什么Dialog不能用Application的Context,说一下原因?
Dialog的Window创建过程是怎样的?
为什么Dialog不能用Application的Context,说一下原因?
10.0.1.9 Dialog和Window有什么关系?Dialog的dismiss和cancel()方法都可销毁弹窗,它们有什么区别?
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 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;
}
}
}
dismiss方法主要是做了什么?
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
10.0.2.0 PopupWindow中不设置为什么必须设置宽高?PopupWindow和Dialog有什么区别?说下创建和销毁的大概流程?
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呢?
10.0.2.1 Snackbar与吐司有何区别在哪里?Snackbar控件show时为何从下往上移出来?为什么显示在最下面?
Snackbar与吐司有何区别
Snackbar控件show时为何从下往上移出来?
为什么显示在最下面?
Snackbar显示会导致FloatingActionButton上移?
10.0.2.2 说一下Snackbar和SnackbarManager类的设计有哪些奥妙的地方,如何处理消息的显示顺序?
Snackbar负责显示和消失,具体来说其实就是添加和移除View的过程。Snackbar和SnackbarManager的设计很巧妙,利用一个SnackbarRecord对象保存Snackbar的显示时间以及SnackbarManager.Callback对象,前面说到每一个Snackbar都有一个叫做mManagerCallback的SnackbarManager.Callback对象,下面看一下SnackRecord类的定义: