在这篇文章中,我将为你们展示开发者开启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); } }); } } |
这儿还有一些个小问题等着你:
库加载的过程中,可能会抛出异常,我们需要在
onError
方法中去处理它们。库加载过程中,用户可能会离开此页或旋转屏幕。由于我们在回调函数中引用了
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(); } |
记住这几点,我们就能轻松地在闪屏页中加载库、数据,执行网络请求或者做一些其他的繁重的任务。
感谢阅读,获取源码请点这里。
共同学习,写下你的评论
评论加载中...
作者其他优质文章