Android:Toast源码分析
2016-09-09 13:09
507 查看
本来想在一篇文章中吧Toast的用法,原理以及如何自定义都跟大家说说的,但是限于篇幅,还是分开写了,这里主要是介绍Toast的原理。
如果大家对SDK中提供的Toast用法不是很熟悉,请移步我的上一篇blog 《Android:Toast的用法》
说句题外话,看源码真是个不错的习惯,大热天一下心就哇凉哇凉的,看不懂啊,自嘲一下。
书归正传:上代码
makeText的源码:
很简单,只是实例化一个Toast,设置了文本和时间,在把Toast当做返回值返回。
看到这,下一步看show()是怎么实现的。
这里先是做了一个判断,如果没有对mNextView 赋值,抛出一个异常。然后是获取一个服务:INotificationManager,然后调用service.enqueueToast(pkg, tn, mDuration),将Toast放到一个队列里面显示。这里enqueueToast方法传入一个TN,下面看一下TN是个什么东西。
乍一看,代码很长,一点一点分析,还是很容易能看明白的。
我们发现TN继承了ITransientNotification.Stub,这个类的形式大家应该都有印象吧,没有也没关系,这个是android中AIDL(进程间通信)的写法,这个类会对应有一个ITransientNotification.aidl的文件(注意后缀aidl)
定义了两个接口 show() 和 hide(), 我们来看一下TN中的实现。
这里发现 Toast中用的是 handler机制。分别post一个mShow和一个mHide。我们来看看这两个Runnable是怎么实现的。
下一步肯定是看handleShow()和handleHide()的实现了。
到这里,我们发现原来Toast是用WindowManager的addView和removeView实现的。
其中TN的构造方法中设置了WindowManager.LayoutParams ,然后在handleShow()方法中,把我们设置的参数复制个Params,然后显示出来。
到这里,基本我就已经分析完了。
其实Toast的原理是这样的,先通过makeText()实例化出一个Toast,然后调用toast.Show()方法,这时并不会马上显示Toast,而是通过 AIDL(进程间通信) 加到 SystemService进程中的 一个队列 然后通过TN的两个预留接口来控制Toast的显示和隐藏。在TN中进行调控Toast的显示格式以及里面的hide()、show()方法来控制Toast的出现以及消失,强调一下的是这个队列是系统维护的,我们并不能干涉。
如果大家对SDK中提供的Toast用法不是很熟悉,请移步我的上一篇blog 《Android:Toast的用法》
说句题外话,看源码真是个不错的习惯,大热天一下心就哇凉哇凉的,看不懂啊,自嘲一下。
书归正传:上代码
Toast.makeText(MainActivity.this, "欢迎光临阿东的博客", Toast.LENGTH_LONG).show();
makeText的源码:
/** * Make a standard toast that just contains a text view. * * @param context The context to use. Usually your {@link android.app.Application} * or {@link android.app.Activity} object. * @param text The text to show. Can be formatted text. * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or * {@link #LENGTH_LONG} * */ public static Toast makeText(Context context, CharSequence text, 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; }
很简单,只是实例化一个Toast,设置了文本和时间,在把Toast当做返回值返回。
看到这,下一步看show()是怎么实现的。
/** * Show the view for the specified duration. */ 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 } }
这里先是做了一个判断,如果没有对mNextView 赋值,抛出一个异常。然后是获取一个服务:INotificationManager,然后调用service.enqueueToast(pkg, tn, mDuration),将Toast放到一个队列里面显示。这里enqueueToast方法传入一个TN,下面看一下TN是个什么东西。
private static class TN extends ITransientNotification.Stub { final Runnable mShow = new Runnable() { @Override public void run() { handleShow(); } }; 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; } }; private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); final Handler mHandler = new Handler(); int mGravity; int mX, mY; float mHorizontalMargin; float mVerticalMargin; View mView; View mNextView; WindowManager mWM; TN() { // XXX This should be changed to use a Dialog, with a Theme.Toast // defined that sets up the layout params appropriately. final WindowManager.LayoutParams params = mParams; params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.format = PixelFormat.TRANSLUCENT; params.windowAnimations = com.android.internal.R.style.Animation_Toast; params.type = WindowManager.LayoutParams.TYPE_TOAST; params.setTitle("Toast"); params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; } /** * schedule handleShow into the right thread */ @Override public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); } /** * schedule handleHide into the right thread */ @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); } public void handleShow() { 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(); 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; 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(); } } private void trySendAccessibilityEvent() { AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mView.getContext()); if (!accessibilityManager.isEnabled()) { return; } // treat toasts as notifications since they are used to // announce a transient piece of information to the user AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); event.setClassName(getClass().getName()); event.setPackageName(mView.getContext().getPackageName()); mView.dispatchPopulateAccessibilityEvent(event); accessibilityManager.sendAccessibilityEvent(event); } 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.removeView(mView); } mView = null; } } }
乍一看,代码很长,一点一点分析,还是很容易能看明白的。
我们发现TN继承了ITransientNotification.Stub,这个类的形式大家应该都有印象吧,没有也没关系,这个是android中AIDL(进程间通信)的写法,这个类会对应有一个ITransientNotification.aidl的文件(注意后缀aidl)
package android.app; /** @hide */ oneway interface ITransientNotification { void show(); void hide(); }
定义了两个接口 show() 和 hide(), 我们来看一下TN中的实现。
/** * schedule handleShow into the right thread */ @Override public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); } /** * schedule handleHide into the right thread */ @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); }
这里发现 Toast中用的是 handler机制。分别post一个mShow和一个mHide。我们来看看这两个Runnable是怎么实现的。
final Runnable mShow = new Runnable() { @Override public void run() { handleShow(); } }; 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; } };
下一步肯定是看handleShow()和handleHide()的实现了。
public void handleShow() { 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(); 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; 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(); } } 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.removeView(mView); } mView = null; } }
到这里,我们发现原来Toast是用WindowManager的addView和removeView实现的。
其中TN的构造方法中设置了WindowManager.LayoutParams ,然后在handleShow()方法中,把我们设置的参数复制个Params,然后显示出来。
到这里,基本我就已经分析完了。
其实Toast的原理是这样的,先通过makeText()实例化出一个Toast,然后调用toast.Show()方法,这时并不会马上显示Toast,而是通过 AIDL(进程间通信) 加到 SystemService进程中的 一个队列 然后通过TN的两个预留接口来控制Toast的显示和隐藏。在TN中进行调控Toast的显示格式以及里面的hide()、show()方法来控制Toast的出现以及消失,强调一下的是这个队列是系统维护的,我们并不能干涉。
相关文章推荐
- Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析
- Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析
- Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析
- 一、ANDROID应用ACTIVITY、DIALOG、POPWINDOW、TOAST窗口添加机制及源码分析
- Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析 《三》-Dialog
- Android悬浮窗TYPE_TOAST小结: 源码分析
- Android Toast源码分析
- 初学android-Toast的源码分析
- Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析 《四》-PopWindow
- Android在子线程中显示Toast实现与源码分析
- Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析
- android源码分析 android toast使用详解 toast自定义
- Android 源码分析 —— 从 Toast 出发
- Android悬浮窗TYPE_TOAST小结源码分析
- Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析
- Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析
- Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析 《二》
- Android中的Toast源码分析和自定义Toast
- Android IPC 通讯机制源码分析(1)