toast源码分析与学习

彼岸花you 彼岸花you     2022-11-06     589

关键词:

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

知识点补充

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.

源码分析总结

toast源码分析(android11)(代码片段)

基于AndroidR(11),targetSdkVersion30源码分析基本用法普通Toast:Toast.makeText(this@MainActivity,"helloToast!",Toast.LENGTH_SHORT).show()自定义View的Toast:valtoast=Toast(this@MainActivi 查看详情

toast源码深度分析(代码片段)

...免重复创建1.4为何会出现内存泄漏1.5吐司是系统级别的2.源码分析2.1Toast(Contextcontext)构造方法源码分析2.2show()方法源码分析2.3mParams.token=windowToken是干什么用的2.4scheduleTimeoutLocked吐司如何自动销毁的2.5TN类中的消 查看详情

toast的window创建过程以及源码分析

Toast的window创建过程以及源码分析Toast是基于Window来实现的,系统中继承了handler做定时处理内部实现IPC过程,第一类访问NotificationManagerService(NMS)通过回调TN接口,实现Toast的显示隐藏Toast属于Window,它内部的视图由... 查看详情

android中的toast源码分析和自定义toast(代码片段)

我的主页Demo下载地址一、系统自带Toast的源码分析1.Toast的调用显示学过Android的人都知道,弹出一个系统API吐司只需要一行代码,调用的是Toast对象的makeText()方法,方法里给一个上下文,显示的文字,和显示的... 查看详情

toast的window创建过程以及源码分析

Toast的window创建过程以及源码分析Toast是基于Window来实现的,系统中继承了handler做定时处理内部实现IPC过程,第一类访问NotificationManagerService(NMS)通过回调TN接口,实现Toast的显示隐藏Toast属于Window,它内部的视图由... 查看详情

hashmap与hashtable源码学习及效率比较分析

  一、个人学习后的见解:    首先表明学习源码后的个人见解,后续一次依次进行分析:    1、线程安全:HashMap是非线程安全的,HashTable是线程安全的(HashTable中使用了synchronized关键字进行控制),HashMap对应的线... 查看详情

android源码分析window添加(代码片段)

1系统添加窗口添加的过程这里以我们常用的Toast来分析添加到SystemWindow中的过程//Toast publicstaticToastmakeText(@NonNullContextcontext,@NullableLooperlooper,@NonNullCharSequencetext,@Durationintduration)//1.实例化Toast 查看详情

schedulingrestartofcrashedservice解决方案与源码分析(代码片段)

测试发现一个bug,service中某个方法由于空指针导致程序挂掉,接着触发程序的保活机制触发程序重启,但是这个异常service先启动访问未初始化资源导致程序连续循环重启。下面代码模拟了service子线程显示toast引起程... 查看详情

schedulingrestartofcrashedservice解决方案与源码分析(代码片段)

测试发现一个bug,service中某个方法由于空指针导致程序挂掉,接着触发程序的保活机制触发程序重启,但是这个异常service先启动访问未初始化资源导致程序连续循环重启。下面代码模拟了service子线程显示toast引起程... 查看详情

android源码分析window添加(代码片段)

1系统添加窗口添加的过程这里以我们常用的Toast来分析添加到SystemWindow中的过程//Toast publicstaticToastmakeText(@NonNullContextcontext,@NullableLooperlooper,@NonNullCharSequencetext,@Durationintduration)//1.实例化Toas 查看详情

redis学习记录:字典(dict)源码分析(代码片段)

...我还是很感兴趣的,由此抽空会切进去学习一下实现源码,并且在CSDN上记录一下吧。个人的《redis设计与实现》读书笔记记录:https://github.com/zgg2001/redis_learning目录一、前言二、redis字典的实现思路三、实现源码分析1.... 查看详情

c/c++学习记录:std::move源码分析(代码片段)

抽空扣一点感兴趣的标准库源码,这里总结一下std::move()相关的分析本文中gccversion:8.4.120200928(RedHat8.4.1-1)(GCC)其中c++库安装路径为/usr/include/c++/8目录一、源码与分析1.std::move源码总览2.std::remove_reference源码分析3.stati... 查看详情

c/c++学习记录:std::move源码分析(代码片段)

抽空扣一点感兴趣的标准库源码,这里总结一下std::move()相关的分析本文中gccversion:8.4.120200928(RedHat8.4.1-1)(GCC)其中c++库安装路径为/usr/include/c++/8目录一、源码与分析1.std::move源码总览2.std::remove_reference源码分析3.stati... 查看详情

c++学习记录:一个协程库的源码分析(代码片段)

抽空学习一些感兴趣的源码,这次学习云风大佬的一个协程库源码。项目地址:https://github.com/cloudwu/coroutine个人fork注释后的项目地址:https://github.com/zgg2001/coroutine目录一、前言二、实现分析1.数据结构2.整体思路3.唤... 查看详情

vlc源码分析调试学习hls协议

    HTTPLiveStreaming(HLS)是苹果公司提出来的流媒体传输协议。与RTP协议不同的是,HLS可以穿透某些允许HTTP协议通过的防火墙。一、HLS播放模式(1) 点播模式(Videoondemand,VOD)    点播模式是指当前... 查看详情

轻量级控件snackbar应用&源码分析

前言        SnackBar是AndroidSupportDesignLibrary库支持的一个控件,它在使用的时候经常和CoordinatorLayout一起使用,它是介于Toast和Dialog之间的产物,属于轻量级控件很方便的提供提示和动作反馈,有时候我... 查看详情

c/c++学习记录:std::forward源码分析/完美转发的作用(代码片段)

抽空扣一点感兴趣的标准库源码,这里总结一下std::forward()相关的分析本文中gccversion:8.4.120200928(RedHat8.4.1-1)(GCC)其中c++库安装路径为/usr/include/c++/8目录一、前言二、源码与分析1.std::forward源码总览2.std::forward分析三... 查看详情

c/c++学习记录:std::forward源码分析/完美转发的作用(代码片段)

抽空扣一点感兴趣的标准库源码,这里总结一下std::forward()相关的分析本文中gccversion:8.4.120200928(RedHat8.4.1-1)(GCC)其中c++库安装路径为/usr/include/c++/8目录一、前言二、源码与分析1.std::forward源码总览2.std::forward分析三... 查看详情