安卓Toast显示流程分析

Toast.makeText()的使用方法 通过方法,我们可以自定义Toast的显示位置。以下例子将Toast如果要显示更复杂的内容,我们可以通过自定义布局文件来创建Toast。例如,创建一个包含图片和文本的Toast");作为Android开发中常用的UI组件,提供了便捷的方式向用户提供短暂的提示信息。通过本文的介绍,我们详细了解了的使用方法,包括基本用法、自定义位置、自定义布局等。在实际应用中,Toast可以用于各种场景,如用户操作反馈、表单验证提示、网络请求结果提示等。希望本文对大家更好地理解和应用有所帮助。 阅读详情

我们在开发apk的过程中,经常会用到Toast,它的确很方便,不用开发者编写UI布局,只需要传入字符串就能给用户提示,那么它的显示与消失的流程是怎样的呢,本来就来讲一讲这个流程(所用源码为android 8.0版本)。

1、首先我们来看一下Toast的弹出的写法:

Toast.makeText(ServiceTestActivity.this,"test",Toast.LENGTH_LONG).show();

1.1 我们看一下Toast.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, @Duration int duration) {
        return makeText(context, null, text, duration);
    }

    /**
     * Make a standard toast to display using the specified looper.
     * If looper is null, Looper.myLooper() is used.
     * @hide
     */
    public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
        Toast result = new Toast(context, looper);

        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;
    }

我们直接看第二个makeText方法,注意参数Looper对象此时为null,此方法里面,先是new一个Toast对象,然后加载了一个布局,这个布局就是Toast的布局,布局代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="?android:attr/toastFrameBackground">

    <TextView
        android:id="@android:id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_gravity="center_horizontal"
        android:textAppearance="@style/TextAppearance.Toast"
        android:textColor="@color/bright_foreground_dark"
        android:shadowColor="#BB000000"
        android:shadowRadius="2.75"
        />

</LinearLayout>

然后就是给Toast对象设置了布局和周期,返回了这个Toast对象。我们需要看一下Toast的构造函数都干了什么:

/**
     * Constructs an empty Toast object.  If looper is null, Looper.myLooper() is used.
     * @hide
     */
    public Toast(@NonNull Context context, @Nullable Looper looper) {
        mContext = context;
        mTN = new TN(context.getPackageName(), looper);
        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);
    }

Toast的构造函数里面又new了一个TN对象,然后给这个对象的两个属性设了值。我们来看一下TN是何方神圣:

private static class TN extends ITransientNotification.Stub
TN(String packageName, @Nullable Looper looper) {
            // 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;

            mPackageName = packageName;

            if (looper == null) {
                // Use Looper.myLooper() if looper is not specified.
                looper = Looper.myLooper();
                if (looper == null) {
                    throw new RuntimeException(
                            "Can't toast on a thread that has not called Looper.prepare()");
                }
            }
            mHandler = new Handler(looper, null) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case SHOW: {
                            IBinder token = (IBinder) msg.obj;
                            handleShow(token);
                            break;
                        }
                        case HIDE: {
                            handleHide();
                            // Don't do this in handleHide() because it is also invoked by
                            // handleShow()
                            mNextView = null;
                            break;
                        }
                        case CANCEL: {
                            handleHide();
                            // Don't do this in handleHide() because it is also invoked by
                            // handleShow()
                            mNextView = null;
                            try {
                                getService().cancelToast(mPackageName, TN.this);
                            } catch (RemoteException e) {
                            }
                            break;
                        }
                    }
                }
            };
        }

TN继承了一个Stub接口,可知其是一个IBinder对象,用来进程间通信的,其作用是为了和系统服务NOTIFICATION_SERVICE进行相互通信,具体的服务类是WindowManagerImpl.java。我们看一下TN的构造方法,其先检查了looper对象是否为null,若为null,则获取线程的looper对象,如果线程的looper对象仍为空,则会报

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

所以,如果想要用Toast,是需要我们的线程有looper的,系统在创建进程和main线程的时候,已经给我们创建好了main线程的looper对象,所以平时我们在Activity、Fragment、Service里面可以直接使用Toast,没有问题。

到这里Toast.makeText方法的关键信息都讲述完了,这里小结一下该方法里面都做了什么:

(1)新建了一个Toast对象,为Toast对象设置了布局和周期。

(2)Toast对象里面有新建了一个TN对象,这个对象是传给远程服务NITIFICATION_SERVICE方便回调的,TN对象里面又创建了一个handler,handler的looper是调用Toast的线程的looper,即handler是在调用Toast的线程处理消息的。

