本文同步自我的個(gè)人小屋,歡迎來(lái)訪交流
事情是這樣的加叁,目前在做一個(gè)醫(yī)療項(xiàng)目循集,需要定時(shí)在某個(gè)時(shí)間段比如午休時(shí)間和晚上讓我們的App休眠扫尺,那么這個(gè)時(shí)候在休眠時(shí)間段如果用戶按了電源鍵點(diǎn)亮屏幕了,我們就需要彈出一個(gè)全屏的窗口去做一個(gè)人性化的提示零远,“當(dāng)前時(shí)間是休眠時(shí)間苗分,請(qǐng)稍安勿躁...blabla”這樣子。
很顯然牵辣,我們需要一個(gè)BroadcastReceiver來(lái)監(jiān)聽(tīng)系統(tǒng)的鎖屏摔癣,亮屏,用戶的解鎖纬向,息屏行為择浊,在收到亮屏廣播的時(shí)候彈窗。那么如果是你逾条,會(huì)選擇怎么樣的方式去實(shí)現(xiàn)呢琢岩?
兩種方案:
- Dialog彈窗,全屏
- 啟動(dòng)一個(gè)Activity
一. Dialog
這里省去我們項(xiàng)目里面的代碼师脂,以簡(jiǎn)單常用的AlertDialog為例
正常彈出AlertDialog的流程如下:
new AlertDialog.Builder(context).setTitle("在BroadcastReceiver里彈出AlertDialog").show();
但是其實(shí)Dialog似乎只能在activity中彈出担孔,至于為什么,網(wǎng)上已經(jīng)有很多相關(guān)文章了危彩。這里我隨手用百度Google了兩篇:
為了解決在BroadcastReceiver里彈出AlertDialog這個(gè)問(wèn)題攒磨,我們可以這樣做:
- 方案一
將Dialog的窗口類型設(shè)置為TYPE_SYSTEM_ALERT
AlertDialog alertDialog=new AlertDialog.Builder(context).create();
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
alertDialog.show();
需要注意的是,最后還要在androidManifest.xml文件中加入以下兩句話:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW"/>
事實(shí)上汤徽,如果你認(rèn)真看了我給出的度娘到的兩篇文章娩缰,你會(huì)發(fā)現(xiàn)這并不是一個(gè)很好的方案。
- 方案二
自定義Activity管理者或者說(shuō)容器吧谒府,通過(guò)它來(lái)獲取當(dāng)前界面的Activity作為Dialog的context
public class MyActivityManager {
private static MyActivityManager sInstance = new MyActivityManager();
private WeakReference<Activity> sCurrentActivityWeakRef;
private List<Activity> activityList = new LinkedList<Activity>();
private MyActivityManager() { }
public synchronized static MyActivityManager getInstance() {
return sInstance;
}
public Activity getCurrentActivity() {
Activity currentActivity = null;
if (sCurrentActivityWeakRef != null) {
currentActivity = sCurrentActivityWeakRef.get();
}
return currentActivity;
}
public void setCurrentActivity(Activity activity) {
sCurrentActivityWeakRef = new WeakReference<>(activity);
}
// add Activity
public void addActivity(Activity activity) {
if (!activityList.contains(activity))
activityList.add(activity);
}
// remove Activity
public void removeActivity(Activity activity) {
if (activityList.contains(activity))
activityList.remove(activity);
}
public void exitToHome() {
try {
for (Activity activity:activityList) {
if (activity != null) {
String className = activity.getClass().getSimpleName();
if (!className.equals("HomeActivity"))
activity.finish();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
//關(guān)閉每一個(gè)list內(nèi)的activity
public void finishActivityList() {
for (Activity activity : activityList) {
activity.finish();
}
}
}
在你的application里面
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
MyActivityManager.getInstance().addActivity(activity);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
MyActivityManager.getInstance().setCurrentActivity(activity);
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
MyActivityManager.getInstance().removeActivity(activity);
}
});
如寫(xiě)的鄙陋還請(qǐng)見(jiàn)諒, 當(dāng)然了類似的工具類在網(wǎng)上也有很多芦瘾。這里順便再提一下
給dialog設(shè)置全屏的最簡(jiǎn)單的方法 邮弹,在構(gòu)造函數(shù)中
super(context,android.R.style.Theme);
setOwnerActivity((Activity)context);
如果該Dialog設(shè)置了自定義style,則在其初始化完view后债蓝,設(shè)置layout寬高
getWindow().setLayout(屏幕寬,屏幕高);
二. Activity
直接上代碼:
Intent intent=new Intent(context,AnotherActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
注意一定要給Intent設(shè)置一個(gè)flag:FLAG_ACTIVITY_NEW_TASK
,不寫(xiě)的話會(huì)拋異常:
* 可捕獲異常信息:
* android.util.AndroidRuntimeException:
* Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.
* Is this really what you want?
Why 盛龄?
* 1 在普通情況下,必須要有前一個(gè)Activity的Context,才能啟動(dòng)后一個(gè)Activity
* 2 但是在BroadcastReceiver里面是沒(méi)有Activity的Context的
* 3 對(duì)于startActivity()方法,源碼中有這么一段描述:
* Note that if this method is being called from outside of an
* {@link android.app.Activity} Context, then the Intent must include
* the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag. This is because,
* without being started from an existing Activity, there is no existing
* task in which to place the new activity and thus it needs to be placed
* in its own separate task.
* 說(shuō)白了就是如果不加這個(gè)flag就沒(méi)有一個(gè)Task來(lái)存放新啟動(dòng)的Activity.
*
* 4 其實(shí)該flag和設(shè)置Activity的LaunchMode為SingleTask的效果是一樣的
*
*
* 如有更加深入的理解,請(qǐng)指點(diǎn),多謝^_^
最后
我在項(xiàng)目里采用的是啟動(dòng)Activity的方法饰迹,just for easy ,比較符合需求場(chǎng)景余舶,不用考慮全屏啊鸭,Activity只做提示作用 基本沒(méi)有什么代碼
class DormancyReminderActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dormancy_reminder)
EventBus.getDefault().register(this)
time.text = intent.getStringExtra("reminder")
@Subscribe
fun onScreenOnEvent(event: ScreenOnEvent) {
Logger.d("get onScreenOnEvent")
finish()
}
override fun onDestroy() {
super.onDestroy()
EventBus.getDefault().unregister(this)
}
override fun onBackPressed() {
}
}
屏蔽返回鍵事件,EventBus注冊(cè)接收到亮屏事件匿值,在亮屏?xí)rfinish赠制,沒(méi)啥好說(shuō)的。值得注意的是考慮到在休眠的時(shí)候挟憔,用戶按電源鍵 解鎖钟些,息屏的時(shí)候,會(huì)不斷創(chuàng)建Activity加入到棧中绊谭,所以要在AndroidManifest文件中給Activity的啟動(dòng)模式設(shè)為singleInstance
<activity
android:name="com.hykd.model.compate.DormancyReminderActivity"
android:launchMode="singleInstance"/>
鑒于我是一個(gè)Android萌新政恍,這里又要回顧一下Activity的四種啟動(dòng)模式了,大神請(qǐng)略過(guò)_
容我簡(jiǎn)單說(shuō)一下它們的使用場(chǎng)景:
Activity啟動(dòng)方式有四種达传,分別是:
- standard
- singleTop
- singleTask
- singleInstance
可以根據(jù)實(shí)際的需求為Activity設(shè)置對(duì)應(yīng)的啟動(dòng)模式抚垃,從而可以避免創(chuàng)建大量重復(fù)的Activity等問(wèn)題。
設(shè)置Activity的啟動(dòng)模式趟大,只需要在AndroidManifest.xml里對(duì)應(yīng)的<activity>標(biāo)簽設(shè)置android:launchMode屬性鹤树,例如:
<activity
android:name=".A1"
android:launchMode="standard" />
下面是這四種模式的作用:
- standard
默認(rèn)模式,可以不用寫(xiě)配置逊朽。在這個(gè)模式下罕伯,都會(huì)默認(rèn)創(chuàng)建一個(gè)新的實(shí)例。因此叽讳,在這種模式下追他,可以有多個(gè)相同的實(shí)例,也允許多個(gè)相同Activity疊加岛蚤。
例如:
若我有一個(gè)Activity名為A1, 上面有一個(gè)按鈕可跳轉(zhuǎn)到A1邑狸。那么如果我點(diǎn)擊按鈕,便會(huì)新啟一個(gè)Activity A1疊在剛才的A1之上涤妒,再點(diǎn)擊单雾,又會(huì)再新啟一個(gè)在它之上……
點(diǎn)back鍵會(huì)依照棧順序依次退出。
- singleTop
可以有多個(gè)實(shí)例,但是不允許多個(gè)相同Activity疊加硅堆。即屿储,如果Activity在棧頂?shù)臅r(shí)候,啟動(dòng)相同的Activity渐逃,不會(huì)創(chuàng)建新的實(shí)例够掠,而會(huì)調(diào)用其onNewIntent方法。
例如:
若我有兩個(gè)Activity名為B1,B2,兩個(gè)Activity內(nèi)容功能完全相同茄菊,都有兩個(gè)按鈕可以跳到B1或者B2疯潭,唯一不同的是B1為standard,B2為singleTop面殖。
若我意圖打開(kāi)的順序?yàn)锽1->B2->B2袁勺,則實(shí)際打開(kāi)的順序?yàn)锽1->B2(后一次意圖打開(kāi)B2,實(shí)際只調(diào)用了前一個(gè)的onNewIntent方法)
若我意圖打開(kāi)的順序?yàn)锽1->B2->B1->B2畜普,則實(shí)際打開(kāi)的順序與意圖的一致,為B1->B2->B1->B2群叶。
- singleTask
只有一個(gè)實(shí)例吃挑。在同一個(gè)應(yīng)用程序中啟動(dòng)他的時(shí)候,若Activity不存在街立,則會(huì)在當(dāng)前task創(chuàng)建一個(gè)新的實(shí)例舶衬,若存在,則會(huì)把task中在其之上的其它Activity destory掉并調(diào)用它的onNewIntent方法赎离。
如果是在別的應(yīng)用程序中啟動(dòng)它逛犹,則會(huì)新建一個(gè)task,并在該task中啟動(dòng)這個(gè)Activity梁剔,singleTask允許別的Activity與其在一個(gè)task中共存虽画,也就是說(shuō),如果我在這個(gè)singleTask的實(shí)例中再打開(kāi)新的Activity荣病,這個(gè)新的Activity還是會(huì)在singleTask的實(shí)例的task中码撰。
例如:
若我的應(yīng)用程序中有三個(gè)Activity,C1,C2,C3,三個(gè)Activity可互相啟動(dòng)个盆,其中C2為singleTask模式脖岛,那么,無(wú)論我在這個(gè)程序中如何點(diǎn)擊啟動(dòng)颊亮,如:C1->C2->C3->C2->C3->C1-C2柴梆,C1,C3可能存在多個(gè)實(shí)例,但是C2只會(huì)存在一個(gè)终惑,并且這三個(gè)Activity都在同一個(gè)task里面绍在。
但是C1->C2->C3->C2->C3->C1-C2,這樣的操作過(guò)程實(shí)際應(yīng)該是如下這樣的,因?yàn)閟ingleTask會(huì)把task中在其之上的其它Activity destory掉揣苏。
操作:C1->C2 C1->C2->C3 C1->C2->C3->C2 C1->C2->C3->C2->C3->C1 C1->C2->C3->C2->C3->C1-C2
實(shí)際:C1->C2 C1->C2->C3 C1->C2 C1->C2->C3->C1 C1->C2
若是別的應(yīng)用程序打開(kāi)C2悯嗓,則會(huì)新啟一個(gè)task。
如別的應(yīng)用Other中有一個(gè)activity卸察,taskId為200脯厨,從它打開(kāi)C2,則C2的taskIdI不會(huì)為200坑质,例如C2的taskId為201合武,那么再?gòu)腃2打開(kāi)C1、C3涡扼,則C2稼跳、C3的taskId仍為201。
注意:如果此時(shí)你點(diǎn)擊home吃沪,然后再打開(kāi)Other汤善,發(fā)現(xiàn)這時(shí)顯示的肯定會(huì)是Other應(yīng)用中的內(nèi)容,而不會(huì)是我們應(yīng)用中的C1 C2 C3中的其中一個(gè)票彪。
- singleInstance
只有一個(gè)實(shí)例红淡,并且這個(gè)實(shí)例獨(dú)立運(yùn)行在一個(gè)task中,這個(gè)task只有這個(gè)實(shí)例降铸,不允許有別的Activity存在在旱。
例如:
程序有三個(gè)ActivityD1,D2,D3,三個(gè)Activity可互相啟動(dòng)推掸,其中D2為singleInstance模式桶蝎。那么程序從D1開(kāi)始運(yùn)行,假設(shè)D1的taskId為200谅畅,那么從D1啟動(dòng)D2時(shí)登渣,D2會(huì)新啟動(dòng)一個(gè)task,即D2與D1不在一個(gè)task中運(yùn)行毡泻。假設(shè)D2的taskId為201绍豁,再?gòu)腄2啟動(dòng)D3時(shí),D3的taskId為200牙捉,也就是說(shuō)它被壓到了D1啟動(dòng)的任務(wù)棧中竹揍。
若是在別的應(yīng)用程序打開(kāi)D2,假設(shè)Other的taskId為200邪铲,打開(kāi)D2芬位,D2會(huì)新建一個(gè)task運(yùn)行,假設(shè)它的taskId為201带到,那么如果這時(shí)再?gòu)腄2啟動(dòng)D1或者D3昧碉,則又會(huì)再創(chuàng)建一個(gè)task,因此,若操作步驟為other->D2->D1被饿,這過(guò)程就涉及到了3個(gè)task了四康。
插曲
至此本次需求就已經(jīng)完美實(shí)現(xiàn)了,細(xì)心的你可能發(fā)現(xiàn)了我的標(biāo)題完美是打引號(hào)的狭握,那么又有怎樣的插曲呢 哎??
因?yàn)榻裉焓俏覍W(xué)習(xí)kotlin的第一天闪金,也是第一次嘗試,當(dāng)我加載Activity界面的時(shí)候论颅,打出onCreate隨手回車哎垦,系統(tǒng)自動(dòng)給我提供了這么一個(gè)onCreate():
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
}
Java代碼:
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
}
然而我這小白并沒(méi)有發(fā)現(xiàn),導(dǎo)致我的休眠提醒界面恃疯,setContentView之后卻始終顯示一片白漏设,找遍一切可能出錯(cuò)的地方,屬實(shí)浪費(fèi)不少時(shí)間今妄,最后在這個(gè)onCreate方法上面發(fā)現(xiàn)了貓膩(在這個(gè)onCreate方法里寫(xiě)了一個(gè)輸出郑口,發(fā)現(xiàn)根本沒(méi)走這個(gè)方法!6芰邸犬性!)。
第一反應(yīng)雁仲,我并不認(rèn)識(shí)這是一個(gè)什么玩意。打開(kāi)陳舊的api文檔琐脏,也沒(méi)有發(fā)現(xiàn)PersistableBundle這個(gè)類攒砖,于是只能求助百度,Google日裙。原來(lái)是Api21新加的特性吹艇,上一下google,找一下最新api昂拂。我們先來(lái)看一下PersistableBundle是什么東西受神。
A mapping from String values to various types that can be saved to persistent and later restored.
顯然,這是一個(gè)和Bundle差不多的東西格侯,Bundle我們就比較熟悉了鼻听。他兩都是一個(gè)鍵值對(duì),前者多了這么一段話联四,can be saved to persistent and later restored撑碴,可以持久化保存并且可以恢復(fù)。我們?cè)倏匆幌滦碌膐nCreate()方法的源碼朝墩。
/**
* Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
* the attribute {@link android.R.attr#persistableMode} set to
* <code>persistAcrossReboots</code>.
*
* @param savedInstanceState if the activity is being re-initialized after
* previously being shut down then this Bundle contains the data it most
* recently supplied in {@link #onSaveInstanceState}.
* <b><i>Note: Otherwise it is null.</i></b>
* @param persistentState if the activity is being re-initialized after
* previously being shut down or powered off then this Bundle contains the data it most
* recently supplied to outPersistentState in {@link #onSaveInstanceState}.
* <b><i>Note: Otherwise it is null.</i></b>
public void onCreate(@Nullable Bundle savedInstanceState,
@Nullable PersistableBundle persistentState) {
onCreate(savedInstanceState);
}
從源碼中可以看到醉拓,依然是調(diào)用了原始的onCreate()方法,結(jié)合以下兩個(gè)方法,
@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onRestoreInstanceState(savedInstanceState, persistentState);
}
最后記得在配置文件中注冊(cè)當(dāng)前Activity的時(shí)候加上這個(gè)屬性亿卤,android:persistableMode="persistAcrossReboots",這樣就可以給你的Activity存儲(chǔ)一些持久化數(shù)據(jù)愤兵。當(dāng)你的手機(jī)重啟或者發(fā)生其他意外情況的時(shí)候,也可以給你的頁(yè)面獲取到相關(guān)數(shù)據(jù)排吴。
結(jié)尾
再次請(qǐng)求原諒我是一只Android萌新秆乳、小白,一個(gè)小小的需求實(shí)現(xiàn)啰嗦這么多傍念,打我別打臉_