Andoird中级——Window和WindowManger

Window和WindowManger

  • 实现可拖动的悬浮框
  • Window内部机制
    • 添加过程
    • 删除过程
    • 更新过程
  • Window的创建过程
    • Activity的Window创建过程
    • Dialog的Window创建过程
    • Toast的Window创建过程

实现可拖动的悬浮框

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


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

具体实现类是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 mViews = new ArrayList();

private final ArrayList mRoots = new ArrayList();
    
private final ArrayList mParams =
            new ArrayList();
            
private final ArraySet mDyingViews = new ArraySet();

添加过程

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

ViewRootImpl中的setView()方法会通过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);
}

删除过程

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) {
	......
	ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	......
	return contentParent;
}

上面的ID_ANDROID_CONTENT,在Window中定义为R.id.content

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

在ActivityThread中的handleResumeActivity()

  • 调用performResumeActivity()再调用Activity的performResume()回调onResume()
  • 调用Activity的makeVisible(),通过WindowManager将mDecor添加到Window
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
                                 String reason) {
    ......
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ....
    r.activity.makeVisible();
    .....
}


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

Dialog的Window创建过程

在构造函数中创建Window

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
    ......
    final Window w = new PhoneWindow(mContext);
    mWindow = w;
    w.setCallback(this);
    w.setOnWindowDismissedCallback(this);
    ......
}

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

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

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

public void show() {
    ......
    onStart();
    mDecor = mWindow.getDecorView();
    ......
    mWindowManager.addView(mDecor, l);
    ......
}

Dialog必须采用Activity的Context,否则会报错,但可以通过设置type让其成为系统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) {
           
        }
    }
}

你可能感兴趣的:(#,Android中级,android,java,android,studio)