Toast源码解析
来源:互联网 发布:淘宝的大拿韩代怎么样 编辑:程序博客网 时间:2024/05/21 18:00
子线程中的Toast
在写代码的时候发现一个现象,在子线程中使用Toast会crash,错误如下
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
可以很明显的看出问题出在当前线程企图创建Handler,但是由于本线程没有Looper所以crash了,这时候我不禁对Toast的实现原理产生兴趣,接下来就一步一步的分析源码。
Toast的创建
在日常使用中都是使用Toast.makeText()来创建一个Toast,代码如下
public static Toast makeText(Context context, CharSequence text, @Duration int duration) { Toast result = new Toast(context); LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); tv.setText(text); result.mNextView = v; result.mDuration = duration; return result; }
其实很简单,就是设置了mNextView和mDuration,创建一个TextView然后设置传入的String就完成mNextView的设置,mDuration参数被@Duration注解标记,注解如下
@IntDef({LENGTH_SHORT, LENGTH_LONG}) @Retention(RetentionPolicy.SOURCE) public @interface Duration {} public static final int LENGTH_SHORT = 0; public static final int LENGTH_LONG = 1;
@IntDef限制了只能传入两个给定的int,也就是说我们只能设置显示时间的长短,而无法设置具体的时长。
Toast.show()
show()方法源码如下
public void show() { if (mNextView == null) { throw new RuntimeException("setView must have been called"); } INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; try { service.enqueueToast(pkg, tn, mDuration); } catch (RemoteException e) { // Empty } }
可以看出这里需要得到一个INotificationManager的服务,传入了TN的实例,首先我们先看看TN是什么东西
private static class TN extends ITransientNotification.Stub { ... }
查看ITransientNotification的代码
public interface ITransientNotification extends android.os.IInterface{/** Local-side IPC implementation stub class. */public static abstract class Stub extends android.os.Binder implements android.app.ITransientNotification{// 省略大部分代码...public void show() throws android.os.RemoteException;public void hide() throws android.os.RemoteException;}
可以看出这就是一个AIDL的接口,有show()和hide()两个办法,可以猜测主要用于远程服务来控制Toast显示和隐藏的。我们就看看show和hide的具体实现
final Runnable mHide = new Runnable() { @Override public void run() { handleHide(); // Don't do this in handleHide() because it is also invoked by handleShow() mNextView = null; } }; final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { IBinder token = (IBinder) msg.obj; handleShow(token); } }; @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); }
这里就用到了Handler,通过Handler实现show和hide,这也就解释了为什么子线程会报错。show()最终调用handlerShow()方法
public void handleShow(IBinder windowToken) { if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView=" + mNextView); if (mView != mNextView) { // remove the old view if necessary handleHide(); mView = mNextView; Context context = mView.getContext().getApplicationContext(); String packageName = mView.getContext().getOpPackageName(); if (context == null) { context = mView.getContext(); } mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); // We can resolve the Gravity here by using the Locale for getting // the layout direction final Configuration config = mView.getContext().getResources().getConfiguration(); final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection()); mParams.gravity = gravity; if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { mParams.horizontalWeight = 1.0f; } if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { mParams.verticalWeight = 1.0f; } mParams.x = mX; mParams.y = mY; mParams.verticalMargin = mVerticalMargin; mParams.horizontalMargin = mHorizontalMargin; mParams.packageName = packageName; mParams.hideTimeoutMilliseconds = mDuration == Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT; mParams.token = windowToken; if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeView(mView); } if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this); mWM.addView(mView, mParams); trySendAccessibilityEvent(); } }
可以看出主要就是通过WindowManager来addView显示Toast。hide()最终调用handleHide方法实现
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; } }
也是通过WindowManager来removevView。
Toast的时长
我们在使用Toast的时候只能穿入LENGTH_LONG活着LENGTH_SHORT两个变量,而具体的时间在handleShow()的代码中可以发现
mParams.hideTimeoutMilliseconds = mDuration == Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
static final long SHORT_DURATION_TIMEOUT = 5000; static final long LONG_DURATION_TIMEOUT = 1000;
可以看出长短时间分别对应5秒和1秒
为何使用AIDL而不是自己控制显示和隐藏
不知道大家有没有发现,在显示两个Toast的时候,总是第一个显示完毕才会显示第二个,如果让Toast自己控制,那么是很难实现这样的效果的,它并不知道其他Toast的状态,所以所有Toast交由系统同意管理,通过队列来依次显示Toast,并会按照设置的时间来hide Toast。
如何自己控制时间
我们可以跳过AIDL,获取NT对象,直接调用show和hide方法,但是NT对象构造方法是私有的,我们可以通过反射来解决,当然也可以反射设置time,然后正常show。
- Android -Toast源码解析
- Toast源码解析
- Toast源码解析
- Toast实现源码解析
- Toast自定义及源码解析
- 系统窗口Toast显示源码解析
- Android源码解析——Toast
- Android 高级自定义Toast及源码解析
- Android 高级自定义Toast及源码解析
- Snackbar新版Toast 从源码角度完全解析
- Android源码解析(二十二)-->Toast加载绘制流程
- Android Toast源码分析
- Android Toast源码实现
- Toast源码浅析
- Toast源码分析
- Android:Toast源码分析
- Toast源码分析
- [android] toast解析
- 基于Android系统的IPv6网络接入分析
- 5.27 JLU校赛部分题解
- Android asynTask造成的内存泄露
- WebView造成的空指针(偶尔发生)
- NOIP2015 提高组 复赛 day1 message 信息传递
- Toast源码解析
- 自然语言处理入门(1)——文本相似度计算
- 二进制中1的个数
- CC2530 Zigbee在IAR在线调试出现错误提示:“Fatal error:Unknown exception indriver(#E1) Session aborted”
- html屏蔽右键、禁止复制与禁止查看源代码
- React1
- Java新特性 lambda表达式
- 关闭 XCode 的自动编译
- spring_SSH整合之_架构的历史(架构整合JSP+Hibernate+Spring)_6