本文共 16346 字,大约阅读时间需要 54 分钟。
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方法
/*** schedule handleShow into the right thread*/@Overridepublic void show(IBinder windowToken) {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.obtainMessage(0, windowToken).sendToTarget();}
/**
/** @hide */oneway interface ITransientNotification { void show(); void hide();}
通过AIDL(Binder)通信拿到NotificationManagerService的服务访问接口,然后把TN对象和一些参数传递到远程NotificationManagerService中去
当 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方法呢?
回调了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());}
具体可以参考我的弹窗封装库:
//判断是否有权限NotificationManagerCompat.from(context).areNotificationsEnabled()
//如果没有通知权限,则直接跳转设置中心设置
为了避免静态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();
/*** 检查上下文不能为空,必须先进性初始化操作*/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();
转载于:https://blog.51cto.com/11359966/2311832