一,寫在前面
這篇文章先介紹如何使用WindowManager向設備窗口裏添加View,並顯示View,然後從源碼角度分析這一過程。
二,WindowManager的使用
WindowManager的使用,先來看看效果,如下所示:
代碼如下:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
//創建WindowManager的佈局參數對象
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
//params.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
params.type = WindowManager.LayoutParams.TYPE_APPLICATION;
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = 50;
params.y = 400;
//創建一個View
final TextView view = new TextView(this);
view.setText("window_demo");
view.setTextSize(24);
view.setTextColor(Color.RED);
view.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
view.setTextColor(Color.BLUE);
return false;
}
});
//在窗口添加View
wm.addView(view, params);
}
}
分析:
1,LayoutParams是WindowManager中的一個內部類,它繼承至ViewGroup.LayoutParams,並實現了Parcelable接口,public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable{...}。需要重點關注LayoutParams中的兩個字段:type,flag。
type:決定了窗口的類型,Android給窗口設置了三個層級,抽象來說分爲:應用程序窗口,子窗口,系統窗口。具體來說對應int值的範圍,分別爲1~99,1000~1999,2000~2999。上面代碼中TYPE_SYSTEM_OVERLAY是系統窗口類型的一種,TYPE_APPLICATION是應用程序窗口的一種。窗口層級越大,該窗口會在比它層級小的窗口上面顯示,也就是層級大的窗口會覆蓋層級較小的窗口。因此,若想讓窗口處在所有窗口上面顯示,可以將type設置爲系統窗口。一般可以設置系統窗口的type值有:TYPE_SYSTEM_OVERLAY,TYPE_SYSTEM_ERROR。
特別需要注意的是:若將type設置爲系統窗口層級時,需要在AndroidManifest.xml文件中配置權限“android.permission.SYSTEM_ALERT_WINDOW”,否則會報異常信息。
如下所示:
flag:決定窗口的顯示特性,上面代碼中的FLAG_NOT_TOUCH_MODAL指:可以處理窗口區域裏的事件,區域外的事件傳給底層的Window。flag有很多值可以選擇,並使窗口顯示不同的特性,有興趣哥們可以自己去研究。
另外,LayoutParams還提供gravity,x,y三個屬性,gravity:決定窗口的重心位置,如果gravity不設置,窗口默認在屏幕中間。值得一提的是,x,y均是相對於設置好gravity後的偏移量。
2,上例中給TextView設置了觸摸的監聽,點擊TextView後修改其顏色爲藍色。若設置窗口類型爲TYPE_SYSTEM_OVERLAY,無法響應觸摸動作,顏色不會改變。這是爲什麼呢?因爲系統窗口不接收焦點,否則會干擾鍵盤窗口的輸入。翻看Android給TYPE_SYSTEM_OVERLAY的註釋便知,如下:
/**
* Window type: system overlay windows, which need to be displayed
* on top of everything else. These windows must not take input
* focus, or they will interfere with the keyguard.
* In multiuser systems shows only on the owning user's window.
*/
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
值得一提的是,即使設備當前顯示界面不是這個主界面,而是顯示桌面,系統窗口仍然顯示(有些手機需要手動設置懸浮框開啓,纔可以看到效果)。
效果如下:
上面介紹了WindowManager的一個簡單使用,將TextView添加到窗口中並顯示,下面分析WindowManager源碼角度的分析。
三,WindowManager源碼分析
public interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.
* <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
* errors, such as adding a second view to a window without removing the first view.
* <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
* secondary {@link Display} and the specified display can't be found
* (see {@link android.app.Presentation}).
* @param view The view to be added to this window.
* @param params The LayoutParams to assign to view.
*/
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
添加,更新,刪除Window的三個方法由ViewManager抽取定義,具體的實現在WindowManagerImpl。public interface WindowManager extends ViewManager {
//...
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
//...
int type;
int flag;
//...
}
//...
}
public final class WindowManagerImpl implements WindowManager {
//...code
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@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);
}
//...code
}
上面簡單介紹了抽象類Window,類WindowPhone,接口ViewManager,接口WindowManager,類WindowManagerImpl。3.1,進入主題
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(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.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
這個方法有很多重要的代碼,這裏我們只分析和WindowManager相關的代碼。12行,創建了PhoneWindow的實例;47行,創建WindowManagerImpl的實例,並賦值給字段Window$mWindowManager;54行 ,返回字段Window$mWindowManager的值,並賦值給字段Activity$mWindowManager。public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
繼續看WindowManagerImpl$createLocalWindowManager方法,源碼如下:public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
當name爲WINDOW_SERVICE時,返回字段mWindowManager。這個mWindowManager在前面的attach方法中已經賦值了,它是一個WindowManagerImpl對象。也就是說,demo中的wm就是WindowManagerImpl對象。那麼,wm.addView方法調用的是WindowManagerImpl的addView(...)。 @Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerImpl重寫了ViewManager中的addView方法,前面已經介紹過了三者之間的關係了。mGlobal變量是WindowManagerGlobal對象,在類WindowManagerImpl中創建了WindowManagerGlobal對象:private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(),mGlobal是一個單例對象。 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 {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
第3行,第6行,第9行分別對參數view,display,params進行檢查;public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
//...code
requestLayout();
//...code
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
//...code
}
setView方法代碼比較多,這裏只展示比較重要的兩個點。requestLayout()方法是View繪製流程的入口,mWindowSession.addToDisplay是真正實現window添加的地方,mWindowSession到底是什麼呢,繼續往下分析。 public ViewRootImpl(Context context, Display display) {
mContext = context;
//通過Binder機制實現,獲取mWindowSession,即Session
mWindowSession = WindowManagerGlobal.getWindowSession();
//...code
}
繼續查看WindowManagerGlobal相關方法的源碼:public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
//繼續看getWindowManagerService方法
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
try {
if (sWindowManagerService != null) {
ValueAnimator.setDurationScale(
sWindowManagerService.getCurrentAnimatorScale());
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowManagerService;
}
}
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
IInputContext inputContext) {
if (client == null) throw new IllegalArgumentException("null client");
if (inputContext == null) throw new IllegalArgumentException("null inputContext");
Session session = new Session(this, callback, client, inputContext);
return session;
}
裏面創建了一個Session對象,也就是說前面mWindowSession就是Session對象。 @Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
這裏的mService就是遠程的系統服務WindowManagerService,也就說添加window的操作,最終交給了系統服務WindowManagerService來處理。