博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android中通过反射来设置Toast的显示时间
阅读量:6075 次
发布时间:2019-06-20

本文共 28178 字,大约阅读时间需要 93 分钟。

这个Toast的显示在Android中的用途还是非常大的,同一时候我们也知道toast显示的时间是不可控的。我们仅仅能改动他的显示样式和显示的位置,尽管他提供了一个显示时间的设置方法。可是那是没有效果的(后面会说到)。他有两个静态的常量Toast.SHORT和Toast.LONG,这个在后面我会在源码中看到这个两个时间事实上是2.5s和3s。

那么我们假设真想控制toast的显示时间该怎么办呢?真的是无计可施了吗?天无绝人之路,并且Linux之父以前说过:遇到问题就去看那个操蛋的源码吧。!以下就从源码開始分析怎么设置toast的显示时间的。

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, int duration) {        Toast result = new Toast(context);        LayoutInflater inflate = (LayoutInflater)                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);        tv.setText(text);                result.mNextView = v;        result.mDuration = duration;        return result;    }
这里面蕴含了非常多的信息的。从这里面我们能够知道Toast显示的布局文件时transient_notification.xml。关于这个文件,我们能够在源代码文件夹中搜索一下transient_notification.xml:

xml version="1.0" encoding="utf-8"?

> <!-- /* //device/apps/common/res/layout/transient_notification.xml ** ** Copyright 2006, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ --> <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>

看到了这个布局是如此的简单,里面显示的内容就是使用TextView来操作的。当然我们也能够改动这个布局的,他提供了一个setView方法。我们能够自己定义样式来进行显示的:

Toast toast = new Toast(this);View v = LayoutInflater.from(this).inflate(R.layout.activity_main, null);toast.setView(v);toast.show();
R.layout.activity_main是我们自己的布局文件

同一时候我们也能够看到Toast.makeText方法也会返回一个Toast,在这种方法里我们看到他是使用系统的布局文件,然后在哪个TextView中进行显示内容,同一时候返回这个Toast,所以假设我们想得到这个系统的显示View能够使用这种方法得到一个Toast,然后再调用getView方法就能够得到了,同一时候我们也是能够在这个view上继续加一下我们相加的控件,可是这样做是不是必需的,这里仅仅是说一下。

以下接着来看一下显示的show方法吧:

/**     * Show the view for the specified duration.     */    public void show() {        if (mNextView == null) {            throw new RuntimeException("setView must have been called");        }        INotificationManager service = getService();        String pkg = mContext.getPackageName();        TN tn = mTN;        tn.mNextView = mNextView;        try {            service.enqueueToast(pkg, tn, mDuration);        } catch (RemoteException e) {            // Empty        }    }

这种方法非常easy的,首先获取一个服务,然后将我们须要显示的toast放到这个服务的队列中进行显示,那么这里最基本的方法就是:

service.enqueueToast(pkg, tn, mDuration);
首先看一下这种方法的參数是:pkg:包名,mDuration:显示的时间。tn:显示回调的包装类

这里我们能够看到事实上最重要的參数是tn了,由于显示的逻辑可能就在这个类里面。找到源码:

private static class TN extends ITransientNotification.Stub {        final Runnable mShow = new Runnable() {            @Override            public void run() {                handleShow();            }        };        final Runnable mHide = new Runnable() {            @Override            public void run() {                handleHide();                // Don't do this in handleHide() because it is also invoked by handleShow()                mNextView = null;            }        };        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();        final Handler mHandler = new Handler();            int mGravity;        int mX, mY;        float mHorizontalMargin;        float mVerticalMargin;        View mView;        View mNextView;        WindowManager mWM;        TN() {            // XXX This should be changed to use a Dialog, with a Theme.Toast            // defined that sets up the layout params appropriately.            final WindowManager.LayoutParams params = mParams;            params.height = WindowManager.LayoutParams.WRAP_CONTENT;            params.width = WindowManager.LayoutParams.WRAP_CONTENT;            params.format = PixelFormat.TRANSLUCENT;            params.windowAnimations = com.android.internal.R.style.Animation_Toast;            params.type = WindowManager.LayoutParams.TYPE_TOAST;            params.setTitle("Toast");            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;        }        /**         * schedule handleShow into the right thread         */        @Override        public void show() {            if (localLOGV) Log.v(TAG, "SHOW: " + this);            mHandler.post(mShow);        }        /**         * schedule handleHide into the right thread         */        @Override        public void hide() {            if (localLOGV) Log.v(TAG, "HIDE: " + this);            mHandler.post(mHide);        }        public void handleShow() {            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView                    + " mNextView=" + mNextView);            if (mView != mNextView) {                // remove the old view if necessary                handleHide();                mView = mNextView;                Context context = mView.getContext().getApplicationContext();                if (context == null) {                    context = mView.getContext();                }                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);                // We can resolve the Gravity here by using the Locale for getting                // the layout direction                final Configuration config = mView.getContext().getResources().getConfiguration();                final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());                mParams.gravity = gravity;                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {                    mParams.horizontalWeight = 1.0f;                }                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {                    mParams.verticalWeight = 1.0f;                }                mParams.x = mX;                mParams.y = mY;                mParams.verticalMargin = mVerticalMargin;                mParams.horizontalMargin = mHorizontalMargin;                if (mView.getParent() != null) {                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);                    mWM.removeView(mView);                }                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);                mWM.addView(mView, mParams);                trySendAccessibilityEvent();            }        }        private void trySendAccessibilityEvent() {            AccessibilityManager accessibilityManager =                    AccessibilityManager.getInstance(mView.getContext());            if (!accessibilityManager.isEnabled()) {                return;            }            // treat toasts as notifications since they are used to            // announce a transient piece of information to the user            AccessibilityEvent event = AccessibilityEvent.obtain(                    AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);            event.setClassName(getClass().getName());            event.setPackageName(mView.getContext().getPackageName());            mView.dispatchPopulateAccessibilityEvent(event);            accessibilityManager.sendAccessibilityEvent(event);        }                public void handleHide() {            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);            if (mView != null) {                // note: checking parent() just to make sure the view has                // been added...  i have seen cases where we get here when                // the view isn't yet added, so let's try not to crash.                if (mView.getParent() != null) {                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);                    mWM.removeView(mView);                }                mView = null;            }        }    }
这个类也不复杂,我们看到他继承了一个类,这个类的形式不知道大家还熟悉吗?我们在前面介绍远程服务AIDL的时候看到过这样的形式的类,所以我们能够看到他使用Binder机制。我们能够在源码中搜索一下:ITransientNotification

看到了,果然是个aidl文件。我们打开看一下:

/* //device/java/android/android/app/ITransientNotification.aidl**** Copyright 2007, The Android Open Source Project**** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ****     http://www.apache.org/licenses/LICENSE-2.0 **** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License.*/package android.app;/** @hide */oneway interface ITransientNotification {    void show();    void hide();}
好吧。我们看到就是两个方法,一个是show显示。一个是隐藏hide,那就看他的实现了,回到上面的代码中:

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

TN类中的实现这两个方法,内部使用Handler机制:post一个mShow和mHide:

final Runnable mShow = new Runnable() {            @Override            public void run() {                handleShow();            }        };final Runnable mHide = new Runnable() {            @Override            public void run() {                handleHide();                // Don't do this in handleHide() because it is also invoked by handleShow()                mNextView = null;            }        };
再看方法:handleShow
public void handleShow() {            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView                    + " mNextView=" + mNextView);            if (mView != mNextView) {                // remove the old view if necessary                handleHide();                mView = mNextView;                Context context = mView.getContext().getApplicationContext();                if (context == null) {                    context = mView.getContext();                }                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);                // We can resolve the Gravity here by using the Locale for getting                // the layout direction                final Configuration config = mView.getContext().getResources().getConfiguration();                final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());                mParams.gravity = gravity;                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {                    mParams.horizontalWeight = 1.0f;                }                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {                    mParams.verticalWeight = 1.0f;                }                mParams.x = mX;                mParams.y = mY;                mParams.verticalMargin = mVerticalMargin;                mParams.horizontalMargin = mHorizontalMargin;                if (mView.getParent() != null) {                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);                    mWM.removeView(mView);                }                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);                mWM.addView(mView, mParams);                trySendAccessibilityEvent();            }        }

看一下TN的构造方法:

这种方法主要是来调节toast的显示位置,同一时候我们能够看到这个显示使用的是WindowManager控件,将我们toast的显示的视图view放到WindowManger中的。

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;        }
之所以用WindowManger。我猜原因非常easy,由于WindowManager是能够独立于Activity来显示的。我们知道toast在我们推出Activity的时候都还能够进行显示的。这个WindowManger用途也非常广泛的,那个360桌面清理小工具就是使用这个控件显示的(后台开启一个service就能够了,不须要借助Activity)。同一时候toast也提供了setGravity或者setMargin方法进行设置toast的显示位置。事实上这些设置就是在设置显示view在WindowManager中的位置

通过上面的知识我们也许略微理清了思路,就是首先借助TN类。全部的显示逻辑在这个类中的show方法中。然后再实例一个TN类变量。将传递到一个队列中进行显示,所以我们要向解决这个显示的时间问题。那就从入队列这部给截断。由于一旦toast入队列了,我们就控制不了,由于这个队列是系统维护的,所以我们如今的解决思路是:

1、不让toast入队列

2、然后我们自己调用TN类中的show和hide方法

第一个简单,我们不调用toast方法就能够了。可是第二个有点问题了,由于我们看到TN这个类是私有的。所以我们也不能实例化他的对象。可是toast类中有一个实例化对象:tn

final TN mTN;
擦。是包訪问权限,不是public的。这时候就要借助强大的技术,反射了。我们仅仅须要反射出这个变量。然后强暴她一次就可以,得到这个变量我们能够得到这个TN类对象了。然后再使用反射获取他的show和hide方法就可以,以下我们就来看一下实际的代码吧:
package com.weijia.toast;import java.lang.reflect.Field;import java.lang.reflect.Method;import android.content.Context;import android.view.View;import android.widget.Toast;public class ReflectToast {	    Context mContext;    private Toast mToast;    private Field field;    private Object obj;    private Method showMethod, hideMethod;    public ReflectToast(Context c, View v) {        this.mContext = c;        mToast = new Toast(mContext);        mToast.setView(v);        reflectionTN();    }    public void show() {        try {            showMethod.invoke(obj, null);        } catch (Exception e) {            e.printStackTrace();        }    }    public void cancel() {        try {            hideMethod.invoke(obj, null);        } catch (Exception e) {            e.printStackTrace();        }    }    private void reflectionTN() {        try {            field = mToast.getClass().getDeclaredField("mTN");            field.setAccessible(true);//强暴            obj = field.get(mToast);            showMethod = obj.getClass().getDeclaredMethod("show", null);            hideMethod = obj.getClass().getDeclaredMethod("hide", null);        } catch (Exception e) {            e.printStackTrace();        }    }}
这里我们实例化一个Toast对象,可是没有调用showf方法。就是不让toast入系统显示队列中,这样就能够控制show方法和hide方法的运行了,以下是測试代码:

package com.weijia.toast;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.TextView;public class MainActivity extends Activity {    ReflectToast toast;    boolean isShown = false;        @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        final TextView tView = new TextView(this);        tView.setText("ReflectToast !!!");        toast = new ReflectToast(this, tView);                findViewById(R.id.show_toast).setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {               if(isShown){                   toast.cancel();                   isShown = false;               }else{                    toast.show();                   isShown = true;               }            }        });            }}

通过一个button能够控制toast的显示了。想显示多长时间就显示多长时间

执行效果:

注意:这里有一个问题。我開始的时候用三星手机測试的。没有不论什么效果。然后换成小米手机也不行,最后用模拟器測试是能够的了。详细原因还在解决中。。。

上面就通过反射技术来实现toast的显示时间,可是到这里我们还没有完,反正都看到源代码了,那个核心的入队列的方法何不也看看呢?

INotificationManager service = getService();        String pkg = mContext.getPackageName();        TN tn = mTN;        tn.mNextView = mNextView;        try {            service.enqueueToast(pkg, tn, mDuration);        } catch (RemoteException e) {            // Empty        }

话说要是想找到这种方法还真是有点难度(反正我是找的好蛋疼,可是这次我也找到了规律了),看一下getService方法:

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

看到这里用使用了AIDL。当然我们能够在源码中搜一下INotificationManager:

/* //device/java/android/android/app/INotificationManager.aidl**** Copyright 2007, The Android Open Source Project**** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ****     http://www.apache.org/licenses/LICENSE-2.0 **** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License.*/package android.app;import android.app.ITransientNotification;import android.service.notification.StatusBarNotification;import android.app.Notification;import android.content.ComponentName;import android.content.Intent;import android.service.notification.INotificationListener;/** {@hide} */interface INotificationManager{    void cancelAllNotifications(String pkg, int userId);    void enqueueToast(String pkg, ITransientNotification callback, int duration);    void cancelToast(String pkg, ITransientNotification callback);    void enqueueNotificationWithTag(String pkg, String basePkg, String tag, int id,            in Notification notification, inout int[] idReceived, int userId);    void cancelNotificationWithTag(String pkg, String tag, int id, int userId);    void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);    boolean areNotificationsEnabledForPackage(String pkg, int uid);    StatusBarNotification[] getActiveNotifications(String callingPkg);    StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);    void registerListener(in INotificationListener listener, in ComponentName component, int userid);    void unregisterListener(in INotificationListener listener, int userid);    void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id);    void cancelAllNotificationsFromListener(in INotificationListener token);    StatusBarNotification[] getActiveNotificationsFromListener(in INotificationListener token);}
全是接口。这时候就蛋疼了,我们该怎样去找到这些实现呢?这次我就总结了一个方法:首先这是接口:所以名字是:INotificationManager。那么他的实现就可能是NotificationManager,我去源码中搜了一下发现的确有这个NotificationManager这个类,可是打开发现这个并没有实现上面的接口,这时候就想了,事实上吧,这个是AIDL,所以我们不可以依照常规的思路去找,既然是AIDL。那么肯定是Service有关的,所以我们去搜索NotificationMangerService(这个在我们搜NotificationManager的时候已经看到了)。打开看看:

果不其然实现了INotificationManager.Stub,我们仅仅看enqueueToast这种方法,也是toast入系统队列的方法,源代码例如以下:

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 ;        }        //推断是不是系统的包或者是系统的uid,是的话        final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));        if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {            if (!isSystemToast) {                Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");                return;            }        }                //入队列mToastQueue        synchronized (mToastQueue) {            int callingPid = Binder.getCallingPid();//获取当前进程id            long callingId = Binder.clearCallingIdentity();            try {                ToastRecord record;                //查看这个toast是否在当前队列中。有的话就返回索引                int index = indexOfToastLocked(pkg, callback);                //假设这个index大于等于0,说明这个toast已经在这个队列中了,仅仅须要更新显示时间就能够了                //当然这里callback是一个对象,pkg是一个String。所以比較的时候是对象的比較                if (index >= 0) {                    record = mToastQueue.get(index);                    record.update(duration);                } else {                    //非系统的toast                    if (!isSystemToast) {                    	//開始在队列中进行计数,假设队列中有这个toast的总数超过一定值,就不把toast放到队列中了                    	//这里使用的是通过包名来推断的。所以说一个app应用仅仅能显示一定量的toast                        int count = 0;                        final int N = mToastQueue.size();                        for (int i=0; i
= MAX_PACKAGE_NOTIFICATIONS) { Slog.e(TAG, "Package has already posted " + count + " toasts. Not showing more. Package=" + pkg); return; } } } } //将这个toast封装成ToastRecord对象。放到队列中 record = new ToastRecord(callingPid, pkg, callback, duration); mToastQueue.add(record); index = mToastQueue.size() - 1; keepProcessAliveLocked(callingPid); } //假设返回的索引是0,说明当前的这个存在的toast就在对头,直接显示 if (index == 0) { showNextToastLocked(); } } finally { Binder.restoreCallingIdentity(callingId); } } }

在Toast的TN对象中,会调用service.enqueueToast(String pkg,ItransientNotification callback,int duaraion)来将创建出来的Toast放入NotificationManagerService的ToastRecord队列中。

NotificationManagerService是一个执行在SystemServer进程中的一个守护进程,Android大部分的IPC通信都是通过Binder机制,这个守护进程像一个主管一样,全部的以下的人都必须让它进行调度,然后由它来进行显示或者是隐藏。
所以说,全部的调度机制都在Service中。

以下来看一下这种方法的逻辑吧:

final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));

首先,会推断pkg是否为android,假设为android的话,则表示为系统的包名。是系统Toast,则将isSystemToast标志为true。

// same as isUidSystem(int, int) for the Binder caller's UID.    boolean isCallerSystem() {        return isUidSystem(Binder.getCallingUid());    }
推断当前的应用用到的uid是不是系统的。假设是系统的isSystemToast标志为true

if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {            if (!isSystemToast) {                Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");                return;            }        }

接着推断是否为系统的Toast,假设是,则继续,假设不是。而且mBlockedPackages这个HashSet中包括这个包名的话。则会直接return,由于在NotificationManagerService中维护了这么一个HashSet<String>对象,里面包括一些不同意发送Toast与Notification的包名,假设包括在这个里面的话,则不同意显示Notification与Toast。

接着得到调用者的pid以及callingId,接着,通过pkg和callback得到在mToastQueue中相应的ToastRecord的index。

int index = indexOfToastLocked(pkg, callback);
看一下indexOfToastLocked方法:

// lock on mToastQueue    private int indexOfToastLocked(String pkg, ITransientNotification callback)    {        IBinder cbak = callback.asBinder();        ArrayList
list = mToastQueue; int len = list.size(); for (int i=0; i
我们看到这里是通过String的equals方法推断和对象引用的推断来得到这个toast是否存在队列中了,那么假设这个回调对象(这个就是我们之前说到的TN类),是不同的实例对象的话,就能够表示不存在。我们在之前的Toast中的show方法中看到:

TN tn = mTN;
这里的mTN是类变量,他是在Toast构造方法中进行实例化的。

private static final int MAX_PACKAGE_NOTIFICATIONS = 50;

假设index>=0的话。则说明这个Toast对象已经在mToastQueue中了。更新这个ToastRecord的时间。假设小于0的话,则说明没有加进去。就须要推断包名相应的ToastRecord的总数是否大于MAX_PACKAGE_NOTIFICATIONS,也就是50个,假设大于的话,就不同意应用再发Toast了,直接返回

假设没返回的话,就创建出一个ToastRecord对象。接着。将这个对象加到mToatQueue中。而且得到这个ToastRecord的index,而且通过方法keepProcessAliveLocked(其方法内部是调用ActivityManagerService.setProcessForeground)来设置这个pid相应的进程为前台进程。保证不被销毁,

private void keepProcessAliveLocked(int pid)    {        int toastCount = 0; // toasts from this pid        ArrayList
list = mToastQueue; int N = list.size(); for (int i=0; i
0); } catch (RemoteException e) { // Shouldn't happen. } }

这种方法中会通过这个pid到队列中进行查找属于这个进程id的toast总数,然后将设置这个进程是守护进程,这里我们可能会想起来就是,一个Activity退出的时候,toast还能够显示就是这原因,由于这个后台进程还在运行,我们能够在代码中測试一下,我们使用finish退出程序測试一下:

toast还在显示,当我们使用杀死进程的方式来退出程序的时候,发现就不显示了,

这里额外的说一下,Android中退出程序的方法:

Android程序有非常多Activity,比方说主窗体A,调用了子窗体B,假设在B中直接finish(), 接下里显示的是A。在B中怎样关闭整个Android应用程序呢?

本人总结了几种比較简单的实现方法。

1. Dalvik VM的本地方法
android.os.Process.killProcess(android.os.Process.myPid())    //获取PID 
System.exit(0);   //常规java、c#的标准退出法。返回值为0代表正常退出
2. 任务管理器方法
首先要说明该方法执行在Android 1.5 API Level为3以上才干够。同一时候须要权限
ActivityManager am = (ActivityManager)getSystemService (Context.ACTIVITY_SERVICE); 
am.restartPackage(getPackageName()); 
系统会将,该包下的 。所有进程,服务。所有杀掉,就能够杀干净了,要注意加上 
<uses-permission android:name=\"android.permission.RESTART_PACKAGES\"></uses-permission>
3. 依据Activity的声明周期
我们知道Android的窗体类提供了历史栈。我们能够通过stack的原理来巧妙的实现。这里我们在A窗体打开B窗体时在Intent中直接增加标志     Intent.FLAG_ACTIVITY_CLEAR_TOP。这样开启B时将会清除该进程空间的全部Activity。
在A窗体中使用以下的代码调用B窗体
Intent intent = new Intent(); 
intent.setClass(Android123.this, CWJ.class); 
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  //注意本行的FLAG设置 
startActivity(intent);
接下来在B窗体中须要退出时直接使用finish方法就可以所有退出。

上面仅仅是个补充知识以下接着来看假设上面的index为0的话,就说明是第一个。然后通过showNextToastLocked来显示Toast。

以下来看一下showNextToastLocked的代码:

private 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();                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);                }                keepProcessAliveLocked(record.pid);                if (mToastQueue.size() > 0) {                    record = mToastQueue.get(0);                } else {                    record = null;                }            }        }    }
我们看到首先到队列中取出第一个toast进行显示

record.callback.show();scheduleTimeoutLocked(record);
我们看到会调用回调对象中的show方法进行显示(这个回调对象就是我们之前说的TN对象)

我们再来看一下scheduleTimeoutLocked方法:

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中的延迟发信息来显示toast的,这里我们也看到了。延迟时间是duration,可是他值是仅仅有两个值:
private static final int LONG_DELAY = 3500; // 3.5 secondsprivate static final int SHORT_DELAY = 2000; // 2 seconds

仅仅有2s和3.5s这两个值,所以我们在之前说过我们设置toast的显示时间是没有不论什么效果的。

总结一下。我们是从源代码的角度来解决的问题的。并且这里还用到了反射的相关技术(事实上这个技术在后面说到静态安装的时候也会用到),所以说反射真是什么都能够,在这我们上面总是说到源代码文件夹中搜索,这个源代码下载地址非常多的,我用的是:这种方法。下载下来是个一个base文件夹,核心代码都在core文件夹中。以后遇到问题还是先看源代码,尽管代码看起来非常蛋疼,可是这也是没办法的。!

你可能感兴趣的文章
linux的nvme驱动需要关心的统计项
查看>>
{{badmatch, {error, eexist}}
查看>>
vue-music 关于基础组件 (Tab组件)
查看>>
用PL/SQL Developer导出表数据的时候,窗口一闪而过解决办法
查看>>
连接排查过程
查看>>
26.CSS前缀和rem
查看>>
java transient关键字
查看>>
mvc model 传值两种方式区别
查看>>
spring
查看>>
正方教务处抓包分析
查看>>
第一次作业
查看>>
openjudge2985(数字组合)
查看>>
步步为营 .NET 设计模式学习笔记 二十二、Memento(备望录模式)
查看>>
步步为营UML建模系列四、状态图(State)
查看>>
(7)javascript的程序控制结构及语句------(2)循环控制语句、跳转语句、对话框...
查看>>
asp.net上传图片
查看>>
如何修改EF的代码生成策略
查看>>
Yii2.0实现语言包切换功能
查看>>
寒假的Java学习笔记总结1
查看>>
C#判断操作系统的位数
查看>>