目录介绍
-
01.全体概述
- 1.1 项目布景
- 1.2 遇到问题
- 1.3 根底概念
- 1.4 规划方针
- 1.5 收益剖析
-
02.Window概念
- 2.1 Window增加View
- 2.2 Window的概念
- 2.3 LayoutParams
- 2.4 WMS流程整理
-
03.悬浮窗技能关键
- 3.1 事务思考点剖析
- 3.2 关键技能关键
- 3.3 运用悬浮窗
- 3.4 增加浮窗源码流程
- 3.5 理解WMS原理
- 3.6 拖拽回弹吸附
-
04.开发重要进程
- 4.1 悬浮窗完成流程
- 4.2 恳求悬浮窗权限
- 4.3 初始化悬浮窗
- 4.4 设置悬浮窗参数
- 4.5 增加View到悬浮窗
- 4.6 悬浮窗拖拽完成
- 4.8 悬浮窗权限适配
- 4.9 LayoutParam坑
-
05.计划根底规划
- 5.1 全体架构图
- 5.2 UML规划图
- 5.3 关键流程图
- 5.4 接口规划图
- 5.5 模块间依靠联系
-
06.其他规划阐明
- 6.1 功用规划
- 6.2 稳定性规划
- 6.3 反常规划
- 6.4 事情上报规划
-
07.遇到的问题和坑
- 7.1 处理输入法层级联系
- 7.2 鸿沟逻辑封闭悬浮窗
- 7.3 点击多次翻开页面
- 7.4 Home键遇到的问题
01.全体概述
1.1 项目布景
-
事务场景剖析
- 以视频通话为例,在视频通话时,咱们翻开其他运用或点击Home键退出时或点击缩放图标,悬浮窗会显现在其他运用之上,给人的假象是通话页面变小了,点击悬浮窗回到经过页面,悬浮窗消失。退出通话页面悬浮窗消失。
-
市面上常见的悬浮窗,如微信视频通话功用,有如下特点:
- 整屏页面能切换到一个小的悬浮窗;悬浮窗能运转在其他app上方;悬浮窗能跳回整屏页面,而且悬浮窗消失
-
需求悬浮窗作用
- 点击缩小按钮,将当时远端视屏加载进悬浮窗,且悬浮窗可拖拽,不影响其他界面焦点;点击悬浮窗可回来本来的Activity
1.2 遇到问题
-
什么是悬浮窗
- 大局悬浮窗在许多运用中都能见到,点击Home键,小窗口依然会在屏幕上显现。留意:悬浮窗留意恳求权限!
-
那么开发大局悬浮窗归于那一类呢?
- 归于体系窗口,适当于跟Toast是一个等级的。针对悬浮窗的展现和移除,则能够仿照Toast中addView和removeView操作……
-
视频通话Activity怎么最小化
- Activity自身自带了一个moveTaskToBack(boolean nonRoot),咱们要完成最小化只需求调用moveTaskToBack(true)传入一个true值就能够了,但是这儿有一个条件,便是需求设置Activity的发动形式为singleInstance形式,两步搞定。
- 注:activity最小化后从头从后台回到前台会回调onRestart()办法。点击悬浮窗敞开activity会回调onNewIntent(留意能够setIntent(intent)一下)
1.3 根底概念
-
Window 有三种类型,分别是运用 Window、子 Window 和体系 Window。
- 运用Window:z-index在1~99之间,它往往对应着一个Activity。
- 子Window:z-index在1000~1999之间,它往往不能独立存在,需求依附在父Window上,例如Dialog等。
- 体系Window:z-index在2000~2999之间,它往往需求声明权限才干创立,例如Toast、状态栏、体系音量条、过错提示框都是体系Window。
-
这些层级规模对应着 WindowManager.LayoutParams 的 type 参数
- 假如想要 Window 位于一切 Window 的最顶层,那么选用较大的层级即可,很明显体系 Window 的层级是最大的。
-
Android显现体系分为3层
- UI结构层:担任办理窗口中View组件的布局与制作以及响运用户输入事情
- WindowManagerService层:担任办理窗口Surface的布局与次第
- SurfaceFlinger层:将WindowManagerService办理的窗口依照一定的次第显现在屏幕上
-
WMS(WindowManagerService)相关概念
- Window:它是一个笼统类,详细完成类为 PhoneWindow ,它对 View 进行办理。Window是View的容器,View是Window的详细表现内容;
- WindowManager:是一个接口类,继承自接口 ViewManager ,从它的名称就知道它是用来办理 Window 的,它的完成类为 WindowManagerImpl;
- WMS:是窗口的办理者,它担任窗口的发动、增加和删除。另外窗口的巨细和层级也是由它进行办理的;
1.4 规划方针
-
目前开发悬浮窗的计划有以下几种
- 第一种:写在base里边或许监听一切activity生命周期,这样每次发动一个新的Activity都要往页面上addView一次,耦合性比较强。
- 第二种:选用在Window上增加View的形式,适当所以大局性的悬浮窗。封装成库,露出Api给开发者调用。
- 第三种:选用服务Service,然后在Service中选用WindowManager增加和移除View操作。那么在Activity中想要展现弹窗则需求经过广播通讯,让Service收到广播处理逻辑。移植性比较弱!
-
悬浮窗规划方针
- 良好的接口规划,能够设置各种自定义视图,支撑拖动和拖拽吸附到边际。强壮的Api办法和傻瓜式调用链路。
-
展现悬浮窗能否想Popup那样依附在某控件方位
- 我在写悬浮窗库时,思考能否想Popup那种有showAsDropDown办法Api,能够显现在某个View的重心方位,然后在设置x和y偏移量。这个是能够做到的,加上这个Api便利库的强壮运用!
1.5 收益剖析
-
悬浮窗收益
- 进步产品的用户体会,app推到后台,或许推出页面做其他操作(比方检查信息),这个时分浮窗功用首要是增加通话的友爱
-
技能收益
- 下沉为功用根底库,能够便利各个产品线运用,进步开发的效率。防止跟事务解耦合。运用场景有:音视频,直播,debug悬浮东西等……
-
悬浮窗库代码
- github.com/yangchong21…
02.Window概念
2.1 Window增加View
-
先看一个简略的案例。在主屏幕上增加一个TextView并展现,而且这个TextView独占一个窗口。
TextView mview = new TextView(context); WindowManager mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); WindowManager.LayoutParams wmParams = new WindowManager.LayoutParams(); wmParams.type = WindowManager.LayoutParams.TYPE_TOAST; wmParams.width = 800; wmParams.height = 800; mWindowManager.addView(mview, wmParams);
-
对Window增加View的流程进程剖析
- WindowManager.addView增加窗口之前,TextView的onDraw不会被调用,也就说View有必要被增加到窗口中,才会被制作。只需恳求了依附窗口,View才会有能够制作的方针内存。
- 当APP经过WindowManagerService的代理向其增加窗口的时分,WindowManagerService除了自己进行登记整理,还需求向SurfaceFlinger服务恳求一块Surface画布,其实首要是画布背后所对应的一块内存,只需这一块内存恳求成功之后,APP端才有绘图的方针,而且这块内存是APP端同SurfaceFlinger服务端同享的,这就省去了绘图资源的复制。
- APP端是能够经过unLockCanvasAndPost直接同SurfaceFlinger通讯进行重绘的,便是说图形的制作同WMS没有联系,WMS仅仅担任窗口的办理,并不担任窗口的制作。
2.2 Window的概念
-
Window是个笼统类,PhoneWindow是Window仅有的完成类。PhoneWindow像是一个东西箱,封装了三种东西:
- DecorView、WindowManager.LayoutParams、WindowManager。
- 其间DecorView和WindowManager.LayoutParams担任窗口的静态特点,比方窗口的标题、布景、输入法形式、屏幕方向等等。WindowManager担任窗口的动态操作,比方窗口的增、删、改。
- Window笼统类对WindowManager.LayoutParams相关的特点(如:输入法形式、屏幕方向)都供给了详细的办法。而对DecorView相关的特点(如:标题、布景),只供给了笼统办法,这些笼统办法由PhoneWindow完成。
-
Window并不是真实地存在着的,而是以View的形式存在。
- Window自身就仅仅一个笼统的概念,而View是Window的表现形式。要想显现窗口,就有必要调用WindowManager.addView(View view, ViewGroup.LayoutParams params)。
- 参数view就代表着一个窗口。在Activity和Dialog的显现进程中都会调用到wm.addView(decor, l);所以Activity和Dialog的DecorView就代表着各自的窗口。
2.3 WindowManager
-
在了解WindowManager办理View完成之前,先了解下WindowManager相关类图以及Activity界面各层级显现联系;
2.4 LayoutParams
-
WindowManager.LayoutParams这个类用于供给悬浮窗所需的参数,其间有几个经常会用到的变量:
- type值用于确认悬浮窗的类型,一般设为2002,表明在一切运用程序之上,但在状态栏之下。
- flags值用于确认悬浮窗的行为,比方说不行聚集,非模态对话框等等,特点十分多,我们能够检查文档。
- gravity值用于确认悬浮窗的对齐方式,一般设为左上角对齐,这样当拖动悬浮窗的时分便利核算坐标。
- x值用于确认悬浮窗的方位,假如要横向移动悬浮窗,就需求改动这个值。
- y值用于确认悬浮窗的方位,假如要纵向移动悬浮窗,就需求改动这个值。
- width值用于指定悬浮窗的宽度。
- height值用于指定悬浮窗的高度。
-
那么这个里边怎么核算悬浮窗上下左右的方位呢?比方有个场景悬浮窗和音视频页面放大和缩小就需求拿到悬浮窗方位
- 普通View怎么拿到上下左右方位,能够选用sourceView.getGlobalVisibleRect(visibleRect),简略来说便是对方针view在父view映射,然后从屏幕左上角开端核算,然后保存到rect中。
- 悬浮窗View怎么拿到上下左右方位,left = layoutParams.x;top = y,right = x + layoutParams.width;bottom = y + layoutParams.height
03.悬浮窗技能关键
3.1 事务思考点剖析
-
针对窗口缩小或许悬浮窗需求考虑几个重要的点:
- 悬浮窗体的份额以及层级,层级要在statusBar之下且在activity之上,这样才干保证其不会被其他事务界面覆盖;
- 悬浮框显现后,内部的内容怎么无缝衔接继续显现;
3.2 关键技能关键
-
悬浮窗权限判别
- 这个需求留意针对不同的版别需求适配权限。留意网上说有什么办法能够绕过权限恳求,这个是不行能的事情。一起要留意,部分手机判别悬浮窗权限Api或许失效……
-
将view增加到悬浮窗上
- 运用addView将View增加在window上,同样的,WindowManager.LayoutParams.type能够设置View的层级,防止被其他事务界面所覆盖。
3.3 运用悬浮窗
-
运用内悬浮窗完成流程
- 1.获取WindowManager;2.创立悬浮View;3.设置悬浮View的拖拽事情;4.增加View到WindowManager中
-
对于运用悬浮窗来说,Android版别对其影响不大。
- Type为TYPE_APPLICATION:只需Activity建立了,就能够增加。
- Type为TYPE_APPLICATION_ATTACHED_DIALOG:需求在Activity获取焦点,而且用户可操作时才可增加。
3.4 增加浮窗源码流程
-
悬浮窗增加流程:
- -> WindowManager.addView 这个是调用ViewManager接口的addView办法增加视图
- -> WindowManagerImpl.addView 接着会调用详细完成类
- -> WindowManagerGlobal.addView 在这个办法中会找到核心的ViewRootImpl,这个Impl适当所以root根
- -> ViewRootImpl.setView 终究会调用setView将view设置出来,mWindowSession在创立ViewRootImpl目标时被实例化
- -> WindowSession.addToDisplay(AIDL进行IPC)
- -> WindowManagerService.addWindow()
- -> ViewRootImpl.setView
-
从 WindowManager 到 WMS 的详细流程如下所示:
-
这儿解说一下AIDL交互的流程逻辑
- 首要剖析是ViewRootImpl#setView()到WindowManagerService.addWindow()的这个进程,涉及到跨进程通讯。
- 1.ViewRootImpl#setView()进程。mWindowSession是IWindowSession目标。在创立ViewRootImpl目标时被实例化。
- 2.WindowManagerGlobal#getWindowSession()进程。getWindowManagerService()经过AIDL回来WindowManagerService实例。之后调用WindowManagerService#openSession()。
- 3.WindowManagerService#openSession()进程。回来一个Session目标。也便是说在ViewRootImpl#setView()中调用的是mWindowSession.addToDisplay,其实便是Session#addToDisplay()。
- 4.Session#addToDisplay()进程。mService是个WindowManagerService目标,也便是说终究调用的是WindowManagerService#addWindow()
- 5.WindowManagerService#addWindow()进程。mWindowMap是个Map实例,将WindowManager增加进WindowManagerService统一办理。至此,整个增加视图操作解析结束。
-
WindowManager.updateViewLayout()解析
- 和addView()进程相同,终究会进入到WindowManagerGlobal#updateViewLayout()。将传入的View设置参数之后,更新mRoot中View的参数。
-
WindowManager.removeView()解析
- 和上面进程相同,终究会进入到WindowManagerGlobal#removeView()。这个进程要略微麻烦点,首要调用root.die(),接着将View增加进mDyingViews。
- ViewRootImpl#die()中,参数immediate默许为false,也便是说这儿仅仅发送了一个what=MSG_DIE的空音讯。ViewRootHandler收到这条音讯会执行doDie()。
- 经过一圈效验终究仍是回到WindowManagerGlobal中移除View
3.6 拖拽回弹吸附
-
先看微信作用
- 当你拖动微信悬浮窗的时分,手指松开,这个时分悬浮窗回到边际,会有一个很友爱的动画过渡作用。而并非是改动方位那么生硬。
-
为何做该功用
- 拖拽回到边际,假如是直接调用updateLocation,那太生硬了。
-
怎么做友爱动画
- 这儿能够增加特点动画,给动画设置时刻,然后在动画执行获取坐标值。然后再更改方位,这样就比较连贯,作用更好一些。
04.开发重要进程
4.1 悬浮窗完成流程
-
运用内悬浮窗完成流程
- 第一个是获取WindowManager,然后设置相关params参数。留意装备参数的时分需求留意type
- 第二个是增加xml或许自定义view到windowManager上
- 第三个是处理拖拽更改view方位的监听逻辑,分别在down,move,up三个事情处理事务
- 第四个是吸附左面或许右边,大约的思路是判别手指抬起时分的点是在屏幕左面仍是右边
4.2 恳求悬浮窗权限
-
关于悬浮窗的权限
- 当API<18时,体系默许是有悬浮窗的权限,不需求去处理;
- 当API >= 23时,需求在AndroidManifest中恳求权限,为了防止用户手动在设置中撤销权限,需求在每次运用时check一下是否有悬浮窗权限存在;
Settings.canDrawOverlays(this)
- 当API > 25时,体系直接制止用户运用TYPE_TOAST创立悬浮窗。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
4.3 初始化悬浮窗
-
第一步:首要创立WindowManager
//创立WindowManager windowManager = (WindowManager)applicationContext.getSystemService(Context.WINDOW_SERVICE); layoutParams = new WindowManager.LayoutParams();
4.4 设置悬浮窗参数
-
第一步:创立LayoutParams
layoutParams = new WindowManager.LayoutParams();
-
第二步:LayoutParam设置
wmParams.type = WindowManager.LayoutParams.TYPE_TOAST; wmParams.width = 800; wmParams.height = 800; mWindowManager.addView(mview, wmParams);
4.5 增加View到悬浮窗
-
界面触发悬浮窗代码如下:
// 新建悬浮窗控件 View view = LayoutInflater.from(this).inflate(R.layout.float_window, null); view.setOnTouchListener(new FloatingOnTouchListener()); // 将悬浮窗控件增加到WindowManager windowManager.addView(view, layoutParams);
-
需求留意的是,在躲藏悬浮窗的时分,最好是移除一下,下次需求显现的时分再增加。
4.6 悬浮窗拖拽完成
-
怎么完成悬浮窗可随手指拖动?
- 思路十分简略,监听悬浮窗那个onTouchListener即可,在刚点击的ACTION_DOWN(手指按下)事情中记载当时的x,y方位,然后在每次移动(ACTION_MOVE事情)后获取到本次移动的方位,二者相减便是需求移动的方位,这是自定义view的最基本操作了。
-
怎么完成悬浮窗左右边的吸顶作用?
- 监听到手指抬起(UP事情)的动作后,判别当时方位是接近左面仍是右边,接近左面就以方位动画的方式平移到左面,接近右边就平移到右边。
4.8 悬浮窗权限适配
-
权限装备和恳求,这一块却是没什么坑
- 在当Android7.0以上的时分,需求在AndroidManifest.xml文件中声明SYSTEM_ALERT_WINDOW权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
4.9 LayoutParam坑
-
LayoutParam的坑!!!!
- WindowManager的addView办法有两个参数,一个是需求参加的控件目标View,另一个参数是WindowManager.LayoutParam目标。
- LayoutParam里的type变量。需求留意一个坑!!!!!!这个变量是用来指定窗口类型的。在设置这个变量时,需求对不同版别的Android体系进行适配。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } else { layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE; }
-
在Android 8.0之前,悬浮窗口设置能够为TYPE_PHONE,这种类型是用于供给用户交互操作的非运用窗口。
- 而Android 8.0对体系和API行为做了修正,包含运用SYSTEM_ALERT_WINDOW权限的运用无法再运用一下窗口类型来在其他运用和窗口上方显现提示窗口:
- 假如需求完成在其他运用和窗口上方显现提示窗口,那么有必要该为TYPE_APPLICATION_OVERLAY的新类型。
-
假如在Android 8.0以上版别依然运用TYPE_PHONE类型的悬浮窗口,则会出现如下反常信息:
android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@f8ec928 -- permission denied for window type 2002
05.计划根底规划
5.1 全体架构图
5.2 UML规划图
-
悬浮窗全体UML类图
06.其他规划阐明
6.1 功用规划
-
功用规划在该库中首要涉及两点
- 第一个假如是用在activity中,那么则需求留意内存走漏的问题,需求释放activity上下文的引用
- 第二个假如是用在大局,那么需求留意增加view防止重复增加(假如现已增加则首要要移除),然后毁掉的时分把FloatWindow各种特点设置成null整理
6.2 稳定性规划
-
怎么防止窗口移动,移动后松手的瞬间触发了点击事情
- 首要设置一个布尔符号值(接触移动符号),在手指按下去(ACTION_DOWN)的时分设置为false。
- 然后在移动(ACTION_MOVE)的时分,假如用户移动了手指,那么就阻拦本次接触事情,然后不让点击事情收效。
- 终究在手指抬起(ACTION_UP,ACTION_CANCEL)的时分,回来记载的接触移动符号。假如是true表明自己消费事情,则不会让点击事情收效。
-
这个当地需求留意两点
- 第一点:为何要监听ACTION_CANCEL事情,是由于手指拖动,快速拖动到窗口外,这个时分没有手指抬起操作,那么监听事情结束首要是增强鸿沟逻辑。
- 第二点:怎么判别滑动?由于点击click也会执行down,move,up等一连串事情。这个时分就要判别最小move间隔是否大于体系最小接触间隔,假如是则为拖动,否则是点击!
-
怎么处理滑出指定间隔又滑入当作是点击事情bug
- 这个这个,能够当作一种增强逻辑,但是但是手指操作不出来,先放着……
6.3 反常规划
-
针对悬浮窗的增加,移除和更新操作需求增加catch操作。那么为何要这样操作,仿照吐司。如下所示:
try { mWindowManager.addView(mDecorView, mWindowParams); } catch (NullPointerException | IllegalStateException | IllegalArgumentException | WindowManager.BadTokenException e) { // 假如这个 View 目标被重复增加到 WindowManager 则会抛出反常 // java.lang.IllegalStateException: View has already been added to the window manager. } //下面这个是更新view try { mWindowManager.updateViewLayout(mDecorView, mWindowParams); } catch (IllegalArgumentException e) { // 当 WindowManager 现已消失时调用会发生崩溃 // IllegalArgumentException: View not attached to window manager }
- 参阅体系等级的Toast,其实悬浮窗跟吐司相同,设置体系层级后,对addView增加catch操作。
try { mWM.addView(mView, mParams); } catch (WindowManager.BadTokenException e) { /* ignore */ }
6.4 事情上报规划
-
在悬浮窗中,有一部分代码增加上了catch操作。那么能否把这一部分的反常当作事情上报到APM上来
- 第一种计划:依靠APM,然后调用api进行事情上报,明显这种是不行行的。由于该功用库是不想依靠太大的外部库。
- 第二种计划:选用接口+完成类,经过反射的形式去调用。但这样又感觉不太好,选用Class.forName要防止混杂导致类找不到。
- 第三种计划:选用笼统类+完成类,将完成类的目标设置到笼统类中调用,完成类在壳工程做详细操作。
-
详细完成进程如下所示
- 举一个简略的比方阐明该思路,比方,我在悬浮窗依靠接口层,然后调用代码如下所示
ExceptionReporter.reportCrash("Float FloatWindow updateViewLayout", e);
-
然后,在app壳工程中详细操作如下所示
ExceptionReporter.setExceptionReporter(ExceptionHelperImpl()) public class ExceptionReporterImpl extends ExceptionReporter { @Override protected void reportCrash(Throwable throwable) { //壳工程中能够拿到APM,比方上传到bugly平台上 } @Override protected void reportCrash(String tag, Throwable throwable) { } }
07.遇到的问题和坑
7.1 处理输入法层级联系
-
先看一下问题
- 微信里的悬浮窗是在输入法之下的,所以交互的同学也要求悬浮窗也要在输入法之下。检查了一下WindowManager源码,悬浮窗的优先级TYPE_APPLICATION_OVERLAY,上面大字写着明明是在输入法之下的,但是实践表现是在输入法之上。
7.2 鸿沟逻辑封闭悬浮窗
-
先看一下问题
- 谷歌坑人的当地,都没当地设置这个悬浮窗是否只用到app内,所以默许在桌面上也会显现自己的悬浮窗。
- 比方在微信里显现其他app的悬浮窗,这种糟糕的体会可想而知,用户不给你卸载就真是奇迹了。
-
尝试处理这个问题
- 为了处理这个问题,开始的完成方式是对一切经过的activity进行记载,显现就加1,页面被挂起就减1,假如减到当时计数为0时阐明一切页面现已封闭了,就能够躲藏悬浮窗了。
- 实践上这么做仍是有问题的,在部分手机上假如是在首页按回来键的话依然不能躲藏,这个又是体系级的兼容性问题。
- 为了处理这问题,后边又做了一个处理,经过注册registerActivityLifecycleCallbacks监听app的前后台回调,检测到假如当时首页被毁掉时,应该将悬浮窗进行躲藏。
7.3 点击多次翻开页面
-
问题阐明一下
- 假如你的悬浮窗点击事情是翻开页面的话,这儿需求留意了,别忘了将这个翻开的页面的发动形式设置为singleTop或许是singleTask,然后复用同一个,远离一直按回来的阴间操作。
7.4 Home键遇到的问题
-
先说一下遇到问题的场景
- 按home退到桌面从桌面点击运用图标又从发动页从头发动的,挺奇怪的。点击home键按道理说是不会推出MainActivity的呀
-
先说下代码逻辑
- 语音/视频通话界面activity 装备 android:launchMode=“singleInstance” 形式,切换到悬浮框调用 moveTaskToBack(true)办法,能发动小窗口,通话页面退到后台。
-
调试中发现的问题
- 通话界面按home键,之前的activity毁掉了,日志发现走了onDestroy,从头点击app图标,MainActivity相关页面从头onCreate(适当于从头发动app了)。
- 由于通话页面是singleInstance形式,此时有两个使命栈,按Home键后再从使命程序中切回,此时运用只保留了第二个使命栈,现已失去了和第一个使命栈的联系,finish之后无法在回到第一个使命栈。
-
该问题处理计划
- 给通话界面设置taskAffinity,假如不设置的话,按下home键时体系会整理最近不活动的和application相同的taskAffinity的一切处于后台的栈,taskAffinity默许与application是同一个。
- 给通话页面设置taskAffinity之后,MainActivity所在后台栈就不会被整理。需求留意:若想在taskAffinity特点收效,需求在发动该Activity时设置Flag为FLAG_ACTIVITY_NEW_TASK。