Android源码解析Window系列第(一)篇---Window的基本认识和Activity的加载流程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android源码解析Window系列第(一)篇---Window的基本认识和Activity的加载流程相关的知识,希望对你有一定的参考价值。
参考技术A您可能听说过View ,ViewManager,Window,PhoneWindow,WindowManager,WindowManagerService,可是你知道这几个类是什么关系,干嘛用的。概括的来说,View是放在Window中的,Window是一个抽象类,它的具体实现是PhoneWindow,PhoneWindow还有个内部类DecorView,WindowManager是一个interface,继承自ViewManager,它是外界访问Window的入口,,提供了add/remove/updata的方法操作View,WindowManager与WindowManagerSerice是个跨进程的过程,WindowManagerService的职责是对系统中的所有窗口进行管理。如果您不太清楚,建议往下看,否则就不要看了。
android系统的Window有很多种,大体上来说,Framework定义了三种窗口类型;
这就是Framework定义了三种窗口类型,这三种类型定义在WindowManager的内部类LayoutParams中,WindowManager讲这三种类型 进行了细化,把每一种类型都用一个int常量来表示,这些常量代表窗口所在的层,WindowManagerService在进行窗口叠加的时候,会按照常量的大小分配不同的层,常量值越大,代表位置越靠上面, 所以我们可以猜想一下,应用程序Window的层值常量要小于子Window的层值常量,子Window的层值常量要小于系统Window的层值常量。 Window的层级关系如下所示。
上面说了Window分为三种,用Window的type区分,在搞清楚Window的创建之前,我们需要知道怎么去描述一个Window,我们就把Window当做一个实体类,给我的感觉,它必须要下面几个字段。
实际上WindowManager.LayoutParams对Window有很详细的定义。
提取几个重要的参数
Window是一个是一个抽象的概念,千万不要认为我们所看到的就是Window,我们平时所看到的是视图,每一个Window都对应着一个View,View和Window通过ViewRootImpl来建立联系。有了View,Window的存在意义在哪里呢,因为View不能单独存在,它必须依附着Window,所以有视图的地方就有Window,比如Activity,一个Dialog,一个PopWindow,一个菜单,一个Toast等等。
通过上面我们知道视图和Window的关系,那么有一个问题,是先有视图,还是先有Window。这个答案只有在源码中找了。应用程序的入口类是ActivityThread,在ActivityThread中有performLaunchActivity来启动Activity,这个performLaunchActivity方法内部会创建一个Activity。
如果activity不为null,就会调用attach,在attach方法中通过PolicyManager创建了Window对象,并且给Window设置了回调接口。
PolicyManager的实现类是Policy
这样Window就创建出来了, 所以先有Window,后有视图,视图依赖Window存在 ,再说一说视图(Activity)为Window设置的回调接口。
Activity实现了这个回调接口,当Window的状态发生变化的时候,就会回调Activity中实现的这些接口,有些回调接口我们还是熟悉的,dispatchTouchEvent,onAttachedToWindow,onDetachedFromWindow等。
下面分析view是如何附属到window上的,通过上面可以看到,在attach之后就要执行callActivityOnCreate,在onCreate中我们会调用setContentView方法。
getWindow获取了Window对象,Window的具体实现类是PhoneWindow,所以要看PhoneWindow的setContentView方法。
这里涉及到一个mContentParent变量,他是一个DecorView的一部分,DecorView是PhoneWindow的一个内部类,我先介绍一下关于DecorView的知识。
DecorView是Activity的顶级VIew,DecorView继承自FrameLayout,在DecorView中有上下两个部分,上面是标题栏,下面是内容栏,我们通过PhoneWindow的setContentView所设置的布局文件是加到内容栏(mContentParent)里面的,View层的事件都是先经过DecorView在传递给我们的View的。
OK在回到setContentView的源码分析,我们可以得到Activity的Window创建需要三步。
- 1、 如果没有DecorView,在installDecor中创建DecorView。
- 2、将View添加到decorview中的mContentParent中。
- 3、回调Activity的onContentChanged接口。
先看看第一步,installDecor的源码
installDecor中调用了generateDecor,继续看
直接给new一个DecorView,有了DecorView之后,就可以加载具体的布局文件到DecorView中了,具体的布局文件和系统和主题有关系。
在看第二步,将View添加到decorview中的mContentParent中。
直接将Activity视图加到DecorView的mContentParent中,最后一步,回调Activity的onContentChanged接口。在Activity中寻找onContentChanged方法,它是个空实现,我们可以在子Activity中处理。
到此DecorView被创建完毕,我们一开始从Thread中的handleLaunchActivity方法开始分析,首先加载Activity的字节码文件,利用反射的方式创建一个Activity对象,调用Activity对象的attach方法,在attach方法中,创建系统需要的Window并为设置回调,这个回调定义在Window之中,由Activity实现,当Window的状态发生变化的时候,就会回调Activity实现的这些回调方法。调用attach方法之后,Window被创建完成,这时候需要关联我们的视图,在handleLaunchActivity中的attach执行之后就要执行handleLaunchActivity中的callActivityOnCreate,在onCreate中我们会调用setContentView方法。通过setContentView,创建了Activity的顶级View---DecorView,DecorView的内容栏(mContentParent)用来显示我们的布局。 这个是我们上面分析得到了一个大致流程,走到这里,这只是添加的过程,还要有一个显示的过程,显示的过程就要调用handleLaunchActivity中的handleResumeActivity方法了。最后会调用makeVisible方法。
这里面首先拿到WindowManager对象,用tWindowManager 的父接口ViewManager接收,ViewManager可以
最后调用 mDecor.setVisibility(View.VISIBLE)设置mDecor可见。到此,我们终于明白一个Activity是怎么显示在我们的面前了。
参考链接:
http://blog.csdn.net/feiduclear_up/article/details/49201357
Android悬浮窗原理解析(Window)[源码]
悬浮窗,在大多数应用中还是很少见的,目前我们接触到的悬浮窗,差不多都是一些系统级的应用软件,例如:360安全卫士,腾讯手机管家等;在某些服务行业如金融,餐饮等,也会在应用中添加悬浮窗,例如:美团的偷红包,博闻金融快捷联系等。但两种悬浮窗还是有区别的:
- 系统悬浮窗:所有界面都会展示,包括主屏、锁屏
- 应用悬浮窗:只在应用Activity中展示。
一、窗口Window
在了解悬浮窗之前,首先我们需要认识一下Android窗口Window。Android Framework将窗口分为三个类型:
- 应用窗口:所谓应用窗口指的就是该窗口对应一个Activity,因此,要创建应用窗口就必须在Activity中完成了。
- 子窗口:所谓子窗口指的是必须依附在某个父窗口之上,比如PopWindow,Dialog。
- 系统窗口:所谓系统窗口指的是由系统进程创建,不依赖于任何应用或者不依附在任何父窗口之上,如:Toast,来电窗口等。
Framework定义了三种窗口类型,这三种类型定义在WindowManager的内部类LayoutParams中,WindowManager将这三种类型 进行了细化,把每一种类型都用一个int常量来表示,这些常量代表窗口所在的层,WindowManagerService在进行窗口叠加的时候,会按照常量的大小分配不同的层,常量值越大,代表位置越靠上面,所以我们可以猜想一下,应用程序Window的层值常量要小于子Window的层值常量,子Window的层值常量要小于系统Window的层值常量。Window的层级关系如下所示。
- 应用窗口:层级范围是1~99
- 子窗口:层级范围是1000~1999
- 系统窗口:层级范围是2000~2999
1.各级别type值在WindowManager中的定义分别为:
i.应用窗口(1~99)
//第一个应用窗口
public static final int FIRST_APPLICATION_WINDOW = 1;
//所有程序窗口的base窗口,其他应用程序窗口都显示在它上面
public static final int TYPE_BASE_APPLICATION = 1;
//所有Activity的窗口,只能配合Activity在当前APP使用
public static final int TYPE_APPLICATION = 2;
//目标应用窗口未启动之前的那个窗口
public static final int TYPE_APPLICATION_STARTING = 3;
//最后一个应用窗口
public static final int LAST_APPLICATION_WINDOW = 99;
ii.子窗口(1000~1999)
//第一个子窗口
public static final int FIRST_SUB_WINDOW = 1000;
// 面板窗口,显示于宿主窗口的上层,只能配合Activity在当前APP使用
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
// 媒体窗口(例如视频),显示于宿主窗口下层
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1;
// 应用程序窗口的子面板,只能配合Activity在当前APP使用(PopupWindow默认就是这个Type)
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
//对话框窗口,只能配合Activity在当前APP使用
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
//
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4;
//最后一个子窗口
public static final int LAST_SUB_WINDOW = 1999;
iii.系统窗口(2000~2999)
//系统窗口,非应用程序创建
public static final int FIRST_SYSTEM_WINDOW = 2000;
//状态栏,只能有一个状态栏,位于屏幕顶端,其他窗口都位于它下方
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
//搜索栏,只能有一个搜索栏,位于屏幕上方
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
//
//电话窗口,它用于电话交互(特别是呼入),置于所有应用程序之上,状态栏之下,属于悬浮窗(并且给一个Activity的话按下HOME键会出现看不到桌面上的图标异常情况)
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
//
//系统警告提示窗口,出现在应用程序窗口之上,属于悬浮窗, 但是会被禁止
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
//
//信息窗口,用于显示Toast, 不属于悬浮窗, 但有悬浮窗的功能, 缺点是在Android2.3上无法接收点击事件
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
//
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
//锁屏窗口
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
//系统顶层窗口,显示在其他一切内容之上,此窗口不能获得输入焦点,否则影响锁屏
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
//电话优先,当锁屏时显示,此窗口不能获得输入焦点,否则影响锁屏
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
//系统对话框窗口
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
//锁屏时显示的对话框
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;
//系统内部错误提示,显示在任何窗口之上
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
//内部输入法窗口,显示于普通UI之上,应用程序可重新布局以免被此窗口覆盖
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
//内部输入法对话框,显示于当前输入法窗口之上
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
//墙纸窗口
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
//状态栏的滑动面板
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
//安全系统覆盖窗口,这些窗户必须不带输入焦点,否则会干扰键盘
public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
//最后一个系统窗口
public static final int LAST_SYSTEM_WINDOW = 2999;
2.窗口flags显示属性在WindowManager中也有定义:
//窗口特征标记
public int flags;
//当该window对用户可见的时候,允许锁屏
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
//窗口后面的所有内容都变暗
public static final int FLAG_DIM_BEHIND = 0x00000002;
//Flag:窗口后面的所有内容都变模糊
public static final int FLAG_BLUR_BEHIND = 0x00000004;
//窗口不能获得焦点
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
//窗口不接受触摸屏事件
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
//即使在该window在可获得焦点情况下,允许该窗口之外的点击事件传递到当前窗口后面的的窗口去
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
//当手机处于睡眠状态时,如果屏幕被按下,那么该window将第一个收到触摸事件
public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
//当该window对用户可见时,屏幕出于常亮状态
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
//:让window占满整个手机屏幕,不留任何边界
public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;
//允许窗口超出整个手机屏幕
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
//window全屏显示
public static final int FLAG_FULLSCREEN = 0x00000400;
//恢复window非全屏显示
public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
//开启窗口抖动
public static final int FLAG_DITHER = 0x00001000;
//安全内容窗口,该窗口显示时不允许截屏
public static final int FLAG_SECURE = 0x00002000;
//锁屏时显示该窗口
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
//系统的墙纸显示在该窗口之后
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
//当window被显示的时候,系统将把它当做一个用户活动事件,以点亮手机屏幕
public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
//该窗口显示,消失键盘
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
//当该window在可以接受触摸屏情况下,让因在该window之外,而发送到后面的window的触摸屏可以支持split touch
public static final int FLAG_SPLIT_TOUCH = 0x00800000;
//对该window进行硬件加速,该flag必须在Activity或Dialog的Content View之前进行设置
public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
//让window占满整个手机屏幕,不留任何边界
public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
//透明状态栏
public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
//透明导航栏
3.添加View到Window的流程代码:
contactView = new ContactView(mContext);
if (layoutParams == null) www.yongshiyule178.com {
layoutParams = new WindowManager.LayoutParams();
layoutParams.width = contactView.width;
layoutParams.height = contactView.height;
layoutParams.x += ScreenSizeUtil.getScreenWidth(www.078881.cn );
layoutParams.y += ScreenSizeUtil.getScreenHeight(www.345608.cn/) - ScreenSizeUtil.dp2px(150);
layoutParams.gravity = www.taohuayuan178.com Gravity.TOP | Gravity.LEFT;
if (Build.VERSION.SDK_INT > 18 && Build.VERSION.SDK_INT < www.feifanyule.cn 23) {
layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;//Type设置
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
}
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//Flags设置,窗口属性
layoutParams.format = PixelFormat.RGBA_8888;
悬浮窗添加原理:View添加到Window源码解析
Window在Android系统中十分重要,其Activity,Dialog的创建都离不开Window.具体Activity,Dialog是怎么添加到Window上的,详情如:
Activity的加载流程;
Dialog加载绘制流程
二、两种悬浮窗
在前言中已说到,悬浮窗在app中,分为两种:系统级别和应用级别。其中系统级别可以在任何界面展示,包括主屏、锁屏(看需要),应用级别只在应用中展示。通过上面对Window的认识,我们可以知道实现方式:
- 系统级别可以通过type设置为:TYPE_TOAST、TYPE_PHONE、TYPE_SYSTEM_ALERT;
- 应用级别可以通过type设置为:TYPE_APPLICATION、TYPE_APPLICATION_ATTACHED_DIALOG;
悬浮窗添加流程:
WindowManager.addView -> ViewRootImpl.setView -> WindowSession.addToDisplay(AIDL进行IPC) -> WindowManagerService.addWindow() -> ViewRootImpl.setView
1.系统悬浮窗
对于系统级别的悬浮窗来说,不同的设置,不同的Android版本,需要权限和交互都不同。通过阅读源码android4.4知:
android 7.1.1的addWindow方法为:
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, InputChannel outInputChannel) {
int[] appOp = new int[1];
int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
......
final int type = attrs.type;
......
} else if (type == TYPE_TOAST) {//android 7.1.1 添加代码(其他版本无)
// Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
callingUid, attachedWindow);
if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
Slog.w(TAG_WM, "Attempted to add a toast window with bad token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}....
synchronized(mWindowMap) {
......
mPolicy.adjustWindowParamsLw(win.mAttrs);
权限检查checkAddPermission(www.tiaotiaoylzc.com)方法:
//权限检查
@Override
public int checkAddPermission(WindowManager.www.chaoyueyule.com LayoutParams attrs, int[] outAppOp) {
int type = attrs.type;
outAppOp[0] = AppOpsManager.OP_NONE;
if (type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
|| type > WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
return WindowManagerGlobal.ADD_OKAY;
}
String permission = null;
switch (type) {
case TYPE_TOAST:
// XXX right now the app process has complete control over
// this... should introduce www.00534.cn a token to let the system
// monitor/control what they are doing.
break;
case TYPE_DREAM:
case TYPE_INPUT_METHOD:
case TYPE_WALLPAPER:
case TYPE_PRIVATE_PRESENTATION:
// The window manager www.dongfan178.com will check these.
break;
case TYPE_PHONE:
case TYPE_PRIORITY_PHONE:
case TYPE_SYSTEM_ALERT:
case TYPE_SYSTEM_ERROR:
case TYPE_SYSTEM_OVERLAY:
permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW;
outAppOp[0] = AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
break;
default:
permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
}
if (permission != null) {
if (mContext.checkCallingOrSelfPermission(permission)
!= PackageManager.PERMISSION_GRANTED) {
return WindowManagerGlobal.ADD_PERMISSION_DENIED;
}
}
return WindowManagerGlobal.ADD_OKAY;
window的flags属性添加adjustWindowParamsLw()方法
//window的flags属性添加
//Android 2.0 - 2.3.7 PhoneWindowManager
public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
switch (attrs.type) {
case TYPE_SYSTEM_OVERLAY:
case TYPE_SECURE_SYSTEM_OVERLAY:
case TYPE_TOAST:
// These types of windows can‘t receive input events.
attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
break;
}
}
//Android 4.0.1 - 4.3.1 PhoneWindowManager
public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
switch (attrs.type) {
case TYPE_SYSTEM_OVERLAY:
case TYPE_SECURE_SYSTEM_OVERLAY:
case TYPE_TOAST:
// These types of windows can‘t receive input events.
attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
break;
}
}
//Android 4.4 PhoneWindowManager
@Override
public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
switch (attrs.type) {
case TYPE_SYSTEM_OVERLAY:
case TYPE_SECURE_SYSTEM_OVERLAY:
// These types of windows can‘t receive input events.
attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
由上可知:
- Type为TYPE_TOAST:版本低于android4.4的,不能接受触摸事件,无法操作;版本高于Android 7.1.1的无法添加悬浮窗
- Type为TYPE_PHONE:所有android版本都需要权限,版本低于android6.0的manifest中添加权限android.Manifest.permission.SYSTEM_ALERT_WINDOW即可,高于andorid6.0的需要判断权限且手动添加。
- Type为TYPE_SYSTEM_ALERT:同TYPE_PHONE
所以系统级别的悬浮窗,android不同版本需要特别处理。
2.应用悬浮窗
对于应用悬浮窗来说,android版本对其影响不大。
- Type为TYPE_APPLICATION:只要Activity建立了,就可以添加。
- Type为TYPE_APPLICATION_ATTACHED_DIALOG:需要在Activity获取焦点,并且用户可操作时才可添加。
三、悬浮窗的实现
悬浮窗添加比较简单,主要是由WindowManager接口的实现类WindowManagerImpl进行操作,WindowManager接口又继承至ViewManager,其中主要方法为:
public void addView(View view, ViewGroup.LayoutParams params);//添加View到Window
public void updateViewLayout(View view, ViewGroup.LayoutParams params);//更新View在Window中的位置
public void removeView(View view);//删除View
- 1
- 2
- 3
主要实现代码:
public class ContactWindowUtil {
private ContactView contactView;
private View dialogView;
private WindowManager windowManager;
private WindowManager.LayoutParams layoutParams;
private WindowManager.LayoutParams dialogParams;
private Context mContext;
private ContactWindowListener mListener;
private ValueAnimator valueAnimator;
private int direction;
private final int LEFT = 0;
private final int RIGHT = 1;
public interface ContactWindowListener {
void onDataCallBack(String str);
}
public void setDialogListener(ContactWindowListener listener) {
mListener = listener;
}
//私有化构造函数
public ContactWindowUtil(Context context) {
mContext = context;
windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
// windowManager = (WindowManager) FloatWindowApp.getAppContext().getSystemService(Context.WINDOW_SERVICE);
}
public void showContactView() {
hideContactView();
contactView = new ContactView(mContext);
if (layoutParams == null) {
layoutParams = new WindowManager.LayoutParams();
layoutParams.width = contactView.width;
layoutParams.height = contactView.height;
layoutParams.x += ScreenSizeUtil.getScreenWidth();
layoutParams.y += ScreenSizeUtil.getScreenHeight() - ScreenSizeUtil.dp2px(150);
layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
if (Build.VERSION.SDK_INT > 18 && Build.VERSION.SDK_INT < 23) {
layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
}
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
layoutParams.format = PixelFormat.RGBA_8888;
}
contactView.setOnTouchListener(touchListener);
contactView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showContactDialog();
}
});
windowManager.addView(contactView, layoutParams);
}
/**
* 显示联系弹框
*/
private void showContactDialog() {
hideDialogView();
if (dialogParams == null) {
dialogParams = new WindowManager.LayoutParams();
dialogParams.width = WindowManager.LayoutParams.MATCH_PARENT;
dialogParams.height = WindowManager.LayoutParams.MATCH_PARENT;
dialogParams.gravity = Gravity.CENTER;
if (Build.VERSION.SDK_INT > 18 && Build.VERSION.SDK_INT < 25){
dialogParams.type = WindowManager.LayoutParams.TYPE_TOAST;
} else {
dialogParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
}
dialogParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_FULLSCREEN;
dialogParams.format = PixelFormat.RGBA_8888;
}
LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
dialogView = layoutInflater.inflate(R.layout.window_contact, null);
TextView okTv = dialogView.findViewById(R.id.mOkTv);
TextView cancleTv = dialogView.findViewById(R.id.mCancleTv);
final TextView contentTv = dialogView.findViewById(R.id.mContentTv);
contentTv.setText(String.format("您确认拨打%s客服电话吗", "4008-111-222"));
cancleTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
hideDialogView();
}
});
okTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
hideDialogView();
mListener.onDataCallBack("");
}
});
dialogView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
hideDialogView();
}
});
windowManager.addView(dialogView, dialogParams);
}
public void hideAllView() {
hideContactView();
hideDialogView();
}
public void hideContactView() {
if (contactView != null) {
windowManager.removeView(contactView);
contactView = null;
stopAnim();
}
}
public void hideDialogView() {
if (dialogView != null) {
windowManager.removeView(dialogView);
dialogView = null;
}
}
View.OnTouchListener touchListener = new View.OnTouchListener() {
float startX;
float startY;
float moveX;
float moveY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getRawX();
startY = event.getRawY();
moveX = event.getRawX();
moveY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float x = event.getRawX() - moveX;
float y = event.getRawY() - moveY;
//计算偏移量,刷新视图
layoutParams.x += x;
layoutParams.y += y;
windowManager.updateViewLayout(contactView, layoutParams);
moveX = event.getRawX();
moveY = event.getRawY();
break;
case MotionEvent.ACTION_UP:
//判断松手时View的横坐标是靠近屏幕哪一侧,将View移动到依靠屏幕
float endX = event.getRawX();
float endY = event.getRawY();
if (endX < ScreenSizeUtil.getScreenWidth() / 2) {
direction = LEFT;
endX = 0;
} else {
direction = RIGHT;
endX = ScreenSizeUtil.getScreenWidth() - contactView.width;
}
if(moveX != startX){
starAnim((int) moveX, (int) endX,direction);
}
//如果初始落点与松手落点的坐标差值超过5个像素,则拦截该点击事件
//否则继续传递,将事件交给OnClickListener函数处理
if (Math.abs(startX - moveX) > 5) {
return true;
}
break;
}
return false;
}
};
private void starAnim(int startX, int endX,final int direction) {
if (valueAnimator != null) {
valueAnimator.cancel();
valueAnimator = null;
}
valueAnimator = ValueAnimator.ofInt(startX, endX);
valueAnimator.setDuration(500);
valueAnimator.setRepeatCount(0);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if(direction == LEFT){
layoutParams.x = (int) animation.getAnimatedValue()-contactView.width/2;
}else{
layoutParams.x = (int) animation.getAnimatedValue();
}
if (contactView != null) {
windowManager.updateViewLayout(contactView, layoutParams);
}
}
});
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.start();
}
private void stopAnim() {
if (valueAnimator != null) {
valueAnimator.cancel();
valueAnimator = null;
悬浮窗源码:https://github.com/awenzeng/FloatWindowDemo
四、注意
1.应用悬浮窗WindowManager的获取环境必须是Activity环境,系统悬浮窗可以Activity环境,也可以是全局的环境。如:
windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
// windowManager = (WindowManager) FloatWindowApp.getAppContext().getSystemService(Context.WINDOW_SERVICE);
- 1
- 2
2.应用级别悬浮窗也可以通过系统级别的方式实现,主要控制一下显示与隐藏就好。但如果只在应用中展示悬浮窗,建议使用应用级别,那样会省去许多不必要的麻烦。
3.系统级别Type为TYPE_PHONE、TYPE_SYSTEM_ALERT是权限判断及设置代码:
/**
* 请求用户给予悬浮窗的权限
*/
public void askForPermission() {
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(TestFloatWinActivity.this, "当前无权限,请授权!", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
} else {
startService(floatWinIntent);
}
}
以上是关于Android源码解析Window系列第(一)篇---Window的基本认识和Activity的加载流程的主要内容,如果未能解决你的问题,请参考以下文章
Android源码解析RPC系列(一)---Binder原理