为了账号安全,请及时绑定邮箱和手机立即绑定

【译】如何恰当的在闪屏页加载繁重的数据和库

标签:
Android

在这篇文章中,我将为你们展示开发者开启App时,因为加载一些库和数据遇到的初始化非常慢的这种情况。在这种情况下,开发者通常不会在主线程中初始化,因为这样做会使整个App卡住。相反,开发者希望通过后台初始化数据和库,然后在主线程处理初始化结果。

闪屏页 SplashActivity

首先,如果你已经有了一些需要在自定义Application类中初始化的东西,你可能想着要做一个恰当的闪屏页。这意味着你点击App图标的同时,闪屏页已经完整的显示出来了。通过设置闪屏页Theme的背景图,我们可以轻易实现这个需求。

res/values/styles.xml

1
2
3
< style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
  <item name="android:windowBackground">@drawable/background_splash</item>
</style>

AndroidManifest.xml

1
2
3
4
5
6
7
8
<activity
  android:name=".splash.SplashActivity"
  android:theme="@style/SplashTheme">
  <intent-filter>
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/>
  </intent-filter>
</activity>

通常情况下,闪屏页一般是放个logo,所以 @drawable/background_splash 可以写成一个 layer-list

例如:

1
2
3
4
5
6
7
8
9
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:drawable="@android:color/holo_blue_dark"/>

  <item>
    <bitmap
      android:gravity="center"
      android:class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="@drawable/ic_hockey_stick"/>
  </item>
</layer-list>

顺便说一下,如果你用了矢量的 <vector> 作为 <src> 赋予 <bitmap> ,那么请你注意这个bug
坑爹的是,这个bug现在没办法解决,所以在API小于23的时候你只能用PNG来代替矢量图。

初始化数据和库

现在我们已经可以瞬间打开App了,那么接下来该怎么做?我们应该想办法如何初始化这种加载缓慢的库。Dagger2 和 RxJava 或许对我们有帮助。

如果只是在闪屏页需要这种‘长初始化’库,来加载必要的资源,那么我们可以定义一个 SplashModule ,那么我们就能把所有库的引用都写到这里。可以,这很解耦。

1
2
3
4
5
6
7
@Module
public class SplashModule {
  @Provides @NonNull @SplashScope 
  public SplashLibrary splashLibrary() {
    return new SplashLibrary(); // Takes >5 seconds.
  }
}

现在,我们还不能注入 @Inject ,因为这样做会阻塞我们的主线程。我们需要通过RxJava来创建一个观察者 Observable ,用来接收 SplashLibrary 实例,由于我们用了懒加载 Lazy<> ,我们的库仍未初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Module
public class SplashModule {
  // ...

  @Provides @NonNull @SplashScope
  public Observable<SplashLibrary> observable(final Lazy<SplashLibrary> library) {
    return Observable.defer(new Func0<Observable<SplashLibrary>>() {
      @Override public Observable<SplashLibrary> call() {
        return Observable.just(library.get());
      }
    });
  }
}

注入这个库

最后,我们要把库 Observable<SplashLibrary> 注入到我们的闪屏页 SplashActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/** Observable which will emit an item when fully initialized. */
@Inject Observable<SplashLibrary> splashLibraryObservable;

/** Subscription to unsubscribe in onStop(). */
private Subscription subscription;

@Override protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  // ...

  subscription = splashLibraryObservable
      // Init library on another thread.
      .subscribeOn(Schedulers.computation())
      // Observe result on the main thread.
      .observeOn(AndroidSchedulers.mainThread())
      .subscribe(new Action1<SplashLibrary>() {
        @Override public void call(SplashLibrary splashLibrary) {

          // Use the initialized library.

          Intent intent = new Intent(activity, MainActivity.class);
          startActivity(intent);
        }
      });
  }
}

这儿还有一些个小问题等着你:

  1. 库加载的过程中,可能会抛出异常,我们需要在 onError 方法中去处理它们。

  2. 库加载过程中,用户可能会离开此页或旋转屏幕。由于我们在回调函数中引用了 Activity ,所以有可能导致内存泄露。

处理加载过程中的异常

为了处理这个问题,我们可以传一个 Observer 实例给 subscribe() 方法。

很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.subscribe(new Observer<SplashLibrary>() {
  final String TAG = "Observer<SplashLibrary>";
  
  @Override public void onCompleted() {  }

  @Override public void onError(Throwable e) {
    Log.d(TAG, "Library init error!", e);
    // Possible UI interaction.
    // ...
    finish();
  }

  @Override public void onNext(SplashLibrary splashLibrary) {
    // ...
    // Use the initialized library.

    Intent intent = new Intent(activity, MainActivity.class);
    startActivity(intent);
    finish();
  }
});

处理内存溢出问题

在这个例子中,我们不能从 Subscription 中取消订阅,因为对象一旦加载开始,Subscription 就不能释放资源了。这也就是为什么在内存中还存在着已经销毁的Activity对象,它导致了内存泄露。如果我们在Application中加入了严苛模式 StrictMode.enableDefaults(); ,我们可以很容易的在 LogCat 中看到看到Log。当我们旋转屏幕,严苛模式显示了Acitivty的实例信息。

E/StrictMode: class .SplashActivity; instances=2; limit=1
android.os.StrictMode$InstanceCountViolation: class .SplashActivity; instances=2; limit=1
at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)

这就是为什么我们需要在创建的 Observer 中释放Activity的引用了。我们可以创建一个静态类去实现 Observer<SplashActivity> ,在给他传入一个Activity的引用,然后在 onDestroy 中清除引用。这样,我们可以确保没有内存泄漏异常了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static final class OnInitObserver implements Observer<SplashLibrary> {
  @Nullable private SplashActivity splashActivity;

  OnInitObserver(@NonNull SplashActivity splashActivity) {
    this.splashActivity = splashActivity;
  }

  @Override public void onCompleted() { /* ... */ }
  @Override public void onError(Throwable e) { /* ... */ }
  @Override public void onNext(SplashLibrary splashLibrary) { /* ... */ }

  public void releaseListener() {
    splashActivity = null;
  }
}
1
2
3
4
5
@Override protected void onDestroy() {
  super.onDestroy();

  onInitObserver.releaseListener();
}

记住这几点,我们就能轻松地在闪屏页中加载库、数据,执行网络请求或者做一些其他的繁重的任务。

感谢阅读,获取源码请点这里

原文链接:http://www.apkbus.com/blog-719059-63124.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消