Andoird中级——Window和WindowManger

实现可拖动的悬浮框

如下添加一个Button到屏幕(100,300)的位置

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
  • FLAG_NOT_TOUCH_MODAL 表示当前Window区域外的点击事件传递给底层的Window,区域内的事件则自己处理,一般都要开启
  • TYPE_APPLICATION_OVERLAY 表示覆盖在所有应用上面,需要申请权限和手动授权
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private Button mButton;
    private WindowManager.LayoutParams mLayoutParams;
    private WindowManager mWindowManager;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        checkAndRequestPermissions();
    }

    private void showButton() {
        mButton = new Button(this);
        mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        mLayoutParams = new WindowManager.LayoutParams();

        mButton.setText("test");
        mButton.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                float rawX = event.getRawX();
                float rawY = event.getRawY();
                switch (event.getAction()) {
                    case MotionEvent.ACTION_MOVE:
                        mLayoutParams.x = (int) rawX;
                        mLayoutParams.y = (int) rawY;
                        mWindowManager.updateViewLayout(mButton, mLayoutParams);
                        break;
                }
                return false;
            }
        });
        mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
        mLayoutParams.x = 100;
        mLayoutParams.y = 300;
        mWindowManager.addView(mButton, mLayoutParams);
    }

    private void checkAndRequestPermissions() {
        if (!Settings.canDrawOverlays(this)) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, 1);
        } else {
            showButton();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1) {
            if (!Settings.canDrawOverlays(this)) {
                Toast.makeText(this, "权限授予失败,无法开启悬浮窗", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "权限授予成功,开启悬浮窗", Toast.LENGTH_SHORT).show();
                showButton();
            }
        }
    }
}

Window有三种,由type决定层级,大的会覆盖在小的上面:

  • 应用Window:对应一个Activity,层级1-99
  • 子Window:不能单独存在,需要依附在父Window,如Dialog,层级1000-1999
  • 系统Window:申请权限才能创建,如Toast,层级2000-2999

Window内部机制

WindowManager继承ViewManager,但其本身是接口

public interface WindowManager extends ViewManager {
	......
}

public interface ViewManager{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

实现ViewManager 3个方法的类是WindowManagerImpl,传递给WindowManagerGlobal

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
            mContext.getUserId());
}

@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.updateViewLayout(view, params);
}

@Override
public void removeView(View view) {
    mGlobal.removeView(view, false);
}

WindowManagerGlobal中的如下域存储Window所对应的View、ViewRootImpl、LayoutParams以及调用removeView但还未被删除的对象

private final ArrayList<View> mViews = new ArrayList<View>();

private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    
private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
            
private final ArraySet<View> mDyingViews = new ArraySet<View>();

添加过程

WindowManagerGlobal中的addView()方法中,将View、ViewRootImpl、LayoutParams对象添加到列表

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId){
	.....
	root = new ViewRootImpl(view.getContext(), display);
	view.setLayoutParams(wparams);
	mViews.add(view);
	mRoots.add(root);
	mParams.add(wparams);
	root.setView(view, wparams, panelParentView, userId);
	.....
}

ViewRootImpl中的setView()方法会调用requestLayout()异步刷新View

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

ViewRootImpl中的scheduleTraversals()通过postCallback()运行TraversalRunnable线程

@UnsupportedAppUsage
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

ViewRootImpl中的doTraversal()会调用performTraversals()完成measure、layout、draw

void doTraversal() {
    if (mTraversalScheduled) {
        ......
        performTraversals();
        ......
    }
}

控件加载完后,接下来会通过mWindowSession的addToDisplayAsUser()添加Window

try {
    ......
    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
            mAttachInfo.mDisplayCutout, inputChannel,
            mTempInsets, mTempControls);
    setFrame(mTmpFrame);
} catch (RemoteException e) {
    ......
} finally {
    ......
}

mWindowSession类型为IWindowSession,其是Binder对象,添加过程是一次IPC调用,实现类是Session,内部通过WindowManagerService实现添加

@Override
public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
                              int viewVisibility, int displayId, int userId, Rect outFrame,
                              Rect outContentInsets, Rect outStableInsets,
                              DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
                              InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
            outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
            outInsetsState, outActiveControls, userId);
}

WindowManagerService的addWindow()

public int addWindow(Session session, IWindow client, int seq, LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,int requestUserId) {
    ......
    //首先检查权限
    int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName, appOp);
    .....
    synchronized (mGlobalLock) {
      	.....
      	//判断Window类型
        if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
            parentWindow = windowForClientLocked(null, attrs.token, false);
            ......
        }
		.....
      
		//创建WindowState和相关属性关联
        final WindowState win = new WindowState(this, session, client, token, parentWindow, appOp[0], seq, attrs, viewVisibility, session.mUid, userId, session.mCanAddInternalSystemWindow);
                
        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
        displayPolicy.adjustWindowParamsLw(win, win.mAttrs, callingPid, callingUid);
        attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);

        res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
        ......
		//注册通道,使窗口可接收事件
        final boolean openInputChannels = (outInputChannel != null && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        if  (openInputChannels) {
            win.openInputChannel(outInputChannel);
        }
		......
        win.attach();
        mWindowMap.put(client.asBinder(), win);
        win.initAppOpsState();
        ......

        win.mToken.addWindow(win);
        displayPolicy.addWindowLw(win, attrs);
        .......
        
		//设置动画
        final WindowStateAnimator winAnimator = win.mWinAnimator;
        winAnimator.mEnterAnimationPending = true;
        winAnimator.mEnteringAnimation = true;
        if (activity != null && activity.isVisible()&& !prepareWindowReplacementTransition(activity)) {
            prepareNoneTransitionForRelaunching(activity);
        }
    }.......
    return res;
}

删除过程

WindowManagerGlobal中的removeView()通过findViewLocked()找到待删除View的索引

@UnsupportedAppUsage
public void removeView(View view, boolean immediate) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        View curView = mRoots.get(index).getView();
        removeViewLocked(index, immediate);
        if (curView == view) {
            return;
        }
        throw new IllegalStateException("Calling with view " + view
                + " but the ViewAncestor is attached to " + curView);
    }
}

private int findViewLocked(View view, boolean required) {
    final int index = mViews.indexOf(view);
    if (required && index < 0) {
        throw new IllegalArgumentException("View=" + view + " not attached to window manager");
    }
    return index;
}

再调用WindowManagerGlobal中的removeViewLocked()

private void removeViewLocked(int index, boolean immediate) {
    ViewRootImpl root = mRoots.get(index);
    View view = root.getView();
    if (root != null) {
        root.getImeFocusController().onWindowDismissed();
    }
    boolean deferred = root.die(immediate);
    if (view != null) {
        view.assignParent(null);
        if (deferred) {
            mDyingViews.add(view);
        }
    }
}

WindowManager提供了removeView() / removeViewImmediate() 异步 / 同步删除,具体实现在ViewRootImpl的die(),异步删除只发送消息,并将其添加到mDyingViews列表

boolean die(boolean immediate) {
    if (immediate && !mIsInTraversal) {
        doDie();
        return false;
    }
    if (!mIsDrawing) {
        destroyHardwareRenderer();
    } else {
        Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                "  window=" + this + ", title=" + mWindowAttributes.getTitle());
    }
    mHandler.sendEmptyMessage(MSG_DIE);
    return true;
}

ViewRootImpl的doDie()调用dispatchDetachedFromWindow(),主要做

  • GC、清除数据、回调
  • 调用Session的remove()删除Window(IPC),最后调到WindowManagerService的removeWindow()
  • 调用View的dispatchDetachedFromWindow(),回调onDetachedFromWindow()
  • 调用WindowManagerGlobal的doRemoveView()刷新数据(mRoot、mParams、mDyingViews)
void doDie() {
    checkThread();
    if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
    synchronized (this) {
        if (mRemoved) {
            return;
        }
        mRemoved = true;
        if (mAdded) {
            dispatchDetachedFromWindow();
        }
       ......
    }
    WindowManagerGlobal.getInstance().doRemoveView(this);
}

更新过程

WindowManagerGlobal的updateViewLayout()

  • 更新View的LayoutParams
  • 调用ViewRootImpl中的setLayoutParams(),调用scheduleTraversals()重新measure、layout、draw
  • 调用ViewRootImpl中的relayoutWindow(),调用WindowSession的relayout(),最后到WindowManagerService的relayoutWindow()
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    view.setLayoutParams(wparams);
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        mParams.add(index, wparams);
        root.setLayoutParams(wparams, false);
    }
}

Window的创建过程

Activity的Window创建过程

ActivityThread中的performLaunchActivity()通过ClassLoader创建activity,调用attach()传入运行过程中所需要的上下文环境变量

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ......
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        ......
    }......
    try {
        ......
        if (activity != null) {
            ......
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.configCallback,
                    r.assistToken);
            ......
        }
        ......
    }......
    return activity;
}

Activity的attach()创建Window的唯一实现类PhoneWindow,将自身作为回调

final void attach(......) {
	......
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ......
}

Activity在setContentView()将布局ID传给了PhoneWindow

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

PhoneWindow的setContentView()如下

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();		// 1. 创建mDecor 、mContentParent
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);	// 2.将Activity的布局加载到DecorView中的mContentParent(即R.id.content)
    }
 	mContentParent.requestApplyInsets();
	final Callback cb = getCallback();
	if (cb != null && !isDestroyed()) {
    	cb.onContentChanged();		// 3. 回调Activity的onContentChanged()
	}
	mContentParentExplicitlySet = true;
}

PhoneWindow的installDecor()通过generateDecor()、generateLayout()创建mDecor 、mContentParent

private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor(-1);
        ......
    } ......
    
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
        ......
	}
}

protected DecorView generateDecor(int featureId) {
    ......
    return new DecorView(context, featureId, this, getAttributes());
}

protected ViewGroup generateLayout(DecorView decor) {

	TypedArray a = getWindowStyle();	//根据Style设置属性
	......
	if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
   		 requestFeature(FEATURE_NO_TITLE);
	}......

	int layoutResource;		//布局资源ID,接下来根据属性设置不同的布局
    int features = getLocalFeatures();
    if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        ......
    }else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {	//取消Title,故其需要在setContentView()之前设置
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
            layoutResource = a.getResourceId(R.styleable.Window_windowActionBarFullscreenDecorLayout, R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
    }......

    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);	//将布局ID加载到mDecor
	......
	ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);	//获取content
	......
	if (getContainer() == null) {		//设置背景和Title
    	mDecor.setWindowBackground(mBackgroundDrawable);
   		 if (mTitle != null) {
        	setTitle(mTitle);
    	}
    	if (mTitleColor == 0) {
        	mTitleColor = mTextColor;
    	}
    	setTitleColor(mTitleColor);
	}
	......
	return contentParent;
}

上面的ID_ANDROID_CONTENT为R.id.content

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

以R.layout.screen_title的布局为例,R.id.content将是main_activity.xml的父布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

在ActivityThread中的handleResumeActivity()

  • 调用performResumeActivity()再调用Activity的performResume()回调onResume()
  • 若window==null,则获取Window、DecorView、WindowManager、LayoutParams,将DecorView添加到WindowManager
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
 	......
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ......
    final Activity a = r.activity;
	......
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        ......
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            }......
        }
    }......
    if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
        ......
        if (r.activity.mVisibleFromClient) {
            r.activity.makeVisible();
        }
    }
	......
}

若window!=null,则调用Activity的makeVisible(),通过WindowManager将mDecor添加到Window

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

Dialog的Window创建过程

在构造函数中创建Window,通过setWindowManager()和WindowManagerImpl建立联系

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
    ......
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    final Window w = new PhoneWindow(mContext);
    mWindow = w;
    w.setCallback(this);
    w.setOnWindowDismissedCallback(this);
    w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
    w.setWindowManager(mWindowManager, null, null);
    ......
}

在setContentView()中通过Window将布局添加到DecorView

public void setContentView(@LayoutRes int layoutResID) {
    mWindow.setContentView(layoutResID);
}

在show()中通过addView()将DecorView添加到Window,在dismiss()中通过removeViewImmediate()移除

public void show() {
    ......
	if (!mCreated) {
        dispatchOnCreate(null);
    } else {
        final Configuration config = mContext.getResources().getConfiguration();
        mWindow.getDecorView().dispatchConfigurationChanged(config);
    }    
    onStart();
    mDecor = mWindow.getDecorView();
    ......
    mWindowManager.addView(mDecor, l);
    ......
}

Dialog必须采用Activity的Context,否则会报错,但可以通过设置type让其成为系统Window

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
public class MainActivity extends AppCompatActivity {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        checkAndRequestPermissions();
    }

    private void showDialog(){
        Dialog dialog = new Dialog(getApplicationContext());
        TextView textView = new TextView(this);
        textView.setText("test");
        dialog.setContentView(textView);
        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
        dialog.show();
    }

    private void checkAndRequestPermissions() {
        if (!Settings.canDrawOverlays(this)) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, 1);
        } else {
            showDialog();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1) {
            if (!Settings.canDrawOverlays(this)) {
                Toast.makeText(this, "permission denied", Toast.LENGTH_SHORT).show();
            } else {
                showDialog();
            }
        }
    }
}

Toast的Window创建过程

Toast基于Handler,且内部有两类IPC

  • Toast访问NotificationManagerService
  • NotificationManagerService回调Toast的TN接口

Toast的视图可采用系统默认,或通过setView()自定义View,其show()方法如下

public void show() {
    ......
    INotificationManager service = getService();
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;
    final int displayId = mContext.getDisplayId();
    try {
        if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
            if (mNextView != null) {
                service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
            } else {
                ITransientNotificationCallback callback =
                        new CallbackBinder(mCallbacks, mHandler);
                service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);
            }
        } else {
            service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
        }
    } catch (RemoteException e) {

    }
}

NotificationManagerService的enqueueToast方法()会将Toast封装成ToastRecord再添加到mToastQueue,同一包名应用最多同时存在50个,防止一直弹Toast导致其他应用没有机会弹

private void enqueueToast(......) {
    synchronized (mToastQueue) {
        ......
        try {
            ToastRecord record;
            int index = indexOfToastLocked(pkg, token);
            if (index >= 0) {
                record = mToastQueue.get(index);
                record.update(duration);
            } else {
                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 = getToastRecord(callingUid, callingPid, pkg, token, text, callback,
                        duration, windowToken, displayId, textCallback);
                mToastQueue.add(record);
                index = mToastQueue.size() - 1;
                .....
            }
            if (index == 0) {
                showNextToastLocked();
            }
        }.....
    }
}

具体显示Toast的方法在showNextToastLocked(),调用ToastRecord的show()显示,并从mToastQueue移除

void showNextToastLocked() {
    ToastRecord record = mToastQueue.get(0);
    while (record != null) {
        if (record.show()) {
            scheduleDurationReachedLocked(record);
            return;
        }
        int index = mToastQueue.indexOf(record);
        if (index >= 0) {
            mToastQueue.remove(index);
        }
        record = (mToastQueue.size() > 0) ? mToastQueue.get(0) : null;
    }
}

scheduleDurationReachedLocked()通过Handler发送一个MESSAGE_DURATION_REACHED控制其显示时间,这里也可知LENGTH_LONG是3.5s,LENGTH_SHORT是2s

private void scheduleDurationReachedLocked(ToastRecord r) {
    mHandler.removeCallbacksAndMessages(r);
    Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
    int delay = r.getDuration() == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
    delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
            AccessibilityManager.FLAG_CONTENT_TEXT);
    mHandler.sendMessageDelayed(m, delay);
}

收到MESSAGE_DURATION_REACHED后,通过cancelToastLocked()调用ToastRecord的hide(),再次调用showNextToastLocked()显示下一个Toast

void cancelToastLocked(int index) {
    ToastRecord record = mToastQueue.get(index);
    record.hide();
    ToastRecord lastToast = mToastQueue.remove(index);

    ......
    if (mToastQueue.size() > 0) {
        showNextToastLocked();
    }
}

ToastRecord的show()会回调到Toast中TN的handleShow(),将View添加到Window

public void handleShow(IBinder windowToken) {
  	......
    if (mView != mNextView) {
   		......
        mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        ......
        try {
            mWM.addView(mView, mParams);
            trySendAccessibilityEvent();
        } catch (WindowManager.BadTokenException e) {
           
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值