React Native Android的启动白屏/闪屏的原因,解决方案,原理,使用方法

React Native Android启动屏,启动白屏,闪现白屏

本文出自《React Native学习笔记》@http://www.cboy.me/系列文章。

React Native Android启动屏,启动白屏,闪现白屏
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";
    }
}

通过上述代码可以看出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();
  }

上面代码是ReactActivityonCreate方法的代码,onCreate作为一个Activity的入口,负责着程序初始化等一系列工作。
熟悉Android开发的小伙伴都知道,在onCreate方法通过setContentView()方法设置一个用于用户交互界面。在ReactActivityonCreate方法中也有使用setContentView()

 mReactRootView = createRootView();
 
 mReactRootView.startReactApplication(
      getReactNativeHost().getReactInstanceManager(),
      getMainComponentName(),
      getLaunchOptions());
 setContentView(mReactRootView);

上述代码中,首先通过mReactRootView = createRootView();创建一个根视图,该视图便是React Native应用的最顶部视图。然后通过mReactRootView.startReactApplication方法,加载并渲染js bundle,此过程是比较耗时的。最后,通过setContentView(mReactRootView);将根视图绑定到Activity界面上。

基本原理就是这些,下面我们就对ReactActivity动动刀子。

实现思路

先说一下思路:

  1. APP启动的时候控制ReactActivity显示启动屏。
  2. 提供关闭启动屏的公共接口。
  3. 在js的适当位(一般是程序初始化工作完成后)置调用上述公共接口关闭启动屏。

具体实现

第一步:APP启动的时候控制ReactActivity显示启动屏

在给ReactActivity动刀子前我们需要进行一些准备工作。

基础准备:

首先,我们需要将ReactActivity复制一份出来。
因为ReactActivity是React Native源码中的一部分,我们无法直接对其源码进行修改,所以我们需将它复制一份出来。然后将MainActivity继承改为我们复制出来的这个ReactActivity

其次。修改getUseDeveloperSupport方法。

因为,ReactNativeHostgetUseDeveloperSupport方法是受保护类型的,所以我们无法在它所属包之外访问该方法。但我们又需要在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();
    }

首先,我创建了一个mRootView = new FrameLayout(this);视图容器。

其次,将启动屏布局文件读到内存中splashView = LayoutInflater.from(this).inflate(R.layout.launch_screen, null);

再次,添加mReactRootViewsplashView,注意添加顺序。

最后,将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中调用LaunchScreenhide()方法来关闭启动屏了。

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.cboy.me/:技术干货都在这里哦
GitHub:我的开源项目

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容