1.最簡單的建立方法php
2.源碼分析android
3.經典總結git
4.Toast封裝庫介紹程序員
5.Toast遇到的問題github
05.PopupWindow源碼分析segmentfault
06.Snackbar源碼分析markdown
07.彈窗常見問題app
Toast是沒有焦點,並且Toast顯示的時間有限,過必定的時間就會自動消失。
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); }
一行代碼調用,十分方便,可是這樣存在一種弊端。
Toast.makeText(this,"吐司",Toast.LENGTH_SHORT).show();
爲了解決1.2中的重複建立問題,則能夠這樣解決
/** * 吐司工具類 避免點擊屢次致使吐司屢次,最後致使Toast就長時間關閉不掉了 * 注意:這裏若是傳入context會報內存泄漏;傳遞activity..getApplicationContext() * @param content 吐司內容 */ private static Toast toast; @SuppressLint("ShowToast") public static void showToast(String content) { checkContext(); if (toast == null) { toast = Toast.makeText(mApp, content, Toast.LENGTH_SHORT); } else { toast.setText(content); } toast.show(); }
這樣用的原理
在構造方法中,建立了NT對象,那麼有人便會問,NT是什麼東西呢?因而帶着好奇心便去看看NT的源碼,能夠發現NT實現了ITransientNotification.Stub,提到這個感受是否是很熟悉,沒錯,在aidl中就會用到這個。
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類中,能夠看到,實現了AIDL的show與hide方法
/** * schedule handleShow into the right thread */ @Override public void show(IBinder windowToken) { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.obtainMessage(0, windowToken).sendToTarget(); } /** * schedule handleHide into the right thread */ @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); }
接着看下這個ITransientNotification.aidl文件
/** @hide */ oneway interface ITransientNotification { void show(); void hide(); }
經過AIDL(Binder)通訊拿到NotificationManagerService的服務訪問接口,而後把TN對象和一些參數傳遞到遠程NotificationManagerService中去
public void show() { if (mNextView == null) { throw new RuntimeException("setView must have been called"); } //經過AIDL(Binder)通訊拿到NotificationManagerService的服務訪問接口,當前Toast類至關於上面例子的客戶端!!!至關重要!!! INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; try { //把TN對象和一些參數傳遞到遠程NotificationManagerService中去 service.enqueueToast(pkg, tn, mDuration); } catch (RemoteException e) { // Empty } }
接着看看getService方法
//遠程NotificationManagerService的服務訪問接口 private static INotificationManager sService; static private INotificationManager getService() { //單例模式 if (sService != null) { return sService; } //經過AIDL(Binder)通訊拿到NotificationManagerService的服務訪問接口 sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification")); return sService; }
接下來看看service.enqueueToast(pkg, tn, mDuration)這段代碼,相信有的小夥伴會質疑,這段代碼報紅色,如何查看呢?
synchronized (mToastQueue) { int callingPid = Binder.getCallingPid(); long callingId = Binder.clearCallingIdentity(); try { ToastRecord record; int index; //判斷是不是系統級別的吐司 if (!isSystemToast) { index = indexOfToastPackageLocked(pkg); } else { index = indexOfToastLocked(pkg, callback); } if (index >= 0) { record = mToastQueue.get(index); record.update(duration); record.update(callback); } else { //建立一個Binder類型的token對象 Binder token = new Binder(); //生成一個Toast窗口,而且傳遞token等參數 mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY); record = new ToastRecord(callingPid, pkg, callback, duration, token); //添加到吐司隊列之中 mToastQueue.add(record); //對當前索引從新進行賦值 index = mToastQueue.size() - 1; } //將當前Toast所在的進程設置爲前臺進程 keepProcessAliveIfNeededLocked(callingPid); if (index == 0) { //若是index爲0,說明當前入隊的Toast在隊頭,須要調用showNextToastLocked方法直接顯示 showNextToastLocked(); } } finally { Binder.restoreCallingIdentity(callingId); } }
接下來看一下showNextToastLocked()方法中的源代碼,看看這個方法中作了什麼……
若是你仔細一點,你能夠看到在handleShow(IBinder windowToken)這個方法中,將windowToken賦值給mParams.token,那麼就會思考這個token是幹什麼用的呢?它是哪裏傳遞過來的呢?
接下來再來看看scheduleTimeoutLocked(record)這部分代碼,這個主要是超時監聽消息邏輯
既然發送了消息,那確定有地方接收消息而且處理消息呀。接着看下面代碼,重點看cancelToastLocked源碼!
cancelToastLocked源碼邏輯主要是
當建立TN對象的時候,就建立了handler和runnable對象。
同時,當toast執行show以後,過了一下子會自動銷燬,那麼這又是爲啥呢?那麼是哪裏調用了hide方法呢?
public void handleHide() { if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); if (mView != null) { // note: checking parent() just to make sure the view has // been added... i have seen cases where we get here when // the view isn't yet added, so let's try not to crash. if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeViewImmediate(mView); } mView = null; } }
如何判斷是不是系統吐司呢?若是當前Toast所屬的進程的包名爲「android」,則爲系統Toast,或者調用isCallerSystem()方法
final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
接着看看isCallerSystem()方法源碼,isCallerSystem的源碼也比較簡單,就是判斷當前Toast所屬進程的uid是否爲SYSTEM_UID、0、PHONE_UID中的一個,若是是,則爲系統Toast;若是不是,則不爲系統Toast。
private static boolean isUidSystem(int uid) { final int appid = UserHandle.getAppId(uid); return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); } private static boolean isCallerSystem() { return isUidSystem(Binder.getCallingUid()); }
爲何要這樣判斷是不是系統吐司呢?從源碼可知:首先系統Toast必定能夠進入到系統Toast隊列中,不會被黑名單阻止。而後系統Toast在系統Toast隊列中沒有數量限制,而普通pkg所發送的Toast在系統Toast隊列中有數量限制。
記得之前昊哥問我,爲什麼toast在activity銷燬後仍然會彈出呢,我絕不思索地說,由於toast是系統級別的呀。那麼是如何實現的呢,我就無言以對呢……今天終於能夠回答呢!
具體能夠參考個人彈窗封裝庫:https://github.com/yangchong2...
//判斷是否有權限 NotificationManagerCompat.from(context).areNotificationsEnabled() //若是沒有通知權限,則直接跳轉設置中心設置 @SuppressLint("ObsoleteSdkInt") private static void toSetting(Context context) { Intent localIntent = new Intent(); localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (Build.VERSION.SDK_INT >= 9) { localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); localIntent.setData(Uri.fromParts("package", context.getPackageName(), null)); } else if (Build.VERSION.SDK_INT <= 8) { localIntent.setAction(Intent.ACTION_VIEW); localIntent.setClassName("com.android.settings", "com.android.setting.InstalledAppDetails"); localIntent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName()); } context.startActivity(localIntent); }
爲了不靜態toast對象內存泄漏,固可使用應用級別的上下文context。因此這裏我就直接採用了應用級別Application上下文,須要在application進行初始化一下。便可調用……
//初始化 ToastUtils.init(this); //能夠自由設置吐司的背景顏色,默認是純黑色 ToastUtils.setToastBackColor(this.getResources().getColor(R.color.color_7f000000)); //直接設置最簡單吐司,只有吐司內容 ToastUtils.showRoundRectToast("自定義吐司"); //設置吐司標題和內容 ToastUtils.showRoundRectToast("吐司一下","他發的撒經濟法的解放軍"); //第三種直接設置自定義佈局的吐司 ToastUtils.showRoundRectToast(R.layout.view_layout_toast_delete); //或者直接採用bulider模式建立 ToastUtils.Builder builder = new ToastUtils.Builder(this.getApplication()); builder .setDuration(Toast.LENGTH_SHORT) .setFill(false) .setGravity(Gravity.CENTER) .setOffset(0) .setDesc("內容內容") .setTitle("標題") .setTextColor(Color.WHITE) .setBackgroundColor(this.getResources().getColor(R.color.blackText)) .build() .show();
由於看到網上有許多toast的封裝,須要傳遞上下文,後來感受是否是不須要傳遞這個參數,直接統一初始化一下就好呢。因此纔有了這個toast的改良版。
/** * 檢查上下文不能爲空,必須先進性初始化操做 */ private static void checkContext(){ if(mApp==null){ throw new NullPointerException("ToastUtils context is not null,please first init"); } }
報錯日誌,是否是有點眼熟呀?更多能夠看個人開源項目:https://github.com/yangchong211
android.view.WindowManager$BadTokenException Unable to add window -- token android.os.BinderProxy@7f652b2 is not valid; is your activity running?
查詢報錯日誌是從哪裏來的
發生該異常的緣由
Toast.makeText(this,"瀟湘劍雨-yc",Toast.LENGTH_SHORT).show(); try { Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); }
解決辦法,目前見過好幾種,思考一下那種比較好……
第二種,拋出異常增長try-catch,代碼以下所示,最後仍然沒法解決問題
哪些狀況會發生該問題?
先來看看問題代碼,會出現什麼問題呢?
new Thread(new Runnable() { @Override public void run() { ToastUtils.showRoundRectToast("瀟湘劍雨-楊充"); } }).start();
而後找找報錯日誌從哪裏來的
子線程中吐司的正確作法,代碼以下所示
new Thread(new Runnable() { @Override public void run() { Looper.prepare(); ToastUtils.showRoundRectToast("瀟湘劍雨-楊充"); Looper.loop(); } }).start();
得出的結論
須要注意:WindowManager檢查當前窗口的token是否有效,若是有效,則添加窗口展現Toast;若是無效,則拋出異常,會發生5.1這種類型的異常。