1.2 我们接下来看一下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
        }
    }

1.2.1 show()方法里面会调用getService方法获取服务,我们看一下获取什么服务:

static private INotificationManager getService() {
        if (sService != null) {
            return sService;
        }
        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
        return sService;
    }

可知其实是通过Binder获取到名称为“notification”的系统服务的代理对象。“notification”服务的启动是在SystemServer.startOtherService里面启动的

mSystemServiceManager.startService(NotificationManagerService.class);

NotificationManagerService的onStart里面回把服务注册到ServiceManager类里面。系统服务的启动和注册有关流程见我的另两篇博客《SystemServer类解析》和《getSystemService追根溯源》。

1.2.2 返回到show方法,接下来会调用INotificationManager的enqueueToast方法,参数是本应用的包名、TN对象、周期。根据Binde机制的原理,我们可知在NotificationManagerSrevice里面会有一个INotificationManager.Stub类的实现类,该实现类会实现enqueueToast方法:

private final IBinder mService = new INotificationManager.Stub() {
        // Toasts
        // ============================================================================

        @Override
        public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            if (DBG) {
                Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
                        + " duration=" + duration);
            }

            if (pkg == null || callback == null) {
                Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
                return ;
            }

            final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
            final boolean isPackageSuspended =
                    isPackageSuspendedForUser(pkg, Binder.getCallingUid());

            if (ENABLE_BLOCKED_TOASTS && (!noteNotificationOp(pkg, Binder.getCallingUid())
                    || isPackageSuspended)) {
                if (!isSystemToast) {
                    Slog.e(TAG, "Suppressing toast from package " + pkg
                            + (isPackageSuspended
                                    ? " due to package suspended by administrator."
                                    : " by user request."));
                    return;
                }
            }

            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    int index = indexOfToastLocked(pkg, callback);
                    // If it's already in the queue, we update it in place, we don't
                    // move it to the end of the queue.
                    if (index >= 0) {
                        record = mToastQueue.get(index);
                        record.update(duration);
                    } else {
                        // Limit the number of toasts that any given package except the android
                        // package can enqueue.  Prevents DOS attacks and deals with leaks.
                        if (!isSystemToast) {
                            int count = 0;
                            final int N = mToastQueue.size();
                            for (int i=0; i<N; i++) {
                                 final ToastRecord r = mToastQueue.get(i);
                                 if (r.pkg.equals(pkg)) {
                                     count++;
                                     if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                         Slog.e(TAG, "Package has already posted " + count
                                                + " toasts. Not showing more. Package=" + pkg);
                                         return;
                                     }
                                 }
                            }
                        }

                        Binder token = new Binder();
                        mWindowManagerInternal.addWindowToken(token,
                                WindowManager.LayoutParams.TYPE_TOAST);
                        record = new ToastRecord(callingPid, pkg, callback, duration, token);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                        keepProcessAliveIfNeededLocked(callingPid);
                    }
                    // If it's at index 0, it's the current toast.  It doesn't matter if it's
                    // new or just been updated.  Call back and tell it to show itself.
                    // If the callback fails, this will remove it from the list, so don't
                    // assume that it's valid after this.
                    if (index == 0) {
                        showNextToastLocked();
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }

该方法显得略长,无关主流程的逻辑不在赘述,我们只捡重要的说。直接进到synchronized关键字修饰的同步代码段,先调用了indexOfToastLocked方法,该方法是返回相同包名并且TN对象相同的的ToastRecord对象在队列中的索引,找不到返回-1,根据我们99%的惯用写法,返回值为-1,所以我们直接看index为-1的逻辑。然后会判断是否为系统弹出的toast,若不是系统Toast,则会对每个应用所能在队列中存留的Toast的数量不能超过MAX_PACKAGE_NOTIFICATIONS(值为50),即一个应用连续发送Toast,超过50个Toast未处理就会报异常。接下来就是新建一个ToastRecord对象,入队列,添加到队列末尾。最后有一个判断,如果index为0,则会调用showNextToastLocked方法,看到这里可能有读者可能会有疑问,这里为什么要加一个index==0的判断,如果index不为0,那么如何显示Toast呢,别着急,其实这些逻辑就在showNextToastLocked里面。

1.3 showNextToastLocked方法

void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
            try {
                record.callback.show(record.token);
                scheduleTimeoutLocked(record);
                return;
            } catch (RemoteException e) {
                Slog.w(TAG, "Object died trying to show notification " + record.callback
                        + " in package " + record.pkg);
                // remove it from the list and let the process die
                int index = mToastQueue.indexOf(record);
                if (index >= 0) {
                    mToastQueue.remove(index);
                }
                keepProcessAliveIfNeededLocked(record.pid);
                if (mToastQueue.size() > 0) {
                    record = mToastQueue.get(0);
                } else {
                    record = null;
                }
            }
        }
    }

进入到该方法,第一就代码就是获取到队列的首部的ToastRecord对象,然后进入了一个while循环。我们来看一下循环体的逻辑。

1.3.1 while循环体里面调用了record.callback.show(record.token),我们来分一下这句代码的逻辑。record.callback其实就是前面讲到TN对象,我们来看一下TN.show方法:

/**
         * schedule handleShow into the right thread
         */
        @Override
        public void show(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
        }

可知是调用mHandler发送了一个SHOW的message,该handler创建请见TN的构造函数。消息处理是在TN.handleShow方法里面:

public void handleShow(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            // If a cancel/hide is pending - no need to show - at this point
            // the window token is already invalid and no need to do any work.
            if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
                return;
            }
            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);
                // Since the notification manager service cancels the token right
                // after it notifies us to cancel the toast there is an inherent
                // race and we may attempt to add a window after the token has been
                // invalidated. Let us hedge against that.
                try {
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                } catch (WindowManager.BadTokenException e) {
                    /* ignore */
                }
            }
        }

该方法核心逻辑是获取WINDOWS_SERVICE服务,该服务类是WindowManagerImpl,该服务是在SystemReServiceRegistry.java的静态代码块里面注册到的,调用WindowManagerImpl.addView方法来显示Toast。至于WindowsManagerImpl是如何加载布局的,本文这里就不在分析了,感兴趣的朋友可以去研究一下。到这里的朋友可能会注意到,Toast的布局的显示利用了WINDOW_SERVICE服务,该处代码是运行在handler所在的进程,如果handler是子线程的,那么该处代码是运行在子线程的,android的子线程不是不能操作UI吗,这里不会有问题吗,该问题请见我的另一篇博客《子线程能弹Toast吗》。

1.3.2 再回到NoificationManagerService的showNextToastLocked方法,接下来我们看一下scheduleTimeroutLocked方法:

private void scheduleTimeoutLocked(ToastRecord r)
    {
        mHandler.removeCallbacksAndMessages(r);
        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
        mHandler.sendMessageDelayed(m, delay);
    }

该方法就是利用Handler发送一个延时消息MESSAGE_TIMEOUT,延迟时间为LONG_DELAY或者SHORT_DELAY,这个延迟时间是根据我们创建Toast的传的参数LENGTH_LONG或者LENGTH_SHORT来决定的,我们来看看分别到时代表多长时间:

static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
    static final int SHORT_DELAY = 2000; // 2 seconds
/**PhoneWindowManager
Amount of time (in milliseconds) a toast window can be shown. */
public static final int TOAST_WINDOW_TIMEOUT = 3500; // 3.5 seconds

可知LENGHT_LONG对应的时间是3.5秒,LENGHT_SHORT对应的时间为2秒。需要注意的是,现在的handler不是TOAST里面的handler了,是NotificationManagerService里面的handler,我们来看一下其是如何处理MESSAGE_TIMEOUT消息的:

case MESSAGE_TIMEOUT:
                    handleTimeout((ToastRecord)msg.obj);
private void handleTimeout(ToastRecord record)
    {
        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
        synchronized (mToastQueue) {
            int index = indexOfToastLocked(record.pkg, record.callback);
            if (index >= 0) {
                cancelToastLocked(index);
            }
        }
    }

在handleTimeout方法里面,会对mToastQueue加锁,如果队列里面知道到了对应的ToastRecord(即当前正在显示的Toast),调用cancelToastLocked方法,看一下该方法:

void cancelToastLocked(int index) {
        ToastRecord record = mToastQueue.get(index);
        try {
            record.callback.hide();
        } catch (RemoteException e) {
            Slog.w(TAG, "Object died trying to hide notification " + record.callback
                    + " in package " + record.pkg);
            // don't worry about this, we're about to remove it from
            // the list anyway
        }

        ToastRecord lastToast = mToastQueue.remove(index);
        mWindowManagerInternal.removeWindowToken(lastToast.token, true);

        keepProcessAliveIfNeededLocked(record.pid);
        if (mToastQueue.size() > 0) {
            // Show the next one. If the callback fails, this will remove
            // it from the list, so don't assume that the list hasn't changed
            // after this point.
            showNextToastLocked();
        }
    }

1.3.2.1 我们来看一下record.callback.hide()方法都干了什么,其调用的是Toast里面的TN的hide方法,该方法也就是发送了一个HIDE消息,我们来看一下handler是如何处理该消息的,直接来看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;
            }
        }

该方法就是调用WidowManageImpl的removeViewImmediate方法使Toast消失。

1.3.2.2 回到cancelToastLocked方法,我们看一下后面的逻辑,后面的逻辑就是mToastQueue删除掉刚才的ToastRecord对象,因为该Toast的显示与消失工作已经做完了。最后就是判断mToastQueue里面还有没有ToastRecord,如果有,调用showNextToastLocked方法继续处理剩余的ToastRecord。

到这里Toast的显示与消失的处理流程就讲述完了,这里总结一下:

1、Toast的显示主要的代码都在Toast.java和NotificationManagerService.java里面,利用了Handler机制和binder机制;

2、Toast显示是利用了WindowsManagerImpl,这个方法避开了操作UI必须在UI线程的限制。

安卓开发-Toast使用详解 安卓开发- Toast 使用详解、修改toast位置、自定义toast 阅读详情

相关推荐

Toast执行流程分析与复用

意图分析的问题 Toast的显示是异步的还是同步。 Toast是否可以在子线程中实例化并调用Toast.show方法。 Toast对象复用的可行性。 源码执行流程分析Toast的本地创建和show()方法实例化Toast对象在实际使用中我们常使用使用如下代码实例化一个Toast对象。Toast.makeText(Context, msg, Toast.LENGTH_SHOW);下面看看makeTex

Final Object 的博客 1334

Android Toast的几种使用方式

Toast是Android中常用的组件,下面介绍下Toast使用的几种方式和注意事项。 Toast的使用方式简单来说有下面五种: 1、默认的显示 // 第一个参数:当前的上下文环境。可用getApplicationContext()或Activity的context // 第二个参数:要显示的字符串。也可是R.string中字符串ID // 第三个参数:显示的时间长短。Toast默认的有两个LENGTH_LONG(长)和LENGTH_SHORT(短),也可以使用毫秒如2000ms Toast

chali1314的博客 2万+

Toast窗口的源码分析

今天写个帖子,主要讲解一下我自己对Toast的理解和创建流程。 整个过程大部分都是分析源码。不多废话了,马上开始。 为了更加直观,先上使用代码: Toast.makeText(context, "hello toast", 1).show(); 咱们一般用Toast来提示信息,比如eoe的安卓客户端按两次返回的推出提示 咱们看下Toast的创建及显示过程吧:

com360的专栏 5230

Android 之 Toast 的基本使用

效果图关键代码//设置显示位置//设置字体颜色如果上面的那种还满足不了你的话,那么你完全可以自己写一个Toast的布局,然后显示出来;但是时间我们依旧控制不了!运行效果图关键代码还有自定义Toast的布局以及圆角背景:-- 设置透明背景色 -->-- 设置一个黑色边框 -->

leyang0910的博客 5230

Android入门第30天-Android里的Toast的使用

本篇带来的是: Android用于提示信息的一个控件——Toast(吐司)!Toast是一种很方便的消息提示框,会在 屏幕中显示一个消息提示框,没任何按钮,也不会获得焦点一段时间过后自动消失! 非常常用!我们通过一个例子把Toast的使用讲透!

打造全国最全的AI Agent开发知识领域的博客 5797

单细胞分析(五)——使用Harmony进行数据整合和去批次

为了保证单细胞RNA测序数据的准确性和可靠性,需要对数据进行样本去批次处理。去批次的方法包括Harmony,SeuratV3等,这些方法可以有效地消除批次效应和技术噪声,并提高单细胞RNA测序数据的质量,为后续的数据分析提供更加可靠的基础。

阅读文献,记录学习笔记 3万+

Android中Toast的显示和隐藏分析

PS:本文系转载文章,阅读原文可读性会更好些,原文链接:https://mp.weixin.qq.com/s/bbfxVHRg5BiLd6_cPsIU6Q ps:源码是基于 android api 27 来分析的,demo 是用 kotlin 语言写的。 Toast 作为 Android 系统中最常用的类之一,因为它方便的 API 设计和简洁的交互体验,所以我们会经常用到,也所以深入学习 Toast 也是很有必要的;在 Android 中,我们知道凡是有视图的地方就会有 Window,Toast 显示出来的

qq_37837621的博客 1828

海康工业相机硬件触发与YOLOv5实时检测的工业自动化实践

本文详细介绍了如何将海康工业相机的硬件触发模式与YOLOv5目标检测模型相结合,构建一套高实时性的工业自动化质检系统。文章从产线痛点出发,深入解析了硬件接线、SDK回调取图的实战技巧(包括规避常见Bug),并展示了如何将YOLOv5封装为服务,最终通过Flask API串联业务逻辑,实现从精准触发到实时AI判断的完整落地流程。

k6l7m8n9的博客 492

Toast源码分析(Android 11)

基于Android R(11) ,targetSdkVersion 30源码分析 基本用法 普通Toast: Toast.makeText(this@MainActivity, "hello Toast!", Toast.LENGTH_SHORT).show() 自定义View的Toast: val toast = Toast(this@MainActivity) val view = Button(this@MainActivity) view.text = "custom toast!".

guangdeshishe的博客 1783

为什么桥式传感器(Bridge Sensor)需要用到仪表放大器?

仪表放大器在桥式传感器的应用里起到关键作用,本文介绍桥式传感器的原理、特性,以及信号调理过程,并附上了一个 TINA - TI 的仿真电路。

疯狂的运放 1527

SPSS(二)SPSS实现多因素方差分析模型(图文教程+数据集)

SPSS(二)SPSS实现多因素方差分析模型 单因素方差分析上一篇博客https://blog.youkuaiyun.com/LuYi_WeiLin/article/details/89917656已经介绍完毕 这篇博客我们主要来学习多因素方差分析 多因素方差分析,就是同时考虑若干个控制因素的情况下,分别分析它们的改变是否造成观察变量的显著变动 (多个自变量,一个因变量)自变量类型以分类变量为主也可以...

可乐联盟 6万+

系统窗口Toast显示源码解析

系统窗口Toast源码解析,希望能给大家一些帮助,谢谢!

红旺永福 1581

Keil C51开发必看:详解data/xdata/bdata内存分配陷阱与避坑指南

本文深入解析Keil C51开发中的data/xdata/bdata内存分配陷阱,提供针对51芯片的高效避坑指南。通过真实案例揭示Keil编译器的隐藏规则,分享内存优化策略和调试技巧,帮助开发者解决data溢出、xdata访问异常等典型问题,提升嵌入式开发效率。

v6b7n8m9q0的博客 1072

Android不同版本toast,Android9.0上针对Toast的特殊处理图文详解

前言我们都清楚,Toast显示时长有两个选择,长显示是3.5秒,端显示是2秒。那如果想要做到长时间显示,该怎么做呢?有个历史遗留的app通过开一个线程,不断调用show方法进行实现,这些年也没出过问题,直到系统版本更新到了Android9.0。实现方式大概如下:mToast = new Toast(context);mToast.setDuration(Toast.LENGTH_LONG);mTo...

weixin_28771365的博客 649

YOLOv11 改进 - 特征融合 | RepGFPN重参数化特征金字塔网络 ,实现高效多尺度特征交互与融合

本文介绍了DAMO - YOLO目标检测方法及其相关技术在YOLOv11中的结合。DAMO - YOLO扩展了多项新技术,如MAE - NAS搜索检测主干网络、Efficient - RepGFPN构建检测neck等。在neck设计上,针对原始GFPN存在的问题,提出Efficient - RepGFPN,包括对不同尺度特征使用不同通道数、删除Queen - Fusion中额外上采样操作、改进特征融合方式等。我们将相关模块集成进YOLOv11,替换部分模块。

魔改工程师的博客 934

AUTOSAR进阶图解==>AUTOSAR_SWS_ECUStateManager

AUTOSAR ECU State Manager 摘要 AUTOSAR ECU状态管理器(EcuM)是Classic Platform 4.4.0规范中的核心模块,负责管理ECU的整个生命周期,包括启动、运行、关机和睡眠等状态转换。EcuM通过协调各基础软件模块的初始化过程,确保系统可靠运行。 主要功能包括: 启动管理 - 控制从硬件初始化到应用运行的完整启动序列 状态转换 - 管理RUN/POST_RUN/SHUTDOWN等状态切换 唤醒验证 - 区分真实与虚假唤醒事件 关机序列 - 确保安全关机保存关

一名留德华的,康德的信徒的,专注于汽车电子领域的博客 2471

Android窗口机制:八、Toast机制-(封装可以在任何线程使用的toast)。(源码版本SDK31)

Toast机制源码分析,源码版本SDK31. 封装可以在任何线程调用的toast

Pumpkin's Blog 4918

【ESP32CAM+Android+OpenCV】自定义目标数据集、训练自己的OpenCV模型部署到手机APP并对ESP32CAM无线图传显示并实时检测识别目标_资料编号30

本项目实现自定义目标数据集(想检测什么就检测什么,按照本文教程自己来更换目标)、训练自己的OpenCV模型实时检测识别(pycharm工程)。手机APP(Android studio工程)部署OpenCV模型实现对ESP32CAM(arduino工程)无线WIFI图传过来的图像实时检测识别。 本文以火焰检测识别为例进行演示,通过该过程,结合本文可自定义尝试更换为其它目标的检测,例如更换为车辆、车牌、手势、人脸面部活动、人脸表情、火焰烟雾、行人、口罩、行为、水果、植物、农作物、建筑、损伤等等部署进行检测。

嵌入式阿齐Archie 3479

Toast源码分析

1, 概述 Toast是一种向用户快速展示少量信息的视图。当它显示时,它会浮在整个应用层的上面,并且不会获取到焦点。它的设计思想是能够向用户展示些信息,但又能尽量不显得唐突。本篇我们来研读一下Toast的源码,并探明它的显示及隐藏机制。 2 实现 从Toast的最简单调用开始,它的调用代码是: Toast.makeText(context,"Show toast",Toast.LENGT

Jack的博客 619

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Toast源码解析

android 源码分析 Toast

hyc1988107的博客 944

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

android 强制关闭toast,Android 程序退出 Toast还一直显示 解决方案

今天,改了个bug:点击两次返回程序退出。如大家所想,第一次点击用Toast提示,如果在两秒内再次点击那么程序退出。在我们平时写App的时候,习惯用Application的上下文对象,但是可能大家不是很清楚这个上下文对象到底有什么用,当然大家可以目前认为是一个依附的对象。接下来说说问题吧:App退出之后,我的Toast还在显示,一直消失不了。先说说我的操作吧:@Overridepublic boo...

weixin_35457969的博客 1616

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

android Toast 吐司 详解

### 什么是土司(Toast)? Toast是Android系统提供的一种非常好的提示方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何的屏幕空间; ### 下面我们通过源代码分析Toast: 前面我们分析了Activity、Dialog、PopupWindow的加载绘制流程,相信大家对整个Android系统中的窗口绘制流程已经有了一个比较...

一航jason 1378

Toast解析《二》

Toast显示流程:源码分析 前提:不熟悉Toast基础知识的可以先查看我的上一篇文章Toast解析《一》 时序图如下: Toast创建 Toast创建分为两种:直接使用构造函数调用和使用makeText调用,我们以经常使用的makeText进行讲解; makeText源码: public static Toast makeText(Context context...

Sunrise的专栏 661

Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析 《五》-Toast

5 Android应用Toast窗口添加显示机制源码 5-1 基础知识准备在开始分析这几个窗口之前需要脑补一点东东,我们从应用层开发来直观脑补,这样下面分析源码时就不蛋疼了。如下是一个我们写的两个应用实现Service跨进程调用服务ADIL的例子,客户端调运远程Service的start与stop方法控制远程Service的操作。Android系统中的应用程序都运行在各自的进程中,进程之间是无法直

枫的专栏 845

Toast BadTokenException原因及修复方案

Toast这一基本的操作,居然也能够产生crash,你可能难以置信

奋斗 1432

Toast原理解析

谈到Toast,我想只要是做过Android开发的恐怕没有不知道的吧,毫无疑问,Toast是一个非常方便的组件,能在任何能获取上下文的地方进行弹窗提示,既然是弹窗那么我们就可以知道,Toast应该是Window的一部分了。在这篇文章中,不对Window讲解,默认这部分内容是已知的。我对Toast的理解最先来自 艺术探索 这本书,在了解了其基本原理之后,我觉得自己去摸索下源码来巩固自己的理解,于是有了

rainbowchou的技术日常 3771
上一篇: SystemServer类解析
下一篇: 子线程能弹Toast吗
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android海纳百川

打赏加微信,送跑车加管理

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值