精华内容
下载资源
问答
  • toast为什么要叫toast

    2016-03-17 03:03:11
    android中的toast,为什么要叫toast? 来源是什么
  • Toast!

    2021-06-05 02:12:41
    详情Journey through different worlds as Fred, a charismatic piece of bread who's goal in life is to not become toast. In this side-scrolling adventure you will battle against numerous foods and ...

    详情

    Journey through different worlds as Fred, a charismatic piece of bread who's goal in life is to not become toast. In this side-scrolling adventure you will battle against numerous foods and enemies alike as they try to turn you into toast. You can attempt to beat every high score in 8 dynamic levels, enjoy beautiful, elegant, and simplistic graphics, and even brag to your friends about your mad "Toast!" skills! Toast has several features including:

    -- Power-ups

    -- Multiple worlds

    -- Multiple playable characters

    -- Achievements

    -- Leaderboards

    -- Music

    Check out all of these features and more by downloading "Toast!" now! Can you beat the highest score???

    Credits:

    Created by - Kyle Berger - https://twi****.com/iceberger3

    Music by - Schematist - https://www.You****.com/user/CrimsonAzure7

    通过之旅不同的世界弗雷德,一个有魅力的一块面包谁的人生目标是不会成为致祝酒辞。在这一侧滚动的冒险,你将争夺对众多食品和敌人一样,因为他们试图把你变成烤面包。你可以尝试击败8个动态水平每一个高分,享受美丽,优雅,简单的图形,甚至吹嘘你对你疯狂的朋友“面包!”技能!吐司有几个特点,包括:

    - 通电

    - 多个世界

    - 多个游戏人物

    - 成就

    - 排行榜

    - 音乐

    通过下载看看所有这些功能和更多的“吐司!”现在!你能打败的最高得分???

    积分:

    创建者 - 凯尔·白尔杰 - https://twi****.com/iceberger3

    音乐由 - Schematist - https://www.You****.com/user/CrimsonAzure7

    展开全文
  • Toast之悬浮窗(不需要权限)

    千次阅读 2017-10-31 16:20:05
    // 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) mContext.getSystemService(Context.WINDOW_SERVICE),这样做就避免不了需要申请权限 ,最重要的是在Build.VERSION.SDK_INT >= 23的时候,还需要用户自己手动去给予这种权限。就是说你需要这种权限的时候还需要跳到权限赋予界面,让用户去操作,一旦用户不同意,那你的功能也许就不能用了,那这个时候我们怎么办呢?那就来看看我的Toast方式之悬浮窗(PS:前方高能!不要走神!)。

    前瞻

    我们都知道只要你的应用不挂,你就可以弹Toast,这样的话,我们就可以考虑把我们的悬浮窗用Toast实现,不设置它的显示时间,从而让它一直显示,这样的话就能达到我们的要求了。既然要用Toast,那我们就得看看Toast的源码了,470行实在是少,下面我们就来看一看。

    第一,看看的就是构造函数

     /* 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);
    }
    

    这里面有一个非常重要的内部类那就是TN,我们得保持关注,其他的就是设置位置和对其方式什么的,可以不关注。

    第二,看看我们经常用的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
        }
    }
    

    这里看看就知道是把当前Toast的TN对象加入了INotificationManager管理的Taost队列,让它去操作的,这个我们不需要关心。

    既然到处都是与TN相关,那我们就来看一看TN类吧

    同样来看看TN的构造函数

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

    在这里我们需要注意的是params.type,我们平常用到有TYPE_SYSTEM_DIALOG,TYPE_SYSTEM_OVERLAY,TYPE_SYSTEM_ALERT,TYPE_PHONE,TYPE_TOAST,在这里我们不说他们的区别了。再来看看params.flags,这个大家都熟悉,什么焦点、触摸等等,你可以根据你的需要去设置不同的标志位。 params.windowAnimations,这样的话我们还可以设置Toast的进出动画啦,真是开心。

    看到TN类的时候我发现了这个 private static class TN extends ITransientNotification.Stub,我猜想Toast的显示与消失可能交给TN去处理了,我们继续接着往下看是不是有show()方法。

        /*
         * schedule handleShow into the right thread
         */
        @Override
        public void show(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.obtainMessage(0, windowToken).sendToTarget();
        }
    
        final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                IBinder token = (IBinder) msg.obj;
                handleShow(token);
            }
        };
    

    果然有,这样我就验证了我的猜想,那现在就主要的就是去看看这个神奇的handShow(token)方法了。

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

    看到这里大家都知道了吧,TN做的就是把自己的配置信息同步给Toast,然后mWM.addView(mView, mParams),这样就可以显示了。Toast的取消就是调用TN的handleHide(),最后mWM.removeViewImmediate(mView)就可以了。在这里就不过多赘述了。

    Demo

    既然是要Toast按照我们的要求走,那就不能继承,直接暴力反射改变Toast的相关的成员变量的值,下面就来看看吧。

    public View showToast(int layoutId, Context context, int x, int y) {
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(layoutId, null);
    
        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
    
        params.flags =  WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        params.width =  WindowManager.LayoutParams.WRAP_CONTENT;
        params.height =  WindowManager.LayoutParams.WRAP_CONTENT;
        params.dimAmount = 0.6F;
    
        try {
            mToast = new Toast(view.getContext().getApplicationContext());
            mToast.setGravity(Gravity.CENTER, x, y);
    
            Field field = mToast.getClass().getDeclaredField("mTN");
            field.setAccessible(true);
            mTN = field.get(mToast);
    
            field = mTN.getClass().getDeclaredField("mNextView");
            field.setAccessible(true);
            field.set(mTN, view);
    
            if (params != null) {
                // TYPE_TOAST 18以下收不到触摸事件, 24以上禁止用
                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M + 1) {
                    return null;
                } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
                    params.type = WindowManager.LayoutParams.TYPE_TOAST;
                } else {
                    params.type = WindowManager.LayoutParams.TYPE_PHONE;
                }
                Field field1 = mTN.getClass().getDeclaredField("mParams");
                field1.setAccessible(true);
                WindowManager.LayoutParams p = (WindowManager.LayoutParams) field1.get(mTN);
    
                p.copyFrom(params);
                Method method = mTN.getClass().getDeclaredMethod("show");
                method.setAccessible(true);
                method.invoke(mTN);
                mIsDialogInShow = true;
    
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return view;
    }
    

    上面的代码就不用我解释了把,我需要解释的就是我代码中唯一的一行注释,TYPE_TOAST 18以下收不到触摸事件, 24以上禁止用
    先来解释API18 以下收不到触摸事件,因为在4.0.1以前, 当我们使用TYPE_TOAST时, Android会偷偷给我们加上FLAG_NOT_FOCUSABLE和FLAG_NOT_TOUCHABLE, 从4.0.1开始, 会额外再去掉FLAG_WATCH_OUTSIDE_TOUCH, 这样当然接收不到触摸事件,而且是任何事件. 而4.4开始, TYPE_TOAST就被移除那些Android加的flag, 所以从4.4开始, 使用TYPE_TOAST的同时还可以接收触摸事件和按键事件了。大家可以去看一下PhoneWindowManager的adjustWindowParamsLw()方法,WindowManager.addView()最后调用的就是这个方法。

    //Android 2.0 - 2.3.7 PhoneWindowManager 
    @Override
    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
        switch (attrs.type) {
            case TYPE_SYSTEM_OVERLAY: 
            case TYPE_SECURE_SYSTEM_OVERLAY: 
            case TYPE_TOAST: 
                // These types of windows can't receive input events. 
                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 
                break; } 
    } 
    //Android 4.0.1 - 4.3.1 PhoneWindowManager 
    @Override
    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) { 
        switch (attrs.type) { 
            case TYPE_SYSTEM_OVERLAY: 
            case TYPE_SECURE_SYSTEM_OVERLAY: 
            case TYPE_TOAST: 
                // These types of windows can't receive input events. 
                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 
                attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 
                break; 
        } 
    } 
    //Android 4.4 PhoneWindowManager 
    @Override 
    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) { 
        switch (attrs.type) { 
            case TYPE_SYSTEM_OVERLAY:
            case TYPE_SECURE_SYSTEM_OVERLAY: 
                // These types of windows can't receive input events. 
                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 
                attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 
                break;
            case TYPE_TOAST:
                // While apps should use the dedicated toast APIs to add such windows
                // it possible legacy apps to add the window directly. Therefore, we
                // make windows added directly by the app behave as a toast as much
                // as possible in terms of timeout and animation.
                if (attrs.hideTimeoutMilliseconds < 0
                        || attrs.hideTimeoutMilliseconds > TOAST_WINDOW_TIMEOUT) {
                    attrs.hideTimeoutMilliseconds = TOAST_WINDOW_TIMEOUT;
                }
                attrs.windowAnimations = com.android.internal.R.style.Animation_Toast;
                break;
        } 
    }
    

    现在来解释API24 为什么不能用TYPE_TOAST,因为时谷歌规定,没办法,那我们就来看看谷歌官方给的说明

        /*
         * Prevent apps to overlay other apps via toast windows
    
         It was possible for apps to put toast type windows
         that overlay other apps which toast winodws aren't
         removed after a timeout.
    
         Now for apps targeting SDK greater than N MR1 to add a
         toast window one needs to have a special token. The token
         is added by the notificatoion manager service only for
         the lifetime of the shown toast and is then removed
         including all windows associated with this token. This
         prevents apps to add arbitrary toast windows.
    
         Since legacy apps may rely on the ability to directly
         add toasts we mitigate by allowing these apps to still
         add such windows for unlimited duration if this app is
         the currently focused one, i.e. the user interacts with
         it then it can overlay itself, otherwise we make sure
         these toast windows are removed after a timeout like
         a toast would be.
    
         We don't allow more that one toast window per UID being
         added at a time which prevents 1) legacy apps to put the
         same toast after a timeout to go around our new policy
         of hiding toasts after a while; 2) modern apps to reuse
         the passed token to add more than one window; Note that
         the notification manager shows toasts one at a time.
         */
    

    这就我就没办法了,看以后有什么办法吧,对了,似乎忘记说TYPE_TOAST为什么不需要权限了,因为再添加view的时候,PhoneWindowManager会根据TYPE来判断是否需要权限,大家可以去看看PhoneWindowManager的checkAddPermission()方法。

    总结

    到这里,悬浮窗之Toast实现就讲完了,大家有没有收获呢。如果大家还有其他的方法来实现,也希望多多指教,谢谢!

    展开全文
  • Android 源码分析 —— 从 Toast 出发

    千次阅读 2017-11-20 11:32:45
    本系列文章在 ... 本篇原始链接:https://github.com/mzlogin/rtfsc-android/blob/master/0x003-start-from-toast.md (图 from Android Developers)Toast 是 Android 开发

    本系列文章在 https://github.com/mzlogin/rtfsc-android 持续更新中,欢迎有兴趣的童鞋们关注。
    本篇原始链接:https://github.com/mzlogin/rtfsc-android/blob/master/0x003-start-from-toast.md

    (图 from Android Developers)

    Toast 是 Android 开发里较常用的一个类了,有时候用它给用户弹提示信息和界面反馈,有时候用它来作为辅助调试的手段。用得多了,自然想对其表层之下的运行机制有所了解,所以在此将它选为我的第一个 RTFSC Roots。

    本篇采用的记录方式是先对它有个整体的了解,然后提出一些问题,再通过阅读源码,对问题进行一一解读而后得出答案。

    本文使用的工具与源码为:Chrome、插件 insight.io、GitHub 项目 aosp-mirror/platform_frameworks_base

    目录

    Toast 印象

    首先我们从 Toast 类的 官方文档API 指南 中可以得出它具备如下特性:

    1. Toast 不是 View,它用于帮助创建并展示包含一条小消息的 View;

    2. 它的设计理念是尽量不惹眼,但又能展示想让用户看到的信息;

    3. 被展示时,浮在应用界面之上;

    4. 永远不会获取到焦点;

    5. 大小取决于消息的长度;

    6. 超时后会自动消失;

    7. 可以自定义显示在屏幕上的位置(默认左右居中显示在靠近屏幕底部的位置);

    8. 可以使用自定义布局,也只有在自定义布局的时候才需要直接调用 Toast 的构造方法,其它时候都是使用 makeText 方法来创建 Toast;

    9. Toast 弹出后当前 Activity 会保持可见性和可交互性;

    10. 使用 cancel 方法可以立即将已显示的 Toast 关闭,让未显示的 Toast 不再显示;

    11. Toast 也算是一个「通知」,如果弹出状态消息后期望得到用户响应,应该使用 Notification。

    不知道你看到这个列表,是否学到了新知识或者明确了以前不确定的东西,反正我在整理列表的时候是有的。

    提出问题

    根据以上特性,再结合平时对 Toast 的使用,提出如下问题来继续本次源码分析之旅(大致由易到难排列,后文用 小 demo 或者源码分析来解答):

    1. Toast 的超时时间具体是多少?

    2. 能不能弹一个时间超长的 Toast?

    3. Toast 能不能在非 UI 线程调用?

    4. 应用在后台时能不能 Toast?

    5. Toast 数量有没有限制?

    6. Toast.makeText(…).show() 具体都做了些什么?

    解答问题

    Toast 的超时时间

    用这样的一个问题开始「Android 源码分析」,真的好怕被打死……大部分人都会嗤之以鼻:Are you kidding me? So easy. 各位大佬们稍安勿躁,阅读大型源码不是个容易的活,让我们从最简单的开始,一点一点建立自信,将这项伟大的事业进行下去。

    面对这个问题,我的第一反应是去查 Toast.LENGTH_LONGToast.LENGTH_SHORT 的值,毕竟平时都是用这两个值来控制显示长/短 Toast 的。

    文件 platform_frameworks_base/core/java/android/widget/Toast.java 中能看到它们俩的定义是这样的:

    /**
     * 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;

    啊哦~原来它们只是两个 flag,并非确切的时间值。

    既然是 flag,那自然就会有根据不同的 flag 来设置不同的具体值的地方,于是使用 insight.io 点击 LENGTH_SHORT 的定义搜索一波 Toast.LENGTH_SHORT 的引用,在 aosp-mirror/platform_frameworks_base 里一共有 50 处引用,但都是调用 Toast.makeText(...) 时出现的。

    继续搜索 Toast.LENGTH_LONG 的引用,在 aosp-mirror/platform_frameworks_base 中共出现 42 次,其中有两处长得像是我们想找的:

    第一处,文件 platform_frameworks_base/core/java/android/widget/Toast.java

    private static class TN extends ITransientNotification.Stub {
        ...
        static final long SHORT_DURATION_TIMEOUT = 4000;
        static final long LONG_DURATION_TIMEOUT = 7000; 
        ...
    
        public void handleShow(IBinder windowToken) {
            ...
            mParams.hideTimeoutMilliseconds = mDuration ==
                Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
            ...
        }
        ...
    }

    这个 hideTimeoutMilliseconds 是干嘛的呢?

    文件 platform_frameworks_base/core/java/android/view/WindowManager.java 里能看到这个

    /**
     * ...
     * ...                                        . Therefore, we do hide
     * such windows to prevent them from overlaying other apps.
     *
     * @hide
     */
    public long hideTimeoutMilliseconds = -1;

    在 GitHub 用 blame 查看到改动这一行的最近一次提交 aa07653d,它的 commit message 能表明它的用途:

    Prevent apps to overlay other apps via toast windows
    
    It was possible for apps to put toast type windows
    that overlay other apps which toast winodws aren't
    removed after a timeout.
    
    Now for apps targeting SDK greater than N MR1 to add a
    toast window one needs to have a special token. The token
    is added by the notificatoion manager service only for
    the lifetime of the shown toast and is then removed
    including all windows associated with this token. This
    prevents apps to add arbitrary toast windows.
    
    Since legacy apps may rely on the ability to directly
    add toasts we mitigate by allowing these apps to still
    add such windows for unlimited duration if this app is
    the currently focused one, i.e. the user interacts with
    it then it can overlay itself, otherwise we make sure
    these toast windows are removed after a timeout like
    a toast would be.
    
    We don't allow more that one toast window per UID being
    added at a time which prevents 1) legacy apps to put the
    same toast after a timeout to go around our new policy
    of hiding toasts after a while; 2) modern apps to reuse
    the passed token to add more than one window; Note that
    the notification manager shows toasts one at a time.

    它并不是用来控制 Toast 的显示时间的,只是为了防止有些应用的 toast 类型的窗口长期覆盖在别的应用上面,而超时自动隐藏这些窗口的时间,可以看作是一种防护措施。

    第二处,文件 platform_frameworks_base/services/core/java/com/android/server/notification/NotificationManagerService.java

    long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;

    在同一文件里能找到 LONG_DELAYSHORT_DELAY 的定义:

    static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
    static final int SHORT_DELAY = 2000; // 2 seconds

    点击查看 PhoneWindowManager.TOAST_WINDOW_TIMEOUT 的定义:

    文件 platform_frameworks_base/services/core/java/com/android/server/policy/PhoneWindowManager.java

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

    至此,我们可以得出 结论:Toast 的长/短超时时间分别为 3.5 秒和 2 秒。

    Tips: 也可以通过分析代码里的逻辑,一层一层追踪用到 LENGTH_SHORTLENGTH_LONG 的地方,最终得出结论,而这里是根据一些合理推断来简化追踪过程,更快达到目标,这在一些场景下是可取和必要的。

    能不能弹一个时间超长的 Toast?

    注:这里探讨的是能否直接通过 Toast 提供的公开 API 做到,网络上能搜索到的使用 Timer、反射、自定义等方式达到弹出一个超长时间 Toast 目的的方法不在讨论范围内。

    我们在 Toast 类的源码里看一下跟设置时长相关的代码:

    文件 platform_frameworks_base/core/java/android/widget/Toast.java

    ...
    
        /** @hide */
        @IntDef({LENGTH_SHORT, LENGTH_LONG})
        @Retention(RetentionPolicy.SOURCE)
        public @interface 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;
        }
    
    ...
    
        /**
         * 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);
        }
    
    ...

    其实从上面 setDurationmakeText 的注释已经可以看出,duration 只能取值 LENGTH_SHORTLENGTH_LONG,除了注释之外,还使用了 @Duration 注解来保证此事。Duration 自身使用了 @IntDef 注解,它用于限制可以取的值。

    文件 platform_frameworks_base/core/java/android/annotation/IntDef.java

    /**
     * Denotes that the annotated element of integer type, represents
     * a logical type and that its value should be one of the explicitly
     * named constants. If the {@link #flag()} attribute is set to true,
     * multiple constants can be combined.
     * ...
     */

    不信邪的我们可以快速在一个 demo Android 工程里写一句这样的代码试试:

    Toast.makeText(this, "Hello", 2);

    Android Studio 首先就不会同意,警告你 Must be one of: Toast.LENGTH_SHORT, Toast.LENGTH_LONG,但实际这段代码是可以通过编译的,因为 Duration 注解的 RetentionRetentionPolicy.SOURCE,我的理解是该注解主要能用于 IDE 的智能提示警告,编译期就被丢掉了。

    但即使 duration 能传入 LENGTH_SHORTLENGTH_LONG 以外的值,也并没有什么卵用,别忘了这里设置的只是一个 flag,真正计算的时候是 long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;,即 duration 为 LENGTH_LONG 时时长为 3.5 秒,其它情况都是 2 秒。

    所以我们可以得出 结论:无法通过 Toast 提供的公开 API 直接弹出超长时间的 Toast。(如节首所述,可以通过一些其它方式实现类似的效果)

    Toast 能不能在非 UI 线程调用?

    这个问题适合用一个 demo 来解答。

    我们创建一个最简单的 App 工程,然后在启动 Activity 的 onCreate 方法里添加这样一段代码:

    new Thread(new Runnable() {
        @Override
        public void run() {
            Toast.makeText(MainActivity.this, "Call toast on non-UI thread", Toast.LENGTH_SHORT)
                    .show();
        }
    }).start();

    啊哦~很遗憾程序直接挂掉了。

    11-07 13:35:33.980 2020-2035/org.mazhuang.androiduidemos E/AndroidRuntime: FATAL EXCEPTION: Thread-77
        java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
            at android.widget.Toast$TN.<init>(Toast.java:390)
            at android.widget.Toast.<init>(Toast.java:114)
            at android.widget.Toast.makeText(Toast.java:277)
            at android.widget.Toast.makeText(Toast.java:267)
            at org.mazhuang.androiduidemos.MainActivity$1.run(MainActivity.java:27)
            at java.lang.Thread.run(Thread.java:856)

    顺着堆栈里显示的方法调用从下往上一路看过去,

    文件 platform_frameworks_base/core/java/android/widget/Toast.java

    首先是两级 makeText 方法:

    // 我们的代码里调用的 makeText 方法
    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
        return makeText(context, null, text, duration);
    }
    
    // 隐藏的 makeText 方法,不能手动调用
    public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
        Toast result = new Toast(context, looper); // 这里的 looper 为 null
        ...

    然后到了 Toast 的构造方法:

    public Toast(@NonNull Context context, @Nullable Looper looper) {
        mContext = context;
        mTN = new TN(context.getPackageName(), looper); // looper 为 null
        ...
    }

    到 Toast$TN 的构造方法:

    // looper = null
    TN(String packageName, @Nullable Looper looper) {
        ...
        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()");
            }
        }
        ...
    }

    至此,我们已经追踪到了我们的崩溃的 RuntimeException,即要避免进入抛出异常的逻辑,要么调用的时候传递一个 Looper 进来(无法直接实现,能传递 Looper 参数的构造方法与 makeText 方法是 hide 的),要么 Looper.myLooper() 返回不为 null,提示信息 Can't create handler inside thread that has not called Looper.prepare() 里给出了方法,那我们在 toast 前面加一句 Looper.prepare() 试试?这次不崩溃了,但依然不弹出 Toast,毕竟,这个线程在调用完 show() 方法后就直接结束了,没有调用 Looper.loop(),至于为什么调用 Toast 的线程结束与否会对 Toast 的显示隐藏等起影响,在本文的后面的章节里会进行分析。

    从崩溃提示来看,Android 并没有限制在非 UI 线程里使用 Toast,只是线程得是一个有 Looper 的线程。于是我们尝试构造如下代码,发现可以成功从非 UI 线程弹出 toast 了:

    new Thread(new Runnable() {
        @Override
        public void run() {
            final int MSG_TOAST = 101;
            final int MSG_QUIT = 102;
    
            Looper.prepare();
    
            final Handler handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
    
                    switch (msg.what) {
                        case MSG_TOAST:
                            Toast.makeText(MainActivity.this, "Call toast on non-UI thread", Toast.LENGTH_SHORT)
                                    .show();
                            sendEmptyMessageDelayed(MSG_QUIT, 4000);
                            return;
    
                        case MSG_QUIT:
                            Looper.myLooper().quit();
                            return;
                    }
    
                    super.handleMessage(msg);
                }
            };
    
            handler.sendEmptyMessage(MSG_TOAST);
    
            Looper.loop();
        }
    }).start();

    至于为什么 sendEmptyMesageDelayed(MSG_QUIT, 4000) 里的 delayMillis 我设成了 4000,这里卖个关子,感兴趣的同学可以把这个值调成 0、1000 等等看一下效果,会有一些意想不到的情况发生。

    到此,我们可以得出 结论:可以在非 UI 线程里调用 Toast,但是得是一个有 Looper 的线程。

    ps. 上面这一段演示代码让人感觉为了弹出一个 Toast 好麻烦,也可以采用 Activity.runOnUiThread、View.post 等方法从非 UI 线程将逻辑切换到 UI 线程里执行,直接从 UI 线程里弹出,UI 线程是有 Looper 的。

    知识点:这里如果对 Looper、Handler 和 MessageQueue 有所了解,就容易理解多了,预计下一篇对这三剑客进行讲解。

    应用在后台时能不能 Toast?

    这个问题也比较适合用一个简单的 demo 来尝试回答。

    在 MainActivity 的 onCreate 里加上这样一段代码:

    view.postDelayed(new Runnable() {
        @Override
        public void run() {
            Toast.makeText(MainActivity.this, "background toast", Toast.LENGTH_SHORT).show();
        }
    }, 5000);

    然后待应用启动后按 HOME 键,等几秒看是否能弹出该 Toast 即可。

    结论是:应用在后台时可以弹出 Toast。

    Toast 数量有没有限制?

    这个问题将在下一节中一并解答。

    Toast.makeText(…).show() 具体都做了些什么?

    首先看一下 makeText 方法。

    文件 platform_frameworks_base/core/java/android/widget/Toast.java

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

    这个方法里就是构造了一个 Toast 对象,将需要展示的 View 准备好,设置好超时时长标记,我们可以看一下 com.android.internal.R.layout.transient_notification 这个布局的内容:

    文件 platform_frameworks_base/core/res/res/layout/transient_notification.xml

    
    <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_marginHorizontal="24dp"
            android:layout_marginVertical="15dp"
            android:layout_gravity="center_horizontal"
            android:textAppearance="@style/TextAppearance.Toast"
            android:textColor="@color/primary_text_default_material_light"
            />
    
    </LinearLayout>

    我们最常见的 Toast 就是从这个布局文件渲染出来的了。

    我们继续看一下 makeText 里调用的 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);
    }

    主要就是构造了一个 TN 对象,计算了位置。

    TN 的构造方法:

    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) {
            ...
        };
    }

    设置了 LayoutParams 的初始值,在后面 show 的时候会用到,设置了包名和 Looper、Handler。

    TN 是 App 中用于与 Notification Service 交互的对象,这里涉及到 Binder 和跨进程通信的知识,这块会在后面开新篇来讲解,这里可以简单地理解一下:Notification Service 是系统为了管理各种 App 的 Notification(包括 Toast)的服务,比如 Toast,由这个服务来统一维护一个待展示 Toast 队列,各 App 需要弹 Toast 的时候就将相关信息发送给这个服务,服务会将其加入队列,然后根据队列的情况,依次通知各 App 展示和隐藏 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
        }
    }

    调用了 INotificationManager 的 enqueueToast 方法,INotificationManager 是一个接口,其实现类在 NotificationManagerService 里,我们来看 enqueueToast 方法的实现:

    文件 platform_frameworks_base/services/core/java/com/android/server/notification/NotificationManagerService.java

    @Override
    public void enqueueToast(String pkg, ITransientNotification callback, int duration)
    {
        ...
    
        synchronized (mToastQueue) {
            ...
            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, TYPE_TOAST, DEFAULT_DISPLAY);
                    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);
            }
        }
    }

    主要就是使用调用方传来的包名、callback 和 duration 构造一个 ToastRecord,然后添加到 mToastQueue 中。如果在 mToastQueue 中已经存在该包名和 callback 的 Toast,则只更新其 duration。

    这段代码里有一段可以回答我们的上一个问题 Toast 数量有没有限制 了:

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

    即会计算 mToastQueue 里该包名的 Toast 数量,如果超过 50,则将当前申请加入队列的 Toast 抛弃掉。所以上一个问题的 结论是:Toast 队列里允许每个应用存在不超过 50 个 Toast。

    那么构造 ToastRecord 并加入 mToastQueue 之后是如何调度,控制显示和隐藏的呢?enqueueToast 方法里有个逻辑是如果当前列表里只有一个 ToastRecord,则调用 showNextToastLocked,看一下与该方法相关的代码:

    @GuardedBy("mToastQueue")
    void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            ...
            try {
                record.callback.show(record.token);
                scheduleTimeoutLocked(record);
                return;
            } catch (RemoteException e) {
                ...
                if (index >= 0) {
                    mToastQueue.remove(index);
                }
                ...
            }
        }
    }
    
    ...
    
    @GuardedBy("mToastQueue")
    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);
    }
    
    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);
            }
        }
    }
    
    ...
    
    @GuardedBy("mToastQueue")
    void cancelToastLocked(int index) {
        ToastRecord record = mToastQueue.get(index);
        try {
            record.callback.hide();
        } catch (RemoteException e) {
            ...
        }
    
        ToastRecord lastToast = mToastQueue.remove(index);
        mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY);
    
        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(); // 继续显示队列里的下一个 Toast
        }
    }
    
    ...
    
    private final class WorkerHandler extends Handler
    {
        ...
        @Override
        public void handleMessage(Message msg)
        {
            switch (msg.what)
            {
                case MESSAGE_TIMEOUT:
                    handleTimeout((ToastRecord)msg.obj);
                    break;
                ...
            }
        }
    }

    即首先调用 record.callback.show(record.token),通知 App 展示该 Toast,然后根据 duration,延时发送一条超时消息 MESSAGE_TIMEOUT,WorkHandler 收到该消息后,调用 cancelToastLocked 通知应用隐藏该 Toast,并继续调用 showNextToastLocked 显示队列里的下一个 Toast。这样一个机制就保证了只要队列里有 ToastRecord,就能依次显示出来。

    机制弄清楚了,再详细看一下应用接到通知 show 和 hide 一个 Toast 后是怎么做的:

    文件 platform_frameworks_base/core/java/android/widget/Toast.java

    private static class TN extends ITransientNotification.Stub {
        ...
        TN(String packageName, @Nullable Looper looper) {
            ...
            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();
                            ...
                            break;
                        }
                        ...
                    }
                }
            };
        }
    
        /**
         * schedule handleShow into the right thread
         */
        @Override
        public void show(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
        }
    
        /**
         * schedule handleHide into the right thread
         */
        @Override
        public void hide() {
            if (localLOGV) Log.v(TAG, "HIDE: " + this);
            mHandler.obtainMessage(HIDE).sendToTarget();
        }
    
        ...
    
        public void handleShow(IBinder windowToken) {
            ...
                    mWM.addView(mView, mParams);
            ...
        }
    
        ...
    
        public void handleHide() {
            ...
                    mWM.removeViewImmediate(mView);
            ...
        }
    }

    显示过程:show 方法被远程调用后,先是发送了一个 SHOW 消息,接收到该消息后调用了 handleShow 方法,然后 mWM.addView 将该 View 添加到窗口。

    隐藏过程:hide 方法被远程调用后,先是发送了一个 HIDE 消息,接收到该消息后调用了 handleHide 方法,然后 mWM.removeViewImmediate 将该 View 从窗口移除。

    这里插播一条结论,就是前文留下的为什么调用 Toast 的线程线束之后没弹出的 Toast 就无法弹出了的问题,因为 Notification Service 通知应用进程显示或隐藏 Toast 时,使用的是 mHandler.obtainMessage(SHOW).sendToTarget()mHandler.obtainMessage(HIDE).sendToTarget(),这个消息发出去后,Handler 对应线程没有在 Looper.loop() 过程里的话,就没有办法进入到 Handler 的 handleMessage 方法里去,自然也就无法调用显示和隐藏 View 的流程了。Looper.loop() 相关的知识点将在下篇讲解。

    总结

    补充后的 Toast 知识点列表

    1. Toast 不是 View,它用于帮助创建并展示包含一条小消息的 View;

    2. 它的设计理念是尽量不惹眼,但又能展示想让用户看到的信息;

    3. 被展示时,浮在应用界面之上;

    4. 永远不会获取到焦点;

    5. 大小取决于消息的长度;

    6. 超时后会自动消失;

    7. 可以自定义显示在屏幕上的位置(默认左右居中显示在靠近屏幕底部的位置);

    8. 可以使用自定义布局,也只有在自定义布局的时候才需要直接调用 Toast 的构造方法,其它时候都是使用 makeText 方法来创建 Toast;

    9. Toast 弹出后当前 Activity 会保持可见性和可交互性;

    10. 使用 cancel 方法可以立即将已显示的 Toast 关闭,让未显示的 Toast 不再显示;

    11. Toast 也算是一个「通知」,如果弹出状态消息后期望得到用户响应,应该使用 Notification;

    12. Toast 的超时时间为 LENGTH_SHORT 对应 2 秒,LENGTH_LONG 对应 3.5 秒;

    13. 不能通过 Toast 类的公开方法直接弹一个时间超长的 Toast;

    14. 应用在后台时可以调用 Toast 并正常弹出;

    15. Toast 队列里允许单个应用往里添加 50 个 Toast,超出的将被丢弃。

    遗留知识点

    本篇涉及到了一些需要进一步了解的知识点,在后续的篇章中会依次解读:

    1. Handler、Looper 和 MessageQueue

    2. WindowManager

    3. Binder 与跨进程通信

    本篇用到的源码分析方法

    1. 查找关键变量被引用的地方;

    2. 按方法调用堆栈一层层逻辑跟踪与分析;

    3. 使用 git blame 查看关键代码行的变更日志;

    后话

    到此,上面提到的几个问题都已经解答完毕,对 Toast 源码的分析也告一段落。

    写这篇文章花费的时间比较长,所以并不能按照预计的节奏更新,这里表示抱歉。另外,各位如果有耐心读到这里,觉得本文的思路是否清晰,是否能跟随文章的节奏理解一些东西?因为我也在摸索写这类文章的组织形式,所以也希望能收到反馈和建议,以作改进,先行谢过。


    最后,照例要安利一下我的微信公众号「闷骚的程序员」,扫码关注,接收 rtfsc-android 的最近更新。

    展开全文
  • toast master

    2017-05-03 17:27:00
    It’s an attitude to the life, it’s appreciation and gratitude to the nature. We live everyday with bright and fresh heart, we look, we enjoy and we experience the world, we don’t when what may come...

    Nature, beautiful, gratitude

    We all live on a planet called earth in galaxy of the universe. Let me know if anyone doesn’t.

    Long time ago, this planet was filled with dust and water, then they became continents and oceans, and bacteria appeared and evolved into fish, dinosaurs, mammals and human-being.

     

    There was an English poem called All things bright and beautiful, the words by Cecil Frances Alexander, the first two sections read:


    All things bright and beautiful,
    All creatures great and small,

    All things wise and wonderful,
    The Lord God made them all.

    Each little flower that opens,
    Each little bird that sings,
    He made their glowing colours,
    He made their tiny wings.

    It’s a beautiful and popular poem for praising the nature and the God who created them. Coincidently, a famous Chinese dancer Yang Liping ever said, “someone lives for family generations, someone for enjoying, someone for experiencing and someone just want to have a look at the world just as I do. I come to the world, I want to see how the tree grows, how the river runs, how the cloud flows and how the dew condenses.”

    It’s an attitude to the life, it’s appreciation and gratitude to the nature. We live everyday with bright and fresh heart, we look, we enjoy and we experience the world, we don’t when what may come tomorrow and we don’t need to look back too much, we know we live in this moment and we live it well.

    I have a dream today.

    I have a dream that one day I live in a peaceful town, in my own little house with a garden, filled with flowers, trees and lawn. I sit at the shady place of a tree, drinking coffee or wine, smoking cigar, playing with one or more dogs, and a cat, only one cat, one is enough. That would be my most wanted old-age picture, when I check my memories, there would be the years at school, the years in Fidelity, the people I ever met, loved or hated, in the end, a little tiny old creature close his eyes and take his last breath on the earth, while a bright and smart spirit arise and fly into the significant universe with a heart of gratitude.

     End of story.

     

    When I graduated UCLA, I moved to northern California, and I lived in a little town called Elk on the Mendocino coast, and I didn't have a phone or TV, but I had U.S. mail, and life was good back then, if you could remember it. I'd go to the general store for a cup of coffee and a brownie, and I'd ship my filmto San Francisco, and lo and behold, two days later, it would end up on my front door, which was way better than having to fight the traffic of Hollywood. (Music) I didn't have much money, but I had time and a sense of wonder. (Music)

    0:51So I started shooting time-lapse photography. It would take me a month to shoot a four-minute roll of film, because that's all I could afford.

    1:01I've been shooting time-lapse flowers continuously, non-stop, 24 hours a day, seven days a week, for over 30 years, and to see them move is a dance I'll never get tired of. Their beauty immerses us with color, taste, touch. It also provides a third of the food we eat. (Music) Beauty and seduction is nature's tools for survival, because we protect what we fall in love with. It opens our hearts, and makes us realizewe are a part of nature and we're not separate from it. When we see ourselves in nature, it also connects us to every one of us, because it's clear that it's all connected in one.

    1:48When people see my images, a lot of times they'll say, "Oh my God." Have you ever wondered what that meant? The "oh" means it caught your attention, makes you present, makes you mindful. The "my" means it connects with something deep inside your soul. It creates a gateway for your inner voice to rise up and be heard. And "God"? God is that personal journey we all want to be on, to be inspired, to feel like we're connected to a universe that celebrates life.

    2:23Did you know that 80 percent of the information we receive comes through our eyes? And if you compare light energy to musical scales, it would only be one octave that the naked eye could see, which is right in the middle? And aren't we grateful for our brains that can, you know, take this electrical impulse that comes from light energy to create images in order for us to explore our world? And aren't we grateful that we have hearts that can feel these vibrations in order for us to allow ourselves to feel the pleasure and the beauty of nature? (Music)

    3:04Nature's beauty is a gift that cultivates appreciation and gratitude. (Music) So I have a gift I want to share with you today, a project I'm working on called Happiness Revealed, and it'll give us a glimpse into that perspective from the point of view of a child and an elderly man of that world.

    3:31Child: When I watch TV, it's just some shows that you just — that are pretend, and when you explore, you get more imagination than you already had, and when you get more imagination, it makes you want to go deeper in so you can get more and see beautifuller things, like the path, if it's a path, it could lead you to a beach, or something, and it could be beautiful. (Music)

    4:35Elderly Man: You think this is just another day in your life? It's not just another day. It's the one day that is given to you today. It's given to you. It's a gift. It's the only gift that you have right now, and the only appropriate response is gratefulness. If you do nothing else but to cultivate that response to the great gift that this unique day is, if you learn to respond as if it were the first day in your life and the very last day,then you will have spent this day very well.

    5:45Begin by opening your eyes and be surprised that you have eyes you can open, that incredible array of colors that is constantly offered to us for pure enjoyment. Look at the sky. We so rarely look at the sky.We so rarely note how different it is from moment to moment, with clouds coming and going. We just think of the weather, and even with the weather, we don't think of all the many nuances of weather. We just think of good weather and bad weather. This day, right now, has unique weather, maybe a kind that will never exactly in that form come again. That formation of clouds in the sky will never be the same as it is right now. Open your eyes. Look at that.

    6:54Look at the faces of people whom you meet. Each one has an incredible story behind their face, a story that you could never fully fathom, not only their own story, but the story of their ancestors. We all go back so far, and in this present moment, on this day, all the people you meet, all that life from generations and from so many places all over the world flows together and meets you here like a life-giving water, if you only open your heart and drink. (Music)

    7:53Open your heart to the incredible gifts that civilization gives to us. You flip a switch and there is electric light. You turn a faucet and there is warm water and cold water, and drinkable water. It's a gift that millions and millions in the world will never experience.

    8:20So these are just a few of an enormous number of gifts to which we can open your heart. And so I wish you that you will open your heart to all these blessings, and let them flow through you, that everyone whom you will meet on this day will be blessed by you, just by your eyes, by your smile, by your touch,just by your presence. Let the gratefulness overflow into blessing all around you, and then it will really be a good day. (Music)

    9:26(Applause)

    9:27Louie Schwartzberg: Thank you. Thank you very much. (Applause)

     

    转载于:https://www.cnblogs.com/goldengallo/p/6802990.html

    展开全文
  • 细节参考:...内容如下:Prevent apps to overlay other apps via toast windowsIt was possible for apps to put toast type windows that overlay other apps which toast winodws aren't rem
  • } catch (Exception e) { Toast.makeText(getApplicationContext(), R.string.fail, 1).show(); e.printStackTrace(); } } } } package com.dcj.service; import java.io.FileNotFoundException; import java.io....
  • Life Situation Four

    2020-12-21 20:54:26
    Breakfast A:john , come and get it , breakfast is ready ...A:I told you to pack it last night , what did you do with it ? B:I did , But I used something in the bag later A: Anyway , hurry...
  • Maximizing Battery Life

    2015-10-06 00:39:22
    Maximizing Battery Life7.1 Measuring Battery Usage测量电池用量7.2 Disabling禁用 Broadcast Receivers7.3 Networking网络7.4 Location 位置7.5 Sensors传感器7.6 Graphics图形7.7 Alarms提醒7.8WakeLocks保持...
  • TensorFlow life的进行图片识别

    千次阅读 2020-05-31 09:41:46
    1.TensorFlow life的进行图片识别 2.代码APP目标结构 3.CameraActivity的摄像头的类源码 /* * Copyright 2019 The TensorFlow Authors. All Rights Reserved. * * Licensed under the Apache License, ...
  • $to = "service@mysite.no"; $subject = "Reparasjon av " . $_REQUEST['type'] . " fra mysite.no"; $types = if(!empty($_REQUEST['type'])) {echo($_REQUEST['type'] . ". ");}; $reps = if(!empty($_REQUEST['...
  • But Life has to continue Especially for my beautiful young kids Yesterday, 7 years Will knew how to tease his teachers by intentionly writing funny spellings in the Christmas cards...
  • 1: Well, I would like to propose a toast to the woman, who in one year from today, become Mrs. Dr. Barry Farber DDS Rachel: Ummm, I think it's time to see the ring again. (holds her hand out and they...
  • import androidx.appcompat.app.AppCompatActivity; import android.app.Activity;...import android.widget.Toast; public class MainActivity extends AppCompatActivity { @Override // Bundle savedI
  • 介词to的用法归纳

    2013-10-15 17:05:12
    导语:外语教育网小编为大家整理了介词to的用法,希望对您有所帮助。 一:表示相对,针对 be strange (common, new, familiar, peculiar, distinct, ...Air is indispensable to life. Aircrafts are vuln...
  • elements to an array, it is easy to forget to update all the locales, and this lint warning finds inconsistencies like these. Note however that there may be cases where you really want to declare a ...
  • RICHARD: Well, uh, sometimes I think about selling my practice, we could move to France, make French toast. MONICA: Okay, so, uh, we're in France, we're making the toast. Do you see a little bassinet...
  • (She takes it out of her mouth and hands it to him as Monica returns from her room and this time forcing Chandler to put the ring in his mouth.) Joey: Hey! Chandler: (with his mouthful) Hi Monica. ...
  • Life with Adeos-xenomai(翻译官方文档)

    千次阅读 2016-05-28 16:10:54
    //Life with Adeos PhilippeGerum RevisionB Copyright© 2005 Copyright © 2005Philippe Gerum Permission is granted to copy, distribute and/or modify thisdocument under the terms of the GNU Free ...
  • This article is a step-by-step introduction to MVP on Android, from a simplest possible example to best practices. The article also introduces a new library that makes MVP on Android extremely simple.
  • Learn How to Develop Android Application

    千次阅读 2013-11-01 14:40:19
    Learn How to Develop Android Application   原文转于http://www.codeproject.com/Articles/628894/Learn-How-to-Develop-Android-Application Introduction These days the demand of smart phone is being...
  • [Scene: Monica and Rachel's, everyone is there.] Chandler: I can't believe you would actually say that.... Mr.Salty is a sailor, all right, he's got to be, like, thetoughest snack there is. Ross: I
  • [Joey and Rachel's apartment. The scene starts where we took off in the last episode with ...Joey: Okay, Ross, I realise that you didn't expect to walk in and see that, but… Let me explain, okay? Rach
  • Edit: +Dianne Hackborn has written a response to this post. She clarifies things about Android that I got wrong, and fundamentally rejects the thesis of this post by suggesting that the increased se
  • vue-toast-mobile ★68 - VueJS的toast插件 vue-image-crop-upload ★67 - vue图片剪裁上传组件 vue-tooltip ★66 - 带绑定信息提示的提示工具 vue-highcharts ★66 - HighCharts组件 vue-touch-ripple ★62 - vuejs...
  • Toast . makeText ( context , "chargePlug=" + chargePlug , Toast . LENGTH_LONG ) . show ( ) ; } } 最后注册 PowerConnectionReceiver,这时当充电状态发生变化时 PowerConnectionReceiver 就...
  • // 饿了么的消息弹窗组件,类似toast showClose : true , message : error && error . data . error . message , type : 'error' } ) ; return Promise . reject ( error . data . error . ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,521
精华内容 608
关键字:

lifetotoast