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();
/** * 吐司工具類 避免點擊屢次致使吐司屢次,最後致使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(); }
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方法php
/** * schedule handleShow into the right thread */ @Override public void show(IBinder windowToken) { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.obtainMessage(0, windowToken).sendToTarget(); }
/**android
/** @hide */ oneway interface ITransientNotification { void show(); void hide(); }
經過AIDL(Binder)通訊拿到NotificationManagerService的服務訪問接口,而後把TN對象和一些參數傳遞到遠程NotificationManagerService中去git
當 Toast在show的時候,而後把這個請求放在 NotificationManager 所管理的隊列中,而且爲了保證 NotificationManager 能跟進程交互,會傳遞一個TN類型的 Binder對象給NotificationManager系統服務,接着看下面getService方法作了什麼?程序員
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 } }
//遠程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; }
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); } }
同時,當toast執行show以後,過了一下子會自動銷燬,那麼這又是爲啥呢?那麼是哪裏調用了hide方法呢?github
回調了Toast的TN的show,當timeout可能就是hide呢。從上面我分析NotificationManagerService源碼中的showNextToastLocked()的scheduleTimeoutLocked(record)源碼,能夠知道在NotificationManagerService經過handler延遲delay時間發送消息,而後經過callback調用hide,因爲callback是TN中Binder的代理對象, 因此即可以調用到TN中的hide方法達到銷燬吐司的目的。handleHide()源碼以下所示面試
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; } }
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()); }
具體能夠參考個人彈窗封裝庫:https://github.com/yangchong211/YCDialogsegmentfault
//判斷是否有權限 NotificationManagerCompat.from(context).areNotificationsEnabled()
//若是沒有通知權限,則直接跳轉設置中心設置br/>@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);
}markdown
爲了不靜態toast對象內存泄漏,固可使用應用級別的上下文context。因此這裏我就直接採用了應用級別Application上下文,須要在application進行初始化一下。便可調用……app
//初始化 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();
/** * 檢查上下文不能爲空,必須先進性初始化操做 */ private static void checkContext(){ if(mApp==null){ throw new NullPointerException("ToastUtils context is not null,please first init"); } }
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(); }
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();