悬浮窗口相信开发android的猩猩们都遇到过或者实现过,简单的说明原理就是获取WindowManager对象,通过该对象的addView和removeView来向一个页面添加一个悬浮框和删除该悬浮框,其实用WindowManger这个可以实现好多小功能:
1)比如在TV端开发的过程中如果某一页面有分页显示数据的话,当用户按遥控器数字键翻页的时候在页面中动态添加一个View来显示用户输入的数字。
2)可以在MediaPlayer全屏播放的时候在某个时机动态添加一个View来展示相关内容。
3)一些手机管理软件也用悬浮框实现来部署一些快捷功能
实现上述功能有两种思路或者方法来实现:
1) 通过(WindowManager)getSystemService(Context.WINDOW_SERVICE);来获取WindowManger对象,调用addView来添加悬浮框,在用addView添加View的时候需要设置该LayoutParams来控制View的大小或者显示位置。
2)在获取WindowManger的同时,在通过反射机制调用makeNewWindow来生成一个Window里面。我们知道Activity都封装了一个Window对象(PhoneWindow),在Activity里面调用setContentView的时候实际上是调用Window对象的setContentView方法。那么我们完全也可以用此种方法来实现:
通过反射机制来获取Window对象的方法如下:
try {
@SuppressWarnings("rawtypes")
Class policyManagerClass = Class.forName("com.android.internal.policy.PolicyManager");
@SuppressWarnings("rawtypes")
Class[] parameterTypes = {Context.class};
@SuppressWarnings("unchecked")
Method method = policyManagerClass.getMethod("makeNewWindow", parameterTypes);
mWindow = (Window)method.invoke(null, this);
mWindow.setFormat(PixelFormat.TRANSLUCENT);
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
mWindow.setWindowManager(mWindowManager, null, null);
mWindow.setBackgroundDrawable(colorDrawable);
} catch (Exception e1) {
e1.printStackTrace();
}
此时添加View的时候可以用下面两行代码来实现:
mWindow.setContentView(view, params)
mWindowManager.addView(mWindow.getDecorView(), params);
删除添加的View的时候可以下面两段代码来实现:
//注意Window对象是没有提供remove等实现删除View的方法的,所以要结合
//mWIndowManger来完成
mWindowManager.removeView(mWindow.getDecorView());
写到这儿可以思考如下问题来理一下思路:
1)Activity中Window具体的说是PhoneWindow在Activity中是怎么初始化的?
2)WindowManger对象又是怎么初始化的?
由于水平有限阅读Activity源码对我来说是个间距的任务,但结合网上大牛们写的技术博客和相关书籍资料(前人栽树后人乘凉就是方便),可以知道在Activity的attach方法里面完成了Window和WindowManager的初始化(简化如下):
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) {
attachBaseContext(context);
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setWindowManager(null, mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
mWindow通过PolicyManger的makeNewWindow来初始化,PolicyManger的源代码也很简单(为了减少篇幅,删除了一些本篇博客不需要的部分代码):
public final class PolicyManager {
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";
private static final IPolicy sPolicy;
static {
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
//通过反射来对IPolicy进行初始化
sPolicy = (IPolicy)policyClass.newInstance();
}
//私有构造器,不能在外部初始化
private PolicyManager() {}
// The static methods to spawn new policy-specific objects
//在attach方法中正式通过调用这个方法来初始化Window对象
public static Window makeNewWindow(Context context) {
return sPolicy.makeNewWindow(context);
}
}
正如上面代码展现的那样,先通过反射来初始化IPolicy 对象实际上是IPolicy接口的实现类Policy,通过该对象来调用makeNewWindow来实例化一个Window对象,所以接下来很简单就看看这个Policy是个什么鬼:
public class Policy implements IPolicy {
private static final String TAG = "PhonePolicy";
private static final String[] preload_classes = {
"com.android.internal.policy.impl.PhoneLayoutInflater",
"com.android.internal.policy.impl.PhoneWindow",
"com.android.internal.policy.impl.PhoneWindow$1",
"com.android.internal.policy.impl.PhoneWindow$ContextMenuCallback",
"com.android.internal.policy.impl.PhoneWindow$DecorView",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
};
static {
// For performance reasons, preload some policy specific classes when
// the policy gets loaded.
for (String s : preload_classes) {
try {
Class.forName(s);
} catch (ClassNotFoundException ex) {
Log.e(TAG, "Could not preload class for phone policy: " + s);
}
}
}
//此正式初始化Activity中mWindow对象的地方
public PhoneWindow makeNewWindow(Context context) {
return new PhoneWindow(context);
}
public PhoneLayoutInflater makeNewLayoutInflater(Context context) {
return new PhoneLayoutInflater(context);
}
public PhoneWindowManager makeNewWindowManager() {
return new PhoneWindowManager();
}
}
上面的代码简单的说明了一下mWindow的初始化流程,获得的是PhoneWindow。下面就简单的分析下WindowManger又是怎么初始化的:
用初始化后的mWindow调用setWindowManager来完成了Window的初始化操作:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
.....
//在attach中传入的是null,所以if条件成立
if (wm == null) {
//此处vm为WindowManagerImpl
wm = WindowManagerImpl.getDefault();
}
//初始化Winow类中封装的mWindowManger变量
//该变量也是一个WindowManger
mWindowManager = new LocalWindowManager(wm, hardwareAccelerated);
}
WindowMangerImpl.getDefault方法直接返回的是一个WindowMangerImpl对象,该对象是接口WindowManger的实现类。紧接着初始化了Window类中的WindowManger类型的变量mWindowManger!而最终在Activity的attach方法中对Activity的mWindowManger这个WindowManger类型的赋值就是通过mWindow.getWindowManager();这个WindowManger就是LocalWindowManager。
到此为止简单的梳理了一下Window和WindowManger的初始化过程,其实我们在Activity中使用WindowManager的时候是调用getSystemService(Context.WINDOW_SERVICE);来获取的,那就在看看该方法都做了些什么事儿:
public Object getSystemService(String name) {
//返回的上面所说的LocalWindowManager
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
可见通常在Activity中获取的WindowManager中获取到的就是LocalWindowManager.
最后在简单梳理一下WindowManager的类继承关系:
其实虽然说LocalWindowManager继承了WindowManager,但是真正工作的还是该类里面持有的WindowManagerImpl类型的mWindowManager这个变量指向的对象WindowManagerImpl.LocalWindowManager主要起到了一种代理的作用。
上面简单的说了一下WindowManager和Window的关系,但是我们如果有好奇心的话应该会有这么个疑问:在Activity中通过setContentView方法把将View添加到DecorView中《详细说明点击此处》,但是这个DecorView又添加到哪儿了呢?在对WindowManager的说明中我们知道我们可以通过WindowManager的addView方法可以添加View.所以我们可以设想一下这个DecorView是不是也添加到WindowManager里面了呢?结合网上的资料查阅ActivityThread类中的handleResumeActivity方法有这么一段代码:
final Activity a = r.activity; final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
//获取Activity的window对象为PhoneWindow
r.window = r.activity.getWindow();
//获取DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获取Activity的WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//把DecorView添加到windowManager中
wm.addView(decor, l);
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(
TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
虽然写到此处还是不能给出Activity中WindowManager,Window,View三者之间的关系下结论,但是基本的简单的关系还是可以很清晰的展现:
1)调用activity中的setContentView。
2)调用Window也就是phoneWindow的setContentView将View添加到DecorView中,确切地说是添加到DecorView的子view :mContentParent中,该view是个ViewGroup.这也是为什么Activity中叫setContentView而不是叫setView的原因。
3)在ActivityThread的handleResumeActivity方法中将DecorView添加到WindowManager中。
至于深处的关联以及ActivityThread这涉及到Activity的启动过程,目前水平很菜还不知道,慢慢研究吧。
这篇博客写到这就结束,没写什么实质性有价值的东东,写这篇博客的时候深切体会到了知识之不足,往往想往深处写点什么但是不知道从何下手的感觉,自己对android的某些知识点理解的还是太肤浅,可以说是革命尚未成功,自己仍需努力。虽然自己的博客没别人博客写的有深度,但是自己还是不会放弃写博客,写博客的好处只有自己动手写了才知道收获是多么巨大。
进步,总是一步步走过来的,编程和学习编程切忌急于求成