React Native Android的啟動白屏/閃屏的原因,解決方案,原理,使用方法
問題描述:
用React Native架構的無論是Android APP還是iOS APP,在啟動時都出現白屏現象,時間大概1~3s(根據手機或模擬器的性能不同而不同)。
問題分析:
React Native應用在啟動時會將js bundle讀取到內存中,并完成渲染。這期間由于js bundle還沒有完成裝載并渲染,所以界面顯示的是白屏。
白屏給人的感覺很不友好,那有沒有辦法不顯示白屏呢?
上文解釋了:為什么React Native應用會在啟動的時候顯示一會白屏。既然知道了出現問題的原因,那么離解決問題也不遠了。市場上大部分APP在啟動的時候都會有個啟動屏,啟動屏對于用戶是比較友好的,一來展示歡迎信息,二來顯示一些產品信息或一些廣告,啟動頁對于程序來說,是為程序完成初始化加載數據,做一些初始化工作的所保留的時間,啟動屏等待的時間可長可短,具體根據業務而定。
下面我就教大家如何給React Native Android加啟動屏,并解決啟動白屏的問題。
為React Native Android添加啟動屏(解決白屏等待問題)
為了實現為React Native Android添加啟動屏,我們需要給React Native動刀了了。下面就讓我們從源碼看起。
原理分析
通過 react-native init <project name> 初始化的應用,Android部分,只有一個 MainActivity ,它是整個Android程序的入口。
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "GitHubPopular";
}
}</code></pre>
通過上述代碼可以看出 MainActivity 很干凈,就一個 getMainComponentName() 方法。顯然啟動白屏不是因為 MainActivity 導致的。
接下來,我們就繼續探索,進入 ReactActivity 源碼一探究竟。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) {
// Get permission to show redbox in dev builds.
if (!Settings.canDrawOverlays(this)) {
Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(serviceIntent);
FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
Toast.makeText(this, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
}
}
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
getMainComponentName(),
getLaunchOptions());
setContentView(mReactRootView);
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
}
上面代碼是 ReactActivity 的 onCreate 方法的代碼, onCreate 作為一個Activity的入口,負責著程序初始化等一系列工作。
熟悉Android開發的小伙伴都知道,在 onCreate 方法通過 setContentView() 方法設置一個用于用戶交互界面。在 ReactActivity 的 onCreate 方法中也有使用 setContentView() 。
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
getMainComponentName(),
getLaunchOptions());
setContentView(mReactRootView);</code></pre>
上述代碼中,首先通過 mReactRootView = createRootView(); 創建一個根視圖,該視圖便是React Native應用的最頂部視圖。然后通過 mReactRootView.startReactApplication 方法,加載并渲染js bundle,此過程是比較耗時的。最后,通過 setContentView(mReactRootView); 將根視圖綁定到Activity界面上。
基本原理就是這些,下面我們就對 ReactActivity 動動刀子。
實現思路
先說一下思路:
- APP啟動的時候控制ReactActivity顯示啟動屏。
- 提供關閉啟動屏的公共接口。
- 在js的適當位(一般是程序初始化工作完成后)置調用上述公共接口關閉啟動屏。
具體實現
第一步:APP啟動的時候控制ReactActivity顯示啟動屏
在給 ReactActivity 動刀子前我們需要進行一些準備工作。
基礎準備:
首先,我們需要將 ReactActivity 復制一份出來。
因為 ReactActivity 是React Native源碼中的一部分,我們無法直接對其源碼進行修改,所以我們需將它復制一份出來。然后將 MainActivity 繼承改為我們復制出來的這個 ReactActivity 。
其次。修改getUseDeveloperSupport方法。
因為, ReactNativeHost 的 getUseDeveloperSupport 方法是受保護類型的,所以我們無法在它所屬包之外訪問該方法。但我們又需要在 ReactActivity 中調用該方法,那么我們可以使用反射來滿足我們這一需求。
protected boolean getUseDeveloperSupport() {
ReactNativeHost rnh=((ReactApplication) getApplication()).getReactNativeHost();
Class<?>cls=rnh.getClass();
Object support= null;
try {
Method method = cls.getDeclaredMethod("getUseDeveloperSupport", new Class[]{});
method.setAccessible(true);
support=method.invoke(rnh);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return (boolean) support;
}
前期工作準備玩了,現在讓我們開始吧。
為了讓ReactActivity顯示啟動屏我們需要創建一個View容器,來容納啟動屏視圖和React Native根視圖。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) {
// Get permission to show redbox in dev builds.
if (!Settings.canDrawOverlays(this)) {
Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(serviceIntent);
FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
Toast.makeText(this, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
}
}
mRootView = new FrameLayout(this);
splashView = LayoutInflater.from(this).inflate(R.layout.launch_screen, null);
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
getMainComponentName(),
getLaunchOptions());
mRootView.addView(mReactRootView);
mRootView.addView(splashView, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
setContentView(mRootView);
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
}</code></pre>
首先,我創建了一個 mRootView = new FrameLayout(this); 視圖容器。
其次,將啟動屏布局文件讀到內存中 splashView = LayoutInflater.from(this).inflate(R.layout.launch_screen, null); 。
再次,添加 mReactRootView 與 splashView ,注意添加順序。
最后,將 mRootView 綁定到Activity。
這樣一來,我們就控制了ReactActivity在啟動的時候顯示歡迎界面。下面我們需要讓ReactActivity開放關閉換用界面的接口方法。
/**
* 隱藏啟動屏幕
*/
public void hide() {
if (mRootView == null || splashView == null) return;
AlphaAnimation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setDuration(1000);
splashView.startAnimation(fadeOut);
fadeOut.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mRootView.removeView(splashView);
splashView = null;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
上述方法,中加入了一個淡出動畫持續1s,目的是讓歡迎界面和其他界面之間過度自然些。
做到這里還不夠,因為我們需要在js中調用 hide 方法還控制歡迎界面的關閉。js不能直接調Java,所有我們需要為他們搭建一個橋梁( Native Modules )。
首先,創建一個 ReactContextBaseJavaModule 類型的類,供js調用。
/**
* LaunchScreenModule
* 出自:http://www.cboy.me
* GitHub:https://github.com/crazycodeboy
* Eamil:crazycodeboy@gmail.com
*/
public class LaunchScreenModule extends ReactContextBaseJavaModule{
public LaunchScreenModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "LaunchScreen";
}
@ReactMethod
public void hide(){
getCurrentActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
((ReactActivity)getCurrentActivity()).hide();
}
});
}
}
其次,創建一個 ReactPackage 類型的類,用于向React Native注冊我們的 LaunchScreenModule 組件。
/**
* LaunchScreenReactPackage
* 出自:http://www.cboy.me
* GitHub:https://github.com/crazycodeboy
* Eamil:crazycodeboy@gmail.com
*/
public class LaunchScreenReactPackage implements ReactPackage {
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new LaunchScreenModule(reactContext));
return modules;
}
}
再次,在MainApplication中注冊 LaunchScreenModule 組件。
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new LaunchScreenReactPackage()
);
}
最后,在js中調用LaunchScreenModule。
創建一個名為 LaunchScreen 的文件,加入下面代碼。
/**
* LaunchScreen
* Android啟動屏
* 出自:http://www.cboy.me
* GitHub:https://github.com/crazycodeboy
* Eamil:crazycodeboy@gmail.com
* @flow
*/
'use strict';
import { NativeModules } from 'react-native';
module.exports = NativeModules.LaunchScreen;
上述代碼,目的是向js暴露 LaunchScreen 模塊。
下面我們就可以在js中調用 LaunchScreen 的 hide() 方法來關閉啟動屏了。
LaunchScreen.hide();
不要忘記在使用LaunchScreen的js文件中導入它哦 import LaunchScreen from './LaunchScreen 。
到這里,React Native Android的啟動白屏的原因,解決方案,原理,使用方法已經向大家介紹完了。大家如果還有什么疑問可以加群: 165774887 ,和我一起討論。
另外,跟大家分享一個Android啟動時閃現白屏或黑屏的解決方案。
這個問題是Android主題的問題和React Native無關,請往下看。
修改主題解決閃現白屏/黑屏
問題描述:
市場上有很多應用,在啟動的時候,會出現閃現黑屏或白屏,有的應用卻沒有。究其原因,是主題在搞鬼。
問題分析
當單擊應用的圖標時,Android會為被單擊的應用創建一個進程,然后創建一個Application實例,然后應用主題,然后啟動Activity。
因為啟動Activity也是需要時間的,這之間的時間間隔,便是閃現白屏或黑屏的時間。
解決方案
為解決啟動時閃現白屏或黑屏的問題,我們可以從主題下手,為應用創建一個透明的主題。
第一步:創建一個透明主題。
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!--設置透明背景-->
<item name="android:windowIsTranslucent">true</item>
</style>
第二步:在AndroidManifest.xml中為application應用主題。
<application
android:name=".MainApplication"
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
這樣一來,啟動時變不會閃現黑屏或白屏了。
如果,你的應用需要一個特定的主題,但該主題不是透明的,你可以先將application的默認主題設置成透明的主題,然后在程序啟動后(可以在啟動頁進行),通過 public void setTheme(int resid) 方法將主題設置成你想要的主題即可。
來自:http://www.jianshu.com/p/6917e536de27