《Android开发艺术探索》第8章的笔记 :)
1、基础使用
首先利用WindowManager添加一个Window
这里直接上代码
mFloatingButton = Button(this)
mFloatingButton.text="button"
mLayoutParams=WindowManager.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,ActionBar.LayoutParams.WRAP_CONTENT,0,0,PixelFormat.TRANSPARENT)
mLayoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
mLayoutParams.type=WindowManager.LayoutParams.TYPE_APPLICATION
mLayoutParams.gravity = Gravity.LEFT or Gravity.TOP
mLayoutParams.x=100
mLayoutParams.y=300
manager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
manager.addView(mFloatingButton,mLayoutParams)
声明一个mFloatingButton,之后设置LayoutParams的各项参数。最后调用Window.addView添加View。
flag
- FLAG_NOT_FOCUSABLE
window不需获得焦点,也不需接受输入事件,且该falg会同时添加FLAG_NOT_TOUCH_MODAL - FLAG_NOT_TOUCH_MODAL
当前window范围内大点击自己处理,范围外的传递给下一层 - FLAG_SHOW_WHEN_LOCKED
让window显示在锁屏界面
type
type参数表示Window的类型,Window共有三种类型,应用Window,子Window,系统Window
- 应用Window
应用Window对应着一个Activity,层级范围是1~99。 - 子Window
子Window不能单独存在,需要附属在特定的父Window中,层级范围是1000~1999。 - 系统Window
系统Window需要声明相应的权限,即"<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>",层级为2000~2999。
Windowmanager
WindowManager常用的有添加,更新,删除View。这三个方法定义在ViewManager中,WindowManager继承了ViewManager。
public interface ViewManager {
void addView(View var1, LayoutParams var2);
void updateViewLayout(View var1, LayoutParams var2);
void removeView(View var1);
}
2、WindowManager常用方法
WindowManager实际上是一个接口,真正的实现是WindowManagerImpl类。
在WindowManagerImpl中的实现如下:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
即在WindowManagerImpl中交给了WindowManagerGlobal实现。
下面看在WindowManagerGlobal中的相关的列表定义。
private final ArrayList<View> mViews = new ArrayList<View>();
//存储所有Window对应的View
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//存储所有Window对应的ViewRootImpl
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
//存储所有Window对应的布局参数
private final ArraySet<View> mDyingViews = new ArraySet<View>();
//存储已经调用remove但未被删除的Window
这四个列表存储了Window的信息,将用在接下来的添加,更新和删除。
2.1、addView
- 第一步验证参数合法,同时判断有无parentWindow,即是否是子Window,如果是子Window,则调整相应参数。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
- 第二步创建ViewRootImpl,并添加View至列表.
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root)
mParams.add(wparams);
- 第三步更新界面完成Window的添加过程
try {
root.setView(view, wparams, panelParentView);
}
catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
在addView中最后可以看到调用了ViewRootImpl的setView方法,在setView内部异步刷新请求,之后通过WindowSession完成对Window的添加。其中所使用的WindowSession是一个Binder对象,在Session类中实现,在WindowManagerService中实现Window的添加。
ViewRootImpl.setVIew -> WindowSession -> Session -> WindowManagerService
2.2、removeView
删除Window同样是通过WindowManagerImpl调用WindowManagerGlobal中的removeView方法。
removeView的源码远少于addView,如下:
- removeView
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);
}
}
首先调用removeView后,会先验证View是否为null,之后通过findViewLocked查找相应View的索引,即在列表中的位置。之后调用removeViewLocked删除View。
- removeViewLocked
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
在removeViewLocked中,首先获得需要删除的View,之后通过die方法完成。
- 如果是异步删除,die方法则发送一个删除的指令,之后立刻返回。即在die返回时,并没有完成删除操作,而是将View加入到了mDyingViews列表,即待删除列表。之后又ViewRootImpl中的Handler调用doDie方法完成删除。
- 如果是同步删除,则在die方法中直接调用doDie方法删除View。
doDie方法内部则会调用dispatchDetachedFromWindow方法完成对View的删除,dispatchDetachedFromWindow将主要完成:1.垃圾回收;2.通过Session移除Window(最终调用WindowManagerService的removeWindow方法;3.调用View的dispatchDetachedFromWindow,在内部调用onDetachedFromWindow和onDetachedFromWindowInternal。在这个方法中完成资源回收;4.调用
WindowManagerGlobal的doRemoveView刷新数据,刷新mRoots,mParams,mDyingViews。
2.3、updateView
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);
}
}
在更新方法中只需要替换掉mParams中的布局信息,之后通过setLayoutParams更新ViewRootImpl中的LayoutParams。在ViewRootImpl中的LayoutParams会通过scheduleTraversals对View重新布局,并通过WindowSession更新Window视图。
3、Window的创建
3.1、Activity的Window创建
Activity的启动过程中,在ActivityThread中的performLaunchActivity方法会通过类加载器创建Activity的实例对象,并调用attach方法为其关联一系列环境变量,在attach方法中,会创建Activity所属的Window并设置回调接口。Window的创建是通过PolicyManager的makeNewWindow方法实现的。当Window接收到外界状态改变时就会回调Activity的方法。
- 创建Window
mWindow=PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if(info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED){
mWindow.setSoftInputMode(info.softInputMode);
}
if(info.uiOptions!=0){
mWindow.setUiOption(info.uiOptions);
}
PolicyManager是一个策略类,方法全部在IPolicy中声明,IPolicy声明如下。
public interface IPolicy{
public Window makeNewWindow(Context context);
public LayoutInflater makeNewLayoutInflater(Context context);
public WindowManagerPolicy makeNewWindowManager();
public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}
在实际调用中,PolicyManager的真正实现类是Policy,在Policy的makeNewWindow方法中,返回了PhoneWindow(context),所以Window的真正实现类是PhoneWindow。
- setContentView
public void setContentView(int layoutResID){
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
关于Ativity视图的附着,可以看到Activity将具体的实现交给了Window,Window的具体实现在PhoneWindow。
在PhoneWindow中:
1.创建DecorView,DecorView是顶级View,创建过程由installDecor完成,在内部调用generateDecor来完成创建。初始化时,PhoneWindow还需要通过generateLayout加载具体的布局文件到DecorView。如下
View in=mLayouytInflater.inflate(layoutResource,null);
decor.addView(in,new ViewGroup.LayoutPatams(MATCH_PARENT,MATCH_PARENT));
mContentRoot=(ViewGroup)in;
ViewGroup contentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT);
2.将View添加到DecorView的mContentParent中,因为之前已经创建完成了DecorVicw,因此只需要添加:mLayoutInflater.inflate(layoutResID,mContentParent)
3.回调Activity的onContentChanged方法通知Acivity视图已经发生改变。在布局文件被添加到DecorView后,会通知Activity。Activity的onContentChanged是一个空实现,可以在子Activity中处理回调。
final Callback cb=getCallback();
if(cb!=null && !isDistoryed()){
cb.onContentChanged();
}
4.在Activity的onResume方法中会调用makeVisible,使DecorView完成了添加和显示过程,此时Activity的视图才被用户看到。
void makeVisible(){
if(!mWindowAdded){
ViewManager wm=getWindowManager();
wm.addView(mDecor,getWindow().getAttributes());
mWindowAdded=true;
}
mDecor.setVisibility(View.VISIBLE);
}
3.2、Dialog的Window创建
Dialog的创建于Activity类似
1.创建Window
Dialog中的创建同样是通过PolicyManager的makeNewWindow完成的,创建后的实际对象是PhoneWindow。与Avtivity相同。
2.初始化DecorView并添加Dialog视图到DecorView
3.将DecorView添加到Window中显示
在Dialog的show方法中,会通过WindowManager将DecorVice添加到Window中
mWindowManager.addView(mDecor,1);
mShowing=true;
Dialog创建Window和Activity的Window创建过程类似。当Dialog移除时,会通过WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor)
PS:Dialog必须使用Activity的context,而不能使用Application的context
3.3、Toast的Window创建
Toast是基于Window来完成的,同时采用了Handler实现了定时取消的功能。在Toast内部有两类IPC过程,一是Toast访问NotificationManagerService,另一类是NotificationManagerService回调Toast中的TN接口。
Toast内部的视图有两种方式,一种是系统默认样式,或者通过setView方法指定一个自定义View,二者都对应了Toast内部的一个View类型成员mNextView。Toast内部提供了show和cancel方法用于显示和隐藏。内部为IPC过程。
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
}
}
public void cancel(){
mTN.hide();
try{
getService().cancelToast(mContext.getPackageName(),mTN);
}catch(RemoteException e){
//Empty
}
}
在show和cancel方法中,都通过NMS来具体实现,NMS运行在系统的进程中,所以只能通过远程调用显示。
TN是一个Binder类,在进行IPC时,NMS处理Toast显示和取消请求时会回调TN中的方法,又因为TN运行在Binder线程池中,所以需要Handler切换到发送Toast的线程。因为使用了Handler,所以Toast无法在没有Looper的线程中使用。
Toast的显示调用了NMS的enqueueToast方法,enqueueToast首先将Toast封装成ToastRecord对象并添加到MToastQueue队列中,MToastQueue实际上是一个数组,大小为50。
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;
}
}
}
}
当Toast被添加到MToastQueue中,NMS会通过showNextToastLocaked方法显示当前Toast,Toast的显示是由ToastRecord的callback完成的。
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(record.token);
cheduleTimeoutLocked(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);
}
keepProcessAliveIfNeededLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
在Toast显示完成后,会通过scheduleTimeroutLocked发送延时消息
private void scheduleTimeoutLocked(ToastRecord r){
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
mHandler.sendMessageDelayed(m, delay);
}
LONG_DELAY是3.5s,SHORT_DELAY是2s。延迟时间后,NMS会通过cancelToastLocked隐藏Toast并将其从mToastQueue中删除。
Toast的隐藏同样是通过ToastRecord的callback完成的,是一次IPC过程。
try {
record.callback.hide();
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to hide notification " + record.callback
+ " in package " + record.pkg);
// don't worry about this, we're about to remove it from
// the list anyway
}
综上,Toast的显示和隐藏实际上是通过Toast的TN这个方法实现的,他有show和hide两个方法。两个方法是被NMS以跨进程调用的,因此运行在Binder的线程池内,同时使用了Handler以便于切换到当前线程。
@Override
public void show(){
if(localLOGY)log.v(TAG,"SHOW:" + this);
mHandler.post(mShow);
}
@Override
public void hide(){
if(localLOGY)log.v(TAG,"HIDE:" + this);
mHandler.post(mHide);
}
此处的mShow和mHide是Runnable类型,内部调用了handleShow和handleHide来具体实现Toast的显示隐藏。
//handleShow
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);
//handleHide
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeViewImmediate(mView);
}
参考来源:
《Android开发艺术探索》