Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中通過反射來設置顯示時間

Android中通過反射來設置顯示時間

編輯:關於Android編程

這個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:






    



看到了這個布局是如此的簡單,裡面顯示的內容就是使用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
        }
    }

這個方法很簡單的,首先獲取一個服務,然後將我們需要顯示的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文件,我們打開看一下:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+CjwvcD4KPHByZSBjbGFzcz0="brush:java;">/* //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,我猜原因很簡單,因為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;
               }
            }
        });
        
    }
}

通過一個按鈕可以控制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對象,裡面包含一些不允許發送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());
系統會將,該包下的 ,所有進程,服務,全部殺掉,就可以殺干淨了,要注意加上

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 seconds
private static final int SHORT_DELAY = 2000; // 2 seconds

只有2s和3.5s這兩個值,所以我們在之前說過我們設置toast的顯示時間是沒有任何效果的。


總結一下,我們是從源代碼的角度來解決的問題的,而且這裡還用到了反射的相關技術(其實這個技術在後面說到靜態安裝的時候也會用到),所以說反射真是什麼都可以,在這我們上面總是說到源碼目錄中搜索,這個源碼下載地址很多的,我用的是:http://blog.csdn.net/jiangwei0910410003/article/details/19980459這個方法。下載下來是個一個base目錄,核心代碼都在core文件夾中。以後遇到問題還是先看源代碼,雖然代碼看起來很蛋疼,但是這也是沒辦法的!!



  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved