編輯:關於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 }
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
接著得到調用者的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(); ArrayListlist = 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 ArrayListlist = 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文件夾中。以後遇到問題還是先看源代碼,雖然代碼看起來很蛋疼,但是這也是沒辦法的!!
TeamViewer 是一個在任何防火牆和NAT代理的後台用於遠程控制 ,桌面共享和文件傳輸的簡單且快速的解決方案。為了連接到另一台計算機,只需要在兩台計算
基本信息Simple2Develop 是一款基於Android平台的跨線程通信框架,可以讓你以一種簡單的方式進行復雜的通信,支持同進程中多Activity之間即時交互,子
這一個知識點主要以理解為主:一、任務棧(task stack)1、作用:就是用來管理activity的進入,退出。記錄了用戶的行為。2、舉例:假如要進行一下操作:這時候,
先看一下standard啟動模式的說明:只有一個實例,在同一個應用程序中啟動他的時候,若不存在此Activity實例,則會在當前棧頂創建一個新的實例,若存在,則會把棧中在