android 开发零起步学习笔记(二十二):ANDROID应用ACTIVITY、DIALOG、POPWINDOW、TOAST窗口添加机制及源码分析(一)

第二部分:

4 Android应用PopWindow窗口添加显示机制源码

PopWindow实质就是弹出式菜单,它与Dialag不同的地方是不会使依赖的Activity组件失去焦点(PopupWindow弹出后可 以继续与依赖的Activity进行交互),Dialog却不能这样。同时PopupWindow与Dialog另一个不同点是PopupWindow是 一个阻塞的对话框,如果你直接在Activity的onCreate等方法中显示它则会报错,所以PopupWindow必须在某个事件中显示地或者是开 启一个新线程去调用。

说这么多还是直接看代码吧。

4-1 PopWindow窗口源码分析

依据PopWindow的使用,我们选择最常用的方式来分析,如下先看其中常用的一种构造函数:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public class PopupWindow {

......

//我们只分析最常用的一种构造函数

public PopupWindow(View contentView, int width, int height, boolean focusable) {

if(contentView !=null) {

//获取mContext,contentView实质是View,View的mContext都是构造函数传入的,View又层级传递,所以最终这个mContext实质是Activity!!!很重要

mContext = contentView.getContext();

//获取Activity的getSystemService的WindowManager

mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);

}

//进行一些Window类的成员变量初始化赋值操作

setContentView(contentView);

setWidth(width);

setHeight(height);

setFocusable(focusable);

}

......

}

可以看见,构造函数只是初始化了一些变量,看完构造函数继续看下PopWindow的展示函数,如下:

1

2

3

4

5

6

7

8

9

10

11

public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {

......

//anchor是Activity中PopWindow准备依附的View,这个View的token实质也是Activity的Window中的token,也即Activity的token

//第一步 初始化WindowManager.LayoutParams

WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());

//第二步

preparePopup(p);

......

//第三步

invokePopup(p);

}

可以看见,当我们想将PopWindow展示在anchor的下方向(Z轴是在anchor的上面)旁边时经理了上面三步,我们一步一步来分析,先看第一步,源码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

private WindowManager.LayoutParams createPopupLayout(IBinder token) {

//实例化一个默认的WindowManager.LayoutParams,其中type=TYPE_APPLICATION

WindowManager.LayoutParams p =newWindowManager.LayoutParams();

//设置Gravity

p.gravity = Gravity.START | Gravity.TOP;

//设置宽高

p.width = mLastWidth = mWidth;

p.height = mLastHeight = mHeight;

//依据背景设置format

if(mBackground !=null) {

p.format = mBackground.getOpacity();

}else{

p.format = PixelFormat.TRANSLUCENT;

}

//设置flags

p.flags = computeFlags(p.flags);

//修改type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,mWindowLayoutType有初始值,type类型为子窗口

p.type = mWindowLayoutType;

//设置token为Activity的token

p.token = token;

......

return

}

接着回到showAsDropDown方法看看第二步,如下源码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

private void preparePopup(WindowManager.LayoutParams p) {

......

//有无设置PopWindow的background区别

if(mBackground !=null) {

......

//如果有背景则创建一个PopupViewContainer对象的ViewGroup

PopupViewContainer popupViewContainer =newPopupViewContainer(mContext);

PopupViewContainer.LayoutParams listParams =newPopupViewContainer.LayoutParams(

ViewGroup.LayoutParams.MATCH_PARENT, height

);

//把背景设置给PopupViewContainer的ViewGroup

popupViewContainer.setBackground(mBackground);

//把我们构造函数传入的View添加到这个ViewGroup

popupViewContainer.addView(mContentView, listParams);

//返回这个ViewGroup

mPopupView = popupViewContainer;

}else{

//如果没有通过PopWindow的setBackgroundDrawable设置背景则直接赋值当前传入的View为PopWindow的View

mPopupView = mContentView;

}

......

}

可以看见preparePopup方法的作用就是判断设置View,如果有背景则会在传入的contentView外面包一层 PopupViewContainer(实质是一个重写了事件处理的FrameLayout)之后作为mPopupView,如果没有背景则直接用 contentView作为mPopupView。我们再来看下这里的PopupViewContainer类,如下源码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

private class PopupViewContainer extends FrameLayout {

......

@Override

protected int[] onCreateDrawableState(int extraSpace) {

......

}

@Override

public boolean dispatchKeyEvent(KeyEvent event) {

......

}

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

if(mTouchInterceptor !=null&& mTouchInterceptor.onTouch(this, ev)) {

returntrue

}

returnsuper.dispatchTouchEvent(ev);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

......

if(xxx) {

dismiss();

}

......

}

@Override

public void sendAccessibilityEvent(int eventType) {

......

}

}

可以看见,这个PopupViewContainer是一个PopWindow的内部私有类,它继承了FrameLayout,在其中重写了Key 和Touch事件的分发处理逻辑。同时查阅PopupView可以发现,PopupView类自身没有重写Key和Touch事件的处理,所以如果没有将 传入的View对象放入封装的ViewGroup中,则点击Back键或者PopWindow以外的区域PopWindow是不会消失的(其实PopWindow中没有向Activity及Dialog一样new新的Window,所以不会有新的callback设置,也就没法处理事件消费了)。

接着继续回到showAsDropDown方法看看第三步,如下源码:

1

2

3

4

5

6

7

8

private void invokePopup(WindowManager.LayoutParams p) {

if(mContext !=null) {

p.packageName = mContext.getPackageName();

}

mPopupView.setFitsSystemWindows(mLayoutInsetDecor);

setLayoutDirectionFromAnchor();

mWindowManager.addView(mPopupView, p);

}

可以看见,这里使用了Activity的WindowManager将我们的PopWindow进行了显示。

到此可以发现,PopWindow的实质无非也是使用WindowManager的addView、updateViewLayout、 removeView进行一些操作展示。与Dialog不同的地方是没有新new Window而已(也就没法设置callback,无法消费事件,也就是前面说的PopupWindow弹出后可以继续与依赖的Activity进行交互 的原因)。

到此PopWindw的窗口加载显示机制就分析完毕了,接下来进行总结与应用开发技巧提示。

4-2 PopWindow窗口源码分析总结及应用开发技巧提示

通过上面分析可以发现总结如下图:

可以看见,PopWindow完全使用了Activity的Window与WindowManager,相对来说比较简单容易记理解。

再来看一个开发技巧:

如果设置了PopupWindow的background,则点击Back键或者点击PopupWindow以外的区域时PopupWindow就 会dismiss;如果不设置PopupWindow的background,则点击Back键或者点击PopupWindow以外的区域 PopupWindow不会消失。

5 Android应用Toast窗口添加显示机制源码

5-1 基础知识准备

在开始分析这几个窗口之前需要脑补一点东东,我们从应用层开发来直观脑补,这样下面分析源码时就不蛋疼了。如下是一个我们写的两个应用实现 Service跨进程调用服务ADIL的例子,客户端调运远程Service的start与stop方法控制远程Service的操作。

Android系统中的应用程序都运行在各自的进程中,进程之间是无法直接交换数据的,但是Android为开发者提供了AIDL跨进程调用Service的功能。其实AIDL就相当于双方约定的一个规则而已。

先看下在Android Studio中AIDL开发的工程目录结构,如下:

由于AIDL文件中不能出现访问修饰符(如public),同时AIDL文件在两个项目中要完全一致而且只支持基本类型,所以我们定义的AIDL文件如下:

ITestService.aidl

1

2

3

4

5

6

package io.github.yanbober.myapplication;

interface ITestService {

void start(int id);

void stop(int id);

}

再来看下依据aidl文件自动生成的ITestService.java文件吧,如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

/*

* This file is auto-generated. DO NOT MODIFY.

*/

package io.github.yanbober.myapplication;

public interface ITestService extends android.os.IInterface

{

//Stub类是ITestService接口的内部静态抽象类,该类继承了Binder类

public static abstract class Stub extends android.os.Binder implements io.github.yanbober.myapplication.ITestService

{

......

//这是抽象静态Stub类中的asInterface方法,该方法负责将service返回至client的对象转换为ITestService.Stub

//把远程Service的Binder对象传递进去,得到的是远程服务的本地代理

public static io.github.yanbober.myapplication.ITestService asInterface(android.os.IBinder obj)

{

......

}

......

//远程服务的本地代理,也会继承自ITestService

private static class Proxy implements io.github.yanbober.myapplication.ITestService

{

......

@Override

public void start(int id) throws android.os.RemoteException

{

......

}

@Override

public void stop(int id) throws android.os.RemoteException

{

......

}

}

......

}

//两个方法是aidl文件中定义的方法

public void start(int id) throws android.os.RemoteException;

public void stop(int id) throws android.os.RemoteException;

}

这就是自动生成的java文件,接下来我们看看服务端的Service源码,如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

//记得在AndroidManifet.xml中注册Service的<action android:name="io.github.yanbober.myapplication.aidl" />

public class TestService extends Service {

private TestBinder mTestBinder;

//该类继承ITestService.Stub类而不是Binder类,因为ITestService.Stub是Binder的子类

//进程内的Service定义TestBinder内部类是继承Binder类

public class TestBinder extends ITestService.Stub {

@Override

public void start(int id) throws RemoteException {

Log.i(null,"Server Service is start!");

}

@Override

public void stop(int id) throws RemoteException {

Log.i(null,"Server Service is stop!");

}

}

@Override

public IBinder onBind(Intent intent) {

//返回Binder

returnmTestBinder;

}

@Override

public void onCreate() {

super.onCreate();

//实例化Binder

mTestBinder =newTestBinder();

}

}

现在服务端App的代码已经OK,我们来看下客户端的代码。客户端首先也要像上面的工程结构一样,把AIDL文件放好,接着在客户端使用远程服务端的Service代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

public class MainActivity extends Activity {

private static final String REMOT_SERVICE_ACTION ="io.github.yanbober.myapplication.aidl"

private Button mStart, mStop;

private ITestService mBinder;

private ServiceConnection connection =newServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

//获得另一个进程中的Service传递过来的IBinder对象

//用IMyService.Stub.asInterface方法转换该对象

mBinder = ITestService.Stub.asInterface(service);

}

@Override

public void onServiceDisconnected(ComponentName name) {

}

};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mStart = (Button)this.findViewById(R.id.start);

mStop = (Button)this.findViewById(R.id.stop);

mStart.setOnClickListener(clickListener);

mStop.setOnClickListener(clickListener);

//绑定远程跨进程Service

bindService(newIntent(REMOT_SERVICE_ACTION), connection, BIND_AUTO_CREATE);

}

@Override

protected void onDestroy() {

super.onDestroy();

//取消绑定远程跨进程Service

unbindService(connection);

}

private View.OnClickListener clickListener =newView.OnClickListener() {

@Override

public void onClick(View v) {

调用远程Service中的start与stop方法

switch(v.getId()) {

caseR.id.start:

try{

mBinder.start(0x110);

}catch(RemoteException e) {

e.printStackTrace();

}

break

caseR.id.stop:

try{

mBinder.stop(0x120);

}catch(RemoteException e) {

e.printStackTrace();

}

break

}

}

};

}

到此你对应用层通过AIDL使用远程Service的形式已经很熟悉了,至于实质的通信使用Binder的机制我们后面会写文章一步一步往下分析。到此的准备知识已经足够用来理解下面我们的源码分析了。

5-2 Toast窗口源码分析

我们常用的Toast窗口其实和前面分析的Activity、Dialog、PopWindow都是不同的,因为它和输入法、墙纸类似,都是系统窗口。

我们还是按照最常用的方式来分析源码吧。

我们先看下Toast的静态makeText方法吧,如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public static Toast makeText(Context context, CharSequence text, @Duration int duration) {

//new一个Toast对象

Toast result =newToast(context);

//获取前面有篇文章分析的LayoutInflater

LayoutInflater inflate = (LayoutInflater)

context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

//加载解析Toast的布局,实质transient_notification.xml是一个LinearLayout中套了一个@android:id/message的TextView而已

View v = inflate.inflate(com.android.internal.R.layout.transient_notification,null);

//取出布局中的TextView

TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);

//把我们的文字设置到TextView上

tv.setText(text);

//设置一些属性

result.mNextView = v;

result.mDuration = duration;

//返回新建的Toast

returnresult;

}

可以看见,这个方法构造了一个Toast,然后把要显示的文本放到这个View的TextView中,然后初始化相关属性后返回这个新的Toast对象。

当我们有了这个Toast对象之后,

可以通过show方法来显示出来,如下看下show方法源码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public void show() {

......

//通过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

}

}

我们看看show方法中调运的getService方法,如下:

1

2

3

4

5

6

7

8

9

10

11

12

//远程NotificationManagerService的服务访问接口

private static INotificationManager sService;

static private INotificationManager getService() {

//单例模式

if(sService !=null) {

returnsService;

}

//通过AIDL(Binder)通信拿到NotificationManagerService的服务访问接口

sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));

returnsService;

}

通过上面我们的基础脑补实例你也能看懂这个getService方法了吧。那接着我们来看mTN吧,好像mTN在Toast的构造函数里见过一眼,我们来看看,如下:

1

2

3

4

5

6

7

8

public Toast(Context context) {

mContext = context;

mTN =newTN();

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

}

可以看见mTN确实是在构造函数中实例化的,那我们就来看看这个TN类,如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

//类似于上面例子的服务端实例化的Service内部类Binder

private static class TN extends ITransientNotification.Stub {

......

//实现了AIDL的show与hide方法

@Override

public void show() {

if(localLOGV) Log.v(TAG,"SHOW: "+this);

mHandler.post(mShow);

}

@Override

public void hide() {

if(localLOGV) Log.v(TAG,"HIDE: "+this);

mHandler.post(mHide);

}

......

}

看见没有,TN是Toast内部的一个私有静态类,继承自ITransientNotification.Stub。你这时指定好奇 ITransientNotification.Stub是个啥玩意,对吧?其实你在上面的脑补实例中见过它的,他出现在服务端实现的Service中, 就是一个Binder对象,也就是对一个aidl文件的实现而已,我们看下这个ITransientNotification.aidl文件,如下:

1

2

3

4

5

6

7

package android.app;

/** @hide */

oneway interface ITransientNotification {

void show();

void hide();

}

看见没有,和我们上面的例子很类似吧。

再回到上面分析的show()方法中可以看到,我们的Toast是传给远程的NotificationManagerService管理的,为了 NotificationManagerService回到我们的应用程序(回调),我们需要告诉NotificationManagerService 我们当前程序的Binder引用是什么(也就是TN)。是不是觉得和上面例子有些不同,这里感觉Toast又充当客户端,又充当服务端的样子,实质就是一 个回调过程而已。

继续来看Toast中的show方法的service.enqueueToast(pkg, tn, mDuration);语句,service实质是远程的NotificationManagerService,所以enqueueToast方法就是 NotificationManagerService类的,如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

private final IBinder mService =newINotificationManager.Stub() {

// Toasts

// ============================================================================

@Override

public void enqueueToast(String pkg, ITransientNotification callback, int duration)

{

......

synchronized (mToastQueue) {

int callingPid = Binder.getCallingPid();

long callingId = Binder.clearCallingIdentity();

try{

ToastRecord record;

//查看该Toast是否已经在队列当中

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.

//注释说了,已经存在则直接取出update

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.

......

//将Toast封装成ToastRecord对象,放入mToastQueue中

record =newToastRecord(callingPid, pkg, callback, duration);

//把他添加到ToastQueue队列中

mToastQueue.add(record);

index = mToastQueue.size() - 1;

//将当前Toast所在的进程设置为前台进程

keepProcessAliveLocked(callingPid);

}

//如果index为0,说明当前入队的Toast在队头,需要调用showNextToastLocked方法直接显示

if(index == 0) {

showNextToastLocked();

}

} finally {

Binder.restoreCallingIdentity(callingId);

}

}

}

}

继续看下该方法中调运的showNextToastLocked方法,如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

void showNextToastLocked() {

//取出ToastQueue中队列最前面的ToastRecord

ToastRecord record = mToastQueue.get(0);

while(record !=null) {

try{

//Toast类中实现的ITransientNotification.Stub的Binder接口TN,调运了那个类的show方法

record.callback.show();

scheduleTimeoutLocked(record);

return

}catch(RemoteException e) {

......

}

}

}

继续先看下该方法中调运的scheduleTimeoutLocked方法,如下:

1

2

3

4

5

6

7

8

9

10

private void scheduleTimeoutLocked(ToastRecord r)

{

//移除上一条消息

mHandler.removeCallbacksAndMessages(r);

//依据Toast传入的duration参数LENGTH_LONG=1来判断决定多久发送消息

Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);

long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;

//依据设置的MESSAGE_TIMEOUT后发送消息

mHandler.sendMessageDelayed(m, delay);

}

可以看见这里先回调了Toast的TN的show,下面timeout可能就是hide了。接着还在该类的mHandler处理了这条消息,然后调运了如下处理方法:

1

2

3

4

5

6

7

8

9

10

private void handleTimeout(ToastRecord record)

{

......

synchronized (mToastQueue) {

int index = indexOfToastLocked(record.pkg, record.callback);

if(index >= 0) {

cancelToastLocked(index);

}

}

}

我们继续看cancelToastLocked方法,如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

void cancelToastLocked(int index) {

ToastRecord record = mToastQueue.get(index);

try{

//回调Toast的TN中实现的hide方法

record.callback.hide();

}catch(RemoteException e) {

......

}

//从队列移除当前显示的Toast

mToastQueue.remove(index);

keepProcessAliveLocked(record.pid);

if(mToastQueue.size() > 0) {

//如果当前的Toast显示完毕队列里还有其他的Toast则显示其他的Toast

showNextToastLocked();

}

}

到此可以发现,Toast的远程管理NotificationManagerService类的处理实质是通过Handler发送延时消息显示取消 Toast的,而且在远程NotificationManagerService类中又远程回调了Toast的TN类实现的show与hide方法。

现在我们就回到Toast的TN类再看看这个show与hide方法,如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

```java

private static class TN extends ITransientNotification.Stub {

......

//仅仅是实例化了一个Handler,非常重要!!!!!!!!

final Handler mHandler =newHandler();

......

final Runnable mShow =newRunnable() {

@Override

public void run() {

handleShow();

}

};

final Runnable mHide =newRunnable() {

@Override

public void run() {

handleHide();

// Don't do this in handleHide() because it is also invoked by handleShow()

mNextView =null

}

};

......

//实现了AIDL的show与hide方法

@Override

public void show() {

if(localLOGV) Log.v(TAG,"SHOW: "+this);

mHandler.post(mShow);

}

@Override

public void hide() {

if(localLOGV) Log.v(TAG,"HIDE: "+this);

mHandler.post(mHide);

}

......

}

可以看见,这里实现aidl接口的方法实质是通过handler的post来执行的一个方法,而这个Handler仅仅只是new了一下,也就是 说,如果我们写APP时使用Toast在子线程中则需要自行准备Looper对象,只有主线程Activity创建时帮忙准备了Looper(关于 Handler与Looper如果整不明白请阅读《Android异步消息处理机制详解及源码分析》)。

那我们重点关注一下handleShow与handleHide方法,如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

public void handleShow() {

if(localLOGV) Log.v(TAG,"HANDLE SHOW: "+this+" mView="+ mView

+" mNextView="+ mNextView);

if(mView != mNextView) {

// remove the old view if necessary

//如果有必要就通过WindowManager的remove删掉旧的

handleHide();

mView = mNextView;

Context context = mView.getContext().getApplicationContext();

String packageName = mView.getContext().getOpPackageName();

if(context ==null) {

context = mView.getContext();

}

//通过得到的context(一般是ContextImpl的context)获取WindowManager对象(上一篇文章分析的单例的WindowManager)

mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);

......

//在把Toast的View添加之前发现Toast的View已经被添加过(有partent)则删掉

if(mView.getParent() !=null) {

......

mWM.removeView(mView);

}

......

//把Toast的View添加到窗口,其中mParams.type在构造函数中赋值为TYPE_TOAST!!!!!!特别重要

mWM.addView(mView, mParams);

......

}

}

1

2

3

4

5

6

7

8

9

10

11

12

public void handleHide() {

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.

//注释说得很清楚了,不解释,就是remove

if(mView.getParent() !=null) {

mWM.removeView(mView);

}

mView =null

}

}

到此Toast的窗口添加原理就分析完毕了,接下来我们进行总结。

5-3 Toast窗口源码分析总结及应用开发技巧

经过上面的分析我们总结如下:

通过上面分析及上图直观描述可以发现,之所以Toast的显示交由远程的NotificationManagerService管理是因为 Toast是每个应用程序都会弹出的,而且位置和UI风格都差不多,所以如果我们不统一管理就会出现覆盖叠加现象,同时导致不好控制,所以Google把 Toast设计成为了系统级的窗口类型,由NotificationManagerService统一队列管理。

在我们开发应用程序时使用Toast注意事项:

  1. 通过分析TN类的handler可以发现,如果想在非UI线程使用Toast需要自行声明Looper,否则运行会抛出Looper相关的异常;UI线程不需要,因为系统已经帮忙声明。

  2. 在使用Toast时context参数尽量使用getApplicationContext(),可以有效的防止静态引用导致的内存泄漏。

  3. 有时候我们会发现Toast弹出过多就会延迟显示,因为上面源码分析可以看见Toast.makeText是一个静态工厂方法,每次调用这 个方法都会产生一个新的Toast对象,当我们在这个新new的对象上调用show方法就会使这个对象加入到 NotificationManagerService管理的mToastQueue消息显示队列里排队等候显示;所以如果我们不每次都产生一个新的 Toast对象(使用单例来处理)就不需要排队,也就能及时更新了。

6 Android应用Activity、Dialog、PopWindow、Toast窗口显示机制总结

可以看见上面无论Acitivty、Dialog、PopWindow、Toast的实质其实都是如下接口提供的方法操作:

1

2

3

4

5

6

public interface ViewManager

{

public void addView(View view, ViewGroup.LayoutParams params);

public void updateViewLayout(View view, ViewGroup.LayoutParams params);

public void removeView(View view);

}

整个应用各种窗口的显示都离不开这三个方法而已,只是token及type与Window是否共用的问题。

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0615/3044.html