作者
大家好,我叫Jack馮;
本人20年碩士畢業(yè)于廣東工業(yè)大學,于2020年6月加入37手游安卓團隊;目前主要負責海外游戲發(fā)行安卓相關(guān)開發(fā)。
本文目錄
一、背景
二、分析及解決
1、生命周期分析
(1)黑屏情況
(2)解決方法
(3)正常顯示
2、涉及方法解析
(1)onWindowFocusChanged (boolean hasFocus)
(2)Android生命周期
(3)對比Android原生工程
(4)unity腳本生命周期
(5)分析腳本生命周期
三、結(jié)論
一、背景
在Unity游戲工程中,經(jīng)常遇到這樣的問題:打開登錄彈框時,點擊Home鍵先處理其他事宜再返回,發(fā)現(xiàn)屏幕黑屏;或者打開了其他接受輸入焦點的對話框或彈出窗口,點擊返回鍵時發(fā)生屏幕黑屏,需要觸摸屏幕(獲得焦點)才能正常顯示。
具體情形見下圖:
1、黑屏情況(UnityDemo):

2、 正常顯示(UnityDemo):

二、分析及解決
1、生命周期分析
(1)黑屏情況(UnityDemo):

其中,生命周期順序如下:
- 打開頁面:onCreate--onStart--onResume--onWindowFocusChanged:true
- 點擊登錄:--onWindowFocusChanged:false
- 點擊Home返回:--onPause
- 重新進入:--onRestart--onStart--onNewIntent--onResume--onWindowFocusChanged:false(此時app頁面出現(xiàn)黑屏)
(2)解決方法
在游戲主活動UnityPlayerActivity中,重寫onStart()方法,添加獲取焦點的方法,可避免黑屏。
@Override
protected void onStart() {
super.onStart();
this.mUnityPlayer.resume();
onWindowFocusChanged(true);
}
(3)正常顯示(UnityDemo):

其中,生命周期順序如下:
- 打開頁面:onCreate--onStart--onWindowFocusChanged:true--onResume--onWindowFocusChanged:true
- 點擊登錄:--onWindowFocusChanged:false
- 點擊Home返回:--onPause
- 重新進入:--onRestart--onStart--onWindowFocusChanged:ture--onNewIntent--onResume(此時app頁面正常顯示)
由上可見,二者生命周期的異同在于,是否在調(diào)用onStart后調(diào)用一次onWindowFocusChanged:true,來獲取當前窗口的焦點,實現(xiàn)正常交互。
2、涉及方法解析
(1)onWindowFocusChanged (boolean hasFocus)
當activity的當前窗口獲得或失去焦點時調(diào)用,hasFocus == true表示當前窗口獲得焦點,false則表示失去焦點。用法:
onWindowFocusChanged (true);
- eg:打開頁面,當前activity處于活動棧最上層的活動,獲得焦點--onWindowFocusChanged:ture;
- 點擊登錄,彈框覆蓋在原activity的上層,原activity失去焦點 --onWindowFocusChanged:false;(不僅限彈框,還可以是其他獲取焦點的頁面)
- 此后點擊Home鍵、再返回app,原activity仍然是失去焦點的狀態(tài)(如果沒有手動重新獲取焦點),當前頁面顯示黑屏。
注意
onWindowFocusChanged方法提供了有關(guān)全局焦點狀態(tài)的信息,該狀態(tài)獨立于活動生命周期進行管理。因此,雖然焦點更改通常與生命周期更改有某種關(guān)系(停止的活動通常不會獲得窗口焦點),但您不應(yīng)依賴此處回調(diào)與其他生命周期方法(如onResume()中的回調(diào))之間的特定順序。
但是,一般來說,前臺活動具有窗口焦點。除非它顯示了其他接受輸入焦點的對話框或彈出窗口,在這種情況下,當其他窗口有焦點時,活動本身就沒有焦點。同樣,系統(tǒng)可能會顯示系統(tǒng)級窗口(例如狀態(tài)欄通知面板或系統(tǒng)警報),這些窗口將暫時獲得窗口輸入焦點,而不會暫停前臺活動。
從Android Q開始,在多窗口模式下,可以同時有多個恢復(fù)的活動,因此即使上面沒有覆蓋,恢復(fù)狀態(tài)也不能保證窗口焦點。如果目的是要知道一個活動何時是最活躍的,即用戶在所有活動中與之交互的最后一個活動,但不包括非活動窗口(如對話框和彈出窗口),則應(yīng)使用OnTopheMedActivityChanged(Boolean value)。
(2)生命周期方法簡析
- onCreate (Bundle savedInstanceState):活動創(chuàng)建時調(diào)用一次,用于初始化當前活動數(shù)據(jù)和綁定頁面的組件等。參數(shù)Bundle:如果活動在關(guān)閉后重新初始化,此參數(shù)則包含其最近一次調(diào)用 onSaveInstanceState(Bundle)存儲的數(shù)據(jù)。
- onStart ():在活動創(chuàng)建方法onCreate(Bundle)或重新啟動方法onRestart()之后調(diào)用,開始繪制視圖、動畫等,呈現(xiàn)給用戶,其后一般調(diào)用onResume()。(可視化狀態(tài))
- onResume ():在onRestoreInstanceState()、onRestart()或onPause()之后調(diào)用,當前活動位于活動棧的頂部,即將開始與用戶進行交互、準備好接收輸入事件。(還不能響應(yīng)輸入事件)
- onPause ():活動仍在屏幕上可見,但用戶不再與其交互時進行調(diào)用,eg:彈框等頁面覆蓋了當前活動時。
- onStop ():當活動在屏幕上不可見時調(diào)用,eg:點擊home鍵返回桌面
- onRestart ():在 onStop ()方法后,重新打開原activity時調(diào)用,其后一般調(diào)用onStart ()和onResume ()
- onDestroy ():在銷毀活動之前執(zhí)行任何最后的清理時調(diào)用。一般是活動即將結(jié)束(調(diào)用 finish()),或系統(tǒng)暫時銷毀了此活動實例以節(jié)省空間
(3)對比Android原生工程

圖為原生工程的AndroidDemo。對比UnityDemo,生命周期方法執(zhí)行雖一致、焦點丟失情況則不相同。
為了進一步對比,下面引入unity腳本的常見生命周期方法。
(4)unity腳本生命周期
unity腳本的常見生命周期方法如下:
-- Awake:始終在任何 Start 函數(shù)之前并在實例化組件之后調(diào)用此函數(shù)。(如果游戲?qū)ο笤趩悠陂g處于非活動狀態(tài),則在激活之后才會調(diào)用 Awake。)
-- OnEnable:(僅在對象處于激活狀態(tài)時調(diào)用)在啟用對象后立即調(diào)用此函數(shù)。在創(chuàng)建 MonoBehaviour 實例時(例如加載關(guān)卡或?qū)嵗哂心_本組件的游戲?qū)ο髸r)會執(zhí)行此調(diào)用。
-- OnLevelWasLoaded:場景全部加載完成后
-- Start:僅當啟用腳本實例后,才會在第一次幀更新之前調(diào)用 Start。
-- FixedUpdate:調(diào)用 FixedUpdate 的頻度常常超過 Update。如果幀率很低,可以每幀調(diào)用該函數(shù)多次;如果幀率很高,可能在幀之間完全不調(diào)用該函數(shù)。
-- Update:每幀調(diào)用一次 Update。這是用于幀更新的主要函數(shù)。
-- LateUpdate:每幀調(diào)用一次 LateUpdate__(在 Update__ 完成后)。
-- OnGUI:每幀調(diào)用多次以響應(yīng) GUI 事件。首先處理布局和重新繪制事件,然后為每個輸入事件處理布局和鍵盤/鼠標事件。
-- OnApplicationPause:一幀最后時調(diào)用,調(diào)用后會再觸發(fā)一幀以刷新圖像和切換暫停狀態(tài)
-- OnApplicationQuit:在退出應(yīng)用程序之前在所有游戲?qū)ο笊险{(diào)用此函數(shù)。在編輯器中,用戶停止播放模式時,調(diào)用函數(shù)。
-- OnDisable:行為被禁用或處于非活動狀態(tài)時,調(diào)用此函數(shù)。
-- OnDestroy:對象存在的最后一幀完成所有幀更新之后,調(diào)用此函數(shù)(可能應(yīng) Object.Destroy 要求或在場景關(guān)閉時銷毀該對象)。
(5)分析腳本生命周期
這里將生命周期方法在UnityDemo中打印出來,主要對比黑屏情況下的生命周期情況。
具體日志如下:
//android生命周期
E/UnityPlayerActivity: onCreate
E/UnityPlayerActivity: onStart
E/UnityPlayerActivity: onResume
E/UnityPlayerActivity: onWindowFocusChanged:true
//unity腳本生命周期
I/Unity: UnityPlayerActivity Awake
...
I/Unity: UnityPlayerActivity OnApplicationPause:False
I/Unity: UnityPlayerActivity OnApplicationFocus:True
I/Unity: UnityPlayerActivity start
//點擊home鍵
E/UnityPlayerActivity: onWindowFocusChanged:false
I/Unity: UnityPlayerActivity OnApplicationFocus:False
E/UnityPlayerActivity: onPause
I/Unity: UnityPlayerActivity OnApplicationPause:True
...
//再次返回app
E/UnityPlayerActivity: onRestart
E/UnityPlayerActivity: onStart
E/UnityPlayerActivity: onResume
//***注意***,這里unity腳本生命周期方法并沒有繼續(xù)執(zhí)行!
三、總結(jié)
綜合分析,從桌面返回游戲App時,由于unity丟失焦點(I/Unity: UnityPlayerActivity OnApplicationFocus:False ),腳本沒有執(zhí)行,即無法渲染游戲畫面對象,致使黑屏。
如果根據(jù)第二點添加獲取焦點方法后,由下圖可以看到繼續(xù)執(zhí)行的unity腳本生命周期方法,先獲取到焦點、中止pause狀態(tài)并繪制頁面進行正常顯示。即工程重新獲取焦點后才會繪制圖像。
I/Unity: UnityPlayerActivity OnApplicationFocus:True
I/Unity: UnityPlayerActivity OnApplicationPause:False