Toast源码分析与学习

xiaoxiao2021-02-28  143

源码查看网址,这个是当前我所用的源码地址,自备梯子

知识点补充

android使用注解替代枚举 android 进程间通信使用aidl和Messenger类

源码分析

涉及源码有些长,下面只截取了部分分析,github toast相关源码

① 从Toast.java中的变量定义开始 我们都知道在实际使用中,Toast显示的时间只有两种情况。我们先从源码看看是怎么回事。

static final String TAG = "Toast"; static final boolean localLOGV = false; /** @hide */ @IntDef({LENGTH_SHORT, LENGTH_LONG}) @Retention(RetentionPolicy.SOURCE) public @interface Duration {} /** * Show the view or text notification for a short period of time. This time * could be user-definable. This is the default. * @see #setDuration */ public static final int LENGTH_SHORT = 0; /** * Show the view or text notification for a long period of time. This time * could be user-definable. * @see #setDuration */ public static final int LENGTH_LONG = 1; final Context mContext; final TN mTN; int mDuration; View mNextView;

这里使用注解的方式规定了Duration的取值,只有两个值,LENGTH_SHORT和LENGTH_LONG。在看一下源码中对Duration的设置

/** * Set how long to show the view for. * @see #LENGTH_SHORT * @see #LENGTH_LONG */ public void setDuration(@Duration int duration) { mDuration = duration; mTN.mDuration = duration; } /** * Return the duration. * @see #setDuration */ @Duration public int getDuration() { return mDuration; }

明了了吧,这里使用@Duration对传入的参数进行了规定,如果想传入其他的int值,在代码中是会有标记

② 看看Toast.java的构造函数

/** * Construct an empty Toast object. You must call {@link #setView} before you * can call {@link #show}. * * @param context The context to use. Usually your {@link android.app.Application} * or {@link android.app.Activity} object. */ 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); }

由界面传入一个上下文实例context,后面的代码貌似是创建一个TN对象,我们现在来看看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; int mDuration; WindowManager mWM; static final long SHORT_DURATION_TIMEOUT = 5000; static final long LONG_DURATION_TIMEOUT = 1000; 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(); 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.removeTimeoutMilliseconds = mDuration == Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT; 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; } } }

这个类继承ITransientNotification.stub,那么这个ITransientNotification是一个aidl文件 ITransientNotification.aidl(@hind)源码

20/** @hide */ 21 oneway interface ITransientNotification { 22 void show(); 23 void hide(); 24}

上面的TN继承这个类并提供了具体的方法show和hind,在具体的实现中使用Handler来post两个runnable(TN类开头定义的两个Runnable),两个Runnable中分别执行了handleShow()和handleHind()方法,推测是控制Toast显示和隐藏的具体方法。看具体的实现: 在handleShow()方法中,通过获取WindowManager将创建的View add到界面上,在handleHind()方法中获取WindowManager remove掉这个View.

④ 根据Toast.java的构造函数和上面对TN的分析 小结一下。 使用过程中,创建一个toast对象,会实例化一个内部类TN,这个TN类能够控制Toast显示和隐藏(内部由WindowManager add 和remove view来实现显示和隐藏)。 ⑤ 构造toast view 之后,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.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; try { service.enqueueToast(pkg, tn, mDuration); } catch (RemoteException e) { // Empty } } private static INotificationManager sService; static private INotificationManager getService() { if (sService != null) { return sService; } sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification")); return sService; }

又出现了几个不认识的东西,看看具体是什么。 - ServiceManager.java(@hind)

26 /** @hide */ 27public final class ServiceManager { 28 private static final String TAG = "ServiceManager"; 29 30 private static IServiceManager sServiceManager; 31 private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>(); 32 33 private static IServiceManager getIServiceManager() { 34 if (sServiceManager != null) { 35 return sServiceManager; 36 } 37 38 // Find the service manager 39 sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject()); 40 return sServiceManager; 41 } 42 43 /** 44 * Returns a reference to a service with the given name. 45 * 46 * @param name the name of the service to get 47 * @return a reference to the service, or <code>null</code> if the service doesn't exist 48 */ 49 public static IBinder getService(String name) { 50 try { 51 IBinder service = sCache.get(name); 52 if (service != null) { 53 return service; 54 } else { 55 return getIServiceManager().getService(name); 56 } 57 } catch (RemoteException e) { 58 Log.e(TAG, "error in getService", e); 59 } 60 return null; 61 } 62 63 /** 64 * Place a new @a service called @a name into the service 65 * manager. 66 * 67 * @param name the name of the new service 68 * @param service the service object 69 */ 70 public static void addService(String name, IBinder service) { 71 try { 72 getIServiceManager().addService(name, service, false); 73 } catch (RemoteException e) { 74 Log.e(TAG, "error in addService", e); 75 } 76 } ... 139 }

ServiceManager详解 文章重点:ServiceManager管理整个系统的Service,实际功能发面包括: - 提供IBinder对象(在每个进程中,该IBinder对象是唯一的)就好比我获取一个闹钟的service,那在当前这个app进程中这个service就是唯一的。 - 让各个service注册到ServiceManager中。 简单来说ServiceManager提供getIBinder的方法和每个service都会注册到它。 INotificationManager.aidl(@hind)

/** {@hide} */ 36interface INotificationManager 37 { 38 void cancelAllNotifications(String pkg, int userId); 39 40 void enqueueToast(String pkg, ITransientNotification callback, int duration); 41 void cancelToast(String pkg, ITransientNotification callback); ... 100 }

ITransientNotification.aidl(@hind)

20/** @hide */ 21 oneway interface ITransientNotification { 22 void show(); 23 void hide(); 24}

回到Toast.java 源码

我们操作中调用的Toast show方法在源码中的操作其实是获取这个INotificationManager的实例,然后enqueueToast和cancelToast.

源码分析总结

转载请注明原文地址: https://www.6miu.com/read-20220.html

最新回复(0)