Правильний підхід до ініціалізації важких бібліотек в заставці

5 хв. читання

Заставка (Splash screen)

Перш за все, якщо у вас вже є деякі речі в ініціалізації вашого класу Application, можливо ви захочете зробити правильний екран заставки. Це означає, що заставка повинна відразу відображатися після натискання на іконку програми. Це може бути легко досягнуто шляхом встановлення фону вашого SplashActivity в темі.

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

Та у вашому AndroidManifest.xml:

<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">
  </category></action></intent-filter>
</activity>

Зазвичай заставка - це логотип, отже @drawable/background_splash може бути layer-list, наприклад:

<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:src="@drawable/ic_hockey_stick">
  </bitmap></item>
</item></layer-list>

Всю інформацію цієї реалізації можете отримати тут

До речі, якщо ви використовуєте <vector> ресурси як джерело для вашого растрового зображення, то будьте в курсі цього багу. На жаль, немає обхідних шляхів для вирішення цієї проблеми. Так що, для рівнів API < 23, ви повинні використовувати PNG файли.

Ініціалізація бібліотеки

Тепер у нас є миттєвий запуск. Що ми робимо далі? Ми повинні придумати спосіб, як ми ініціалізуємо нашу повільну бібліотеку. І тут Dagger 2 та RxJava приходять на допомогу!

Якщо ця "важка" бібліотека потребує тільки заставку для завантаження своїх даних, то ми можемо визначити її в SplashModule, таким чином, ми зможемо очистити всі посилання на бібліотеку після її використання.

@Module
public class SplashModule {
  @Provides @NonNull @SplashScope 
  public SplashLibrary splashLibrary() {
    return new SplashLibrary(); // Займає >5 секунд
  }
}

На даний момент ми не можемо @Inject (вставити) цю бібліотеку в будь-якому місці, тому що вона заморозить інтерфейс користувача. Замість цього ми створимо Observable, який отримує SplashLibrary екземпляр, але все ще не є ініціалізований, оскільки ми передаємо тільки його Lazy<> екземпляр.

@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.

/** Observable який буде видавати елемент при повній ініціалізації. */
@Inject Observable<splashlibrary> splashLibraryObservable;

/** Підписка відписатися в OnStop(). */
private Subscription subscription;

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

  // ...

  subscription = splashLibraryObservable
      // Ініціалізація бібліотеки в іншому потоці.
      .subscribeOn(Schedulers.computation())
      // Опрацювання результату в основному потоці.
      .observeOn(AndroidSchedulers.mainThread())
      .subscribe(new Action1<splashlibrary>() {
        @Override public void call(SplashLibrary splashLibrary) {

          // Використаємо ініціалізовану бібліотеку.

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

Підводні камені, які потрібно знати:

  1. Можливість бібліотеки кидати виключення => нам необхідно реалізувати метод OnError().

  2. Можливість виходу користувача / повороту Activity перед ініціалізацією бібліотеки. Ця можливість призводить до витоку пам'яті, тому що наш код посилається на Activity в зворотньому виклику.

Обслуговування помилок при ініціалізації важкої бібліотеки

Для вирішення цієї проблеми, можна передати Observer екземпляр до subscribe() методу.

.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);
    // Можлива взаємодія UI.
    // ...
    finish();
  }

  @Override public void onNext(SplashLibrary splashLibrary) {
    // ...
    // Використаємо ініціалізовану бібліотеку.

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

Обробка витоків пам'яті, якщо користувач покидає Activity

В даному прикладі не достатньо просто відмовитися від Subscription, тому що в той час як об'єкт ініціалізується, Subscription не може вивільнити ресурси, і саме тому ми знищуємо Activity, яка викликає витік пам'яті. Це можна легко побачити в LogCat, якщо встановлено значення StrictMode.enableDefaults(); в класі Application. При повороті Activity, StrictMode реєструє кілька примірників Activity.

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)

Ось чому нам потрібно звільнити Activity, на яку посилається створений Observer. Ми можемо зробити це шляхом створення статичного класу, який реалізує Observer<splashactivity>, передаючи посилання на активність там, а потім очищуючи її в onDestroy(). Таким чином, ми можемо гарантувати, що нічого не було пропущено.

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;
  }
}
@Override protected void onDestroy() {
  super.onDestroy();

  onInitObserver.releaseListener();
}

Беручи до уваги всі ці приклади, можна легко ініціалізувати бібліотеку, зробити запит до мережі або робити будь-яку іншу важку роботу, показуючи заставку.

Вихідний код доступний тут.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація