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
*/
@Override
public 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());
}
具体可以参考我的弹窗封装库:https://github.com/yangchong211/YCDialog
//判断是否有权限
NotificationManagerCompat.from(context).areNotificationsEnabled()
//如果没有通知权限,则直接跳转设置中心设置@SuppressLint("ObsoleteSdkInt")
br/>@SuppressLint("ObsoleteSdkInt")
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();
/**
* 检查上下文不能为空,必须先进性初始化操作
*/
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();
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。