源码查看网址,这个是当前我所用的源码地址,自备梯子
android使用注解替代枚举
android 进程间通信使用aidl和Messenger类
涉及源码有些长,下面只截取了部分分析,github toast相关源码
① 从Toast.java中的变量定义开始
我们都知道在实际使用中,Toast显示的时间只有两种情况。我们先从源码看看是怎么回事。
static final String TAG = "Toast";
static final boolean localLOGV = false;
/** @hide */
@IntDef({LENGTH_SHORT, LENGTH_LONG})
@Retention(RetentionPolicy.SOURCE)
public @interface Duration {}
/**
* Show the view or text notification for a short period of time. This time
* could be user-definable. This is the default.
* @see #setDuration
*/
public static final int LENGTH_SHORT = 0;
/**
* Show the view or text notification for a long period of time. This time
* could be user-definable.
* @see #setDuration
*/
public static final int LENGTH_LONG = 1;
final Context mContext;
final TN mTN;
int mDuration;
View mNextView;
这里使用注解的方式规定了Duration的取值,只有两个值,LENGTH_SHORT和LENGTH_LONG。在看一下源码中对Duration的设置
/**
* Set how long to show the view for.
* @see #LENGTH_SHORT
* @see #LENGTH_LONG
*/
public void setDuration(@Duration int duration) {
mDuration = duration;
mTN.mDuration = duration;
}
/**
* Return the duration.
* @see #setDuration
*/
@Duration
public int getDuration() {
return mDuration;
}
明了了吧,这里使用@Duration对传入的参数进行了规定,如果想传入其他的int值,在代码中是会有标记
② 看看Toast.java的构造函数
/**
* Construct an empty Toast object. You must call {@link #setView} before you
* can call {@link #show}.
*
* @param context The context to use. Usually your {@link android.app.Application}
* or {@link android.app.Activity} object.
*/
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);
}
由界面传入一个上下文实例context,后面的代码貌似是创建一个TN对象,我们现在来看看TN是什么东西。
③ 查看构造函数中创建的TN具体定义,这是一个内部类
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
@Override
public void run() {
handleShow();
}
};
final Runnable mHide = new Runnable() {
@Override
public void run() {
handleHide();
// Don't do this in handleHide() because it is also invoked by handleShow()
mNextView = null;
}
};
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
final Handler mHandler = new Handler();
int mGravity;
int mX, mY;
float mHorizontalMargin;
float mVerticalMargin;
View mView;
View mNextView;
int mDuration;
WindowManager mWM;
static final long SHORT_DURATION_TIMEOUT = 5000;
static final long LONG_DURATION_TIMEOUT = 1000;
TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
}
/**
* schedule handleShow into the right thread
*/
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
/**
* schedule handleHide into the right thread
*/
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
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();
String packageName = mView.getContext().getOpPackageName();
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;
mParams.packageName = packageName;
mParams.removeTimeoutMilliseconds = mDuration ==
Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
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();
}
}
private void trySendAccessibilityEvent() {
AccessibilityManager accessibilityManager =
AccessibilityManager.getInstance(mView.getContext());
if (!accessibilityManager.isEnabled()) {
return;
}
// treat toasts as notifications since they are used to
// announce a transient piece of information to the user
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
event.setClassName(getClass().getName());
event.setPackageName(mView.getContext().getPackageName());
mView.dispatchPopulateAccessibilityEvent(event);
accessibilityManager.sendAccessibilityEvent(event);
}
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.removeView(mView);
}
mView = null;
}
}
}
这个类继承ITransientNotification.stub,那么这个ITransientNotification是一个aidl文件
ITransientNotification.aidl(@hind)源码
20/** @hide */
21 oneway interface ITransientNotification {
22 void show();
23 void hide();
24}
上面的TN继承这个类并提供了具体的方法show和hind,在具体的实现中使用Handler来post两个runnable(TN类开头定义的两个Runnable),两个Runnable中分别执行了handleShow()和handleHind()方法,推测是控制Toast显示和隐藏的具体方法。看具体的实现:
在handleShow()方法中,通过获取WindowManager将创建的View add到界面上,在handleHind()方法中获取WindowManager remove掉这个View.
④ 根据Toast.java的构造函数和上面对TN的分析 小结一下。
使用过程中,创建一个toast对象,会实例化一个内部类TN,这个TN类能够控制Toast显示和隐藏(内部由WindowManager add 和remove view来实现显示和隐藏)。
⑤ 构造toast view 之后,toast.show();
/**
* Show the view for the specified duration.
*/
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
private static INotificationManager sService;
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
又出现了几个不认识的东西,看看具体是什么。
- ServiceManager.java(@hind)
26 /** @hide */
27public final class ServiceManager {
28 private static final String TAG = "ServiceManager";
29
30 private static IServiceManager sServiceManager;
31 private static HashMap sCache = new HashMap();
32
33 private static IServiceManager getIServiceManager() {
34 if (sServiceManager != null) {
35 return sServiceManager;
36 }
37
38 // Find the service manager
39 sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
40 return sServiceManager;
41 }
42
43 /**
44 * Returns a reference to a service with the given name.
45 *
46 * @param name the name of the service to get
47 * @return a reference to the service, or null
if the service doesn't exist
48 */
49 public static IBinder getService(String name) {
50 try {
51 IBinder service = sCache.get(name);
52 if (service != null) {
53 return service;
54 } else {
55 return getIServiceManager().getService(name);
56 }
57 } catch (RemoteException e) {
58 Log.e(TAG, "error in getService", e);
59 }
60 return null;
61 }
62
63 /**
64 * Place a new @a service called @a name into the service
65 * manager.
66 *
67 * @param name the name of the new service
68 * @param service the service object
69 */
70 public static void addService(String name, IBinder service) {
71 try {
72 getIServiceManager().addService(name, service, false);
73 } catch (RemoteException e) {
74 Log.e(TAG, "error in addService", e);
75 }
76 }
...
139 }
ServiceManager详解
文章重点:ServiceManager管理整个系统的Service,实际功能发面包括:
- 提供IBinder对象(在每个进程中,该IBinder对象是唯一的)就好比我获取一个闹钟的service,那在当前这个app进程中这个service就是唯一的。
- 让各个service注册到ServiceManager中。
简单来说ServiceManager提供getIBinder的方法和每个service都会注册到它。
INotificationManager.aidl(@hind)
/** {@hide} */
36interface INotificationManager
37 {
38 void cancelAllNotifications(String pkg, int userId);
39
40 void enqueueToast(String pkg, ITransientNotification callback, int duration);
41 void cancelToast(String pkg, ITransientNotification callback);
...
100 }
ITransientNotification.aidl(@hind)
20/** @hide */
21 oneway interface ITransientNotification {
22 void show();
23 void hide();
24}
回到Toast.java 源码
我们操作中调用的Toast show方法在源码中的操作其实是获取这个INotificationManager的实例,然后enqueueToast和cancelToast.