说明

本篇文章用于介绍Android中Toast的实现原理。和简单实现一个自定义的Toast.

Toast实现

一般常用Toast格式为:

Toast.makeText(context,"text.",Toast.LENGTH_LONG).show();

就此,对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的三个属性:mNextView、mDuration.mNextView为显示的Toast view,Toast中使用的是系统的一套资源layout,我们其实可以据此替换绘制的View.再看,这里的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);
}

发现生成一个TN对象,发现其是处理Toast显示的一个服务处理类。再看Toast.show()方法,代码如下:

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
}
}

通过代码发现,这里通过Binder方式将处理发送给了INotificationManager来处理,INotificationManager.aidl的实现类为com.android.server.NotificationManagerService,在源码中找到enqueueToast(…)实现如下:

@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
...........
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, callback);
// If it's already in the queue, we update it in place, we don't
// move it to the end of the queue.
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else {
// Limit the number of toasts that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemToast) {
int count = 0;
final int N = mToastQueue.size();
for (int i=0; i<N; i++) {
final ToastRecord r = mToastQueue.get(i);
if (r.pkg.equals(pkg)) {
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " toasts. Not showing more. Package=" + pkg);
return;
}
}
}
} record = new ToastRecord(callingPid, pkg, callback, duration);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
keepProcessAliveLocked(callingPid);
}
// If it's at index 0, it's the current toast. It doesn't matter if it's
// new or just been updated. Call back and tell it to show itself.
// If the callback fails, this will remove it from the list, so don't
// assume that it's valid after this.
if (index == 0) {
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}

如上代码,发现Toast发送的enqueue会被保存在一个List列表中,最后显示操作如下:

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;
}
}
}
}

其中,scheduleTimeoutLocked(record);控制显示的时间,Toast提供的显示时间有俩个。通过设置Toast的duration类型控制,如下:

static final int LONG_DELAY = 3500; // 3.5 seconds
static final int SHORT_DELAY = 2000; // 2 seconds

处理回调交由Toast中的ITransientNotification.Stub 处理,即TN对象,show方法启动mShow线程,线程执行代码如下:

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();
}
}

最后还是通过WindowManager来加载Toast界面。

自定义Toast

自定义Toast就简单了,两种思路:一个是通过自定义toast的view;一个是通过WindowManager来控制加载view实现一个Toast,这个是最终的解决办法.如下,为简单的替换一个view,代码如下:

private void showToast(Context context){
Toast toast=new Toast(context.getApplicationContext());
LayoutInflater inflate = (LayoutInflater)
this.getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(R.layout.test, null);
toast.setView(v);
toast.setDuration(Toast.LENGTH_LONG);
toast.show();
}

布局文件代码为:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="5dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"> <Button
android:text="cancel"
android:background="@drawable/aa"
android:textColor="@android:color/holo_blue_bright"
android:layout_width="150dip"
android:layout_height="50dip" /> </LinearLayout>

背景控制xml为:


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke android:width="1dp" android:color="#63a219"></stroke>
<corners android:radius="5dp" />
</shape>

如上,为我对Toast实现的浅显认识,表做记录。

Enjoytoday,EnjoyCoding

Toast实现源码解析的更多相关文章

  1. Android源码解析——Toast

    简介 Toast是一种向用户快速展示少量信息的视图.当它显示时,它会浮在整个应用层的上面,并且不会获取到焦点.它的设计思想是能够向用户展示些信息,但又能尽量不显得唐突.本篇我们来研读一下Toast的源 ...

  2. Android AsyncTask 源码解析

    1. 官方介绍 public abstract class AsyncTask extends Object  java.lang.Object    ↳ android.os.AsyncTask&l ...

  3. 【Android应用开发】EasyDialog 源码解析

    示例源码下载 : http://download.csdn.net/detail/han1202012/9115227 EasyDialog 简介 : -- 作用 : 用于在界面进行一些介绍, 说明; ...

  4. 阿里ARouter使用及源码解析(一)

    在app的开发中,页面之间的相互跳转是最基本常用的功能.在Android中的跳转一般通过显式intent和隐式intent两种方式实现的,而Android的原生跳转方式会存在一些缺点: 显式inten ...

  5. 源码解析-EventBus

    示例使用 时序图 源码解读 EventBus 使用 官网定义:EventBus 是一个使用 Java 写的观察者模式,解耦的 Android 开源库.EventBus 只需要几行代码即可解耦简化代码, ...

  6. Android源码解析系列

    转载请标明出处:一片枫叶的专栏 知乎上看了一篇非常不错的博文:有没有必要阅读Android源码 看完之后痛定思过,平时所学往往是知其然然不知其所以然,所以为了更好的深入Android体系,决定学习an ...

  7. andorid jar/库源码解析之Bolts

    目录:andorid jar/库源码解析 Bolts: 作用: 用于链式执行跨线程代码,且传递数据 栗子: Task.call(new Callable<Boolean>() { @Ove ...

  8. andorid jar/库源码解析之Butterknife

    目录:andorid jar/库源码解析 Butterknife: 作用: 用于初始化界面控件,控件方法,通过注释进行绑定控件和控件方法 栗子: public class MainActivity e ...

  9. Redux异步解决方案之Redux-Thunk原理及源码解析

    前段时间,我们写了一篇Redux源码分析的文章,也分析了跟React连接的库React-Redux的源码实现.但是在Redux的生态中还有一个很重要的部分没有涉及到,那就是Redux的异步解决方案.本 ...

随机推荐

  1. (转)vscode实现markdown流程图

    原文:https://blog.csdn.net/LaySwift/article/details/79458947 1,vscode原生支持markdown,导出需要插件,基于node.js,需要n ...

  2. Python中的常见特殊方法—— repr方法

    在Python中有些方法名.属性名的前后都添加了双下划线,这种方法.属性通常都属于Python的特殊方法和特殊属性,开发者可以通过重写这些方法或者直接调用这些方法来实现特殊的功能.其实前面见过的构造方 ...

  3. jmap 导出 tomcat 内存快照分析

    登录系统(注意这里启动 tomcat 的用户) # 获取 tomcat 的 pid 号 ps -ef|grep tomcat # 例如这里 pid 号为 13133 jmap -dump:live,f ...

  4. MySql Navicat可视化工具

    下载链接 链接:https://pan.baidu.com/s/1ca5KbpCFc4UbcYkXZDu6aA 提取码:8nku 安装比较简单,选完安装路径,下一步即可 Navicat for MyS ...

  5. ant 打包脚本

    现在很多人都用ant脚本来进行打包,下面就介绍一下这个打包工具常见的用法.以及脚本如何编写 <!-- 定义任务,清空任务:清空原有的classes目录,重新创建 --> <targe ...

  6. canvas绘制星星和月亮

    用变量的方式绘制一个五角星,首先求五角星十个顶点的坐标. 可以把每个五角星看成外顶点用一个大圆绘制,内顶点用小圆绘制.在坐标系(0deg)下,根据每个顶点的角度和圆的半径求得x,y. 而每个大顶点相差 ...

  7. ArcGIS Server10.4 service发布步骤

    准备内容 安装环境:win10*64位专业版 安装文件:ArcGIS_Server_Ent_Windows_1041_150998.iso 破解文件:Lic10.4.1.ecp #安装Server前, ...

  8. iOS---------查看界面的流畅度

    iOS查看屏幕帧数工具--YYFPSLabel iOS 保持界面流畅的技巧 iOS 优化界面流畅度的探讨 先研究一下     改天自己出一篇文章

  9. Linux ssh突然连接不了的案例浅析

    公司的Linux服务器都是通过一台JumpServer跳转的.个人使用Jumpserver(开源跳板机系统)时,有时候由于需要上传.下载文件很不方便.而由于配置关系,一般情况无法使用SecureCRT ...

  10. Go module的两个代理源和设置方法

    Go module的两个代理源 阿里云 https://mirrors.aliyun.com/goproxy/ 使用帮助 1.使用go1.11以上版本并开启go module机制 2.导出GOPROX ...