Giap Hiep

I'm Giap Hiep

I'm a web developer, a gymer. I enjoy share something i know that help people's work!
Giap Hiep

Một cái nhìn mới mẻ về màn hình Splash trong Android và Coroutines

Một cái nhìn mới mẻ về màn hình Splash trong Android và Coroutines

Màn hình Splash là một phương pháp rất phổ biến trong phát triển ứng dụng Android. Hầu như tất cả các ứng dụng chính đều chứa một số loại màn hình Splash vì nhiều lý do khác nhau từ nhận thức về thương hiệu đến tải tài nguyên nặng trước khi ứng dụng bắt đầu. Một số ứng dụng thậm chí còn thêm màn hình Splash chỉ vì họ muốn đi theo mô hình, mặc dù ứng dụng của họ không cần điều đó.

Đây là một chủ đề rất gây tranh cãi về việc các ứng dụng có nên chứa màn hình Splash hay không vì điều này làm lãng phí vài giây (quan trọng) của người dùng. Trong bài viết này, chúng tôi sẽ không tập trung vào cuộc tranh luận đó. Thay vào đó, chủ đề này là về các chi tiết kỹ thuật về việc tạo màn hình Splash với một số phương pháp dưới đây. Let's go!

Sử dụng Activity Theme

Khi bất kỳ ứng dụng nào được khởi chạy, hệ thống Android sẽ cố gắng vẽ màn hình đầu tiên nhanh nhất có thể. Nhưng đối với các lần chạy mới, điều này có thể mất vài phút. Trong những khoảnh khắc này, Android theo mặc định hiển thị màn hình trống với màu của windowBackground. Bạn có thể thay đổi màn hình này từ chỉ một màu sang màn hình Splash của mình bằng cách sử dụng Activity theme.

Tạo một theme riêng cho màn hình Splash của bạn

<?xml version="1.0" encoding="utf-8"?>
<resources>
  
  <!-- Your AppTheme or other themes/styles here -->
  
  <!-- The splash theme. to set the background with your splash screen drawable -->
  <style name=”AppTheme.Splash” parent="Theme.AppCompat.DayNight.NoActionBar">
    <item name=”android:windowBackground”>@drawable/splash_drawable</item>
  </style>
  
</resources>

Bạn có thể thấy rằng mình đã set thuộc tính windowBackground bằng file splash_drawable.xml. Và file đó đây:

<?xml version="1.0" encoding="utf-8"?>

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">

    <!-- The white background color as one in Amazon, YouTube, or Drive app -->
    <item android:drawable="@android:color/white"/>

    <!-- App logo -->
    <item>
        <bitmap
            android:src="@drawable/amazon_logo"
            android:gravity="center">
        </bitmap>
    </item>

</layer-list>

Bây giờ, hãy set theme này cho màn hình Splash của bạn trong file AndroidManifest.xml.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wajahatkarim.splashscreen.demo">

    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        
        <!-- Your Splash Activity theme -->
        <activity android:name=".SplashActivity"
            android:theme="@style/AppTheme.Splash">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <!-- Other Activities and other stuff -->
        
    </application>

</manifest>

Ta được một màn hình Splash trông như này

Set contentview cho Splash Activity

package com.wajahatkarim.splashscreen

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class SplashThemeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        setTheme(R.style.AppTheme)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash_theme)
    }
}

Nhìn chung, cách tiếp cận này là tốt từ trải nghiệm người dùng vì nó không lãng phí thời gian. Chỉ mất thời gian để tải ứng dụng. Vì vậy, nếu ứng dụng của bạn đã có trong bộ nhớ, bạn sẽ không phải nhìn thấy Splash theme nhiều. Cách tiếp cận này được áp dụng trong các ứng dụng Google rất nhiều.

Nhưng vấn đề với cách tiếp cận này là nếu phần giới thiệu của bạn chứa các hình động ưa thích hoặc thậm chí là một progressbar đơn giản. Bạn có thể thêm vào điều này. Bạn chỉ có thể thêm hình ảnh thông qua thẻ <bitmap> trong theme của mình và thay đổi vị trí của hình ảnh trên màn hình.

Sử dụng Handler / Runnable / Timer

Một số lượng lớn các ứng dụng phát triển theo phương pháp này để tạo màn hình Splash. Ý tưởng là tạo ra một Activity riêng cho màn hình Splash và sau đó khởi chạy màn hình Main được cố định hoặc dựa trên một số logic sau một khoảng thời gian dự kiến ví dụ như 2 - 3 giây. Màn hình Splash này không làm bất cứ điều gì ngoại trừ hiển thị cho người dùng một số màn hình và để cho người dùng chờ trong vài giây. Cách thường được sử dụng là dùng các class Handler và Runnable.

package com.wajahatkarim.splashscreen

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import androidx.core.os.postDelayed

class CommonSplashActivity : AppCompatActivity() {
    
    var handler = Handler()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_common_splash)

        handler.postDelayed(3000) {
            var intent = Intent(this@CommonSplashActivity, LoginActivity::class.java)
            startActivity(intent)
            finish()
        }
    }
    
    override fun onDestroy() {
        handler.removeCallbacksAndMessages(null)
        super.onDestroy()
    }
}

Điều này khá giống với cách sử dụng Activity Theme. Nhưng chúng ta nhận được nhiều lợi thế trong phương pháp này so với Activity Theme. Bạn có thể thêm hình động hoặc bất kỳ loại tùy chỉnh nào trong bố cục của bạn cho màn hình Splash. Bạn cũng có thể thêm logic nghiệp vụ để chuyển hướng người dùng đến bất kỳ màn hình cụ thể nào. Nhưng có một vấn đề.

Nếu người dùng nhấn nút Back cứng trong khi màn hình Splash đang chạy, nó sẽ không thể hủy đối tượng Handler và bạn sẽ thấy một số cửa sổ bật lên màn hình ngẫu nhiên vì nó đã được lên lịch để khởi chạy. Điều này có thể gây rò rỉ bộ nhớ hoặc nếu bạn đang lưu trữ một số dữ liệu trong bộ nhớ trong màn hình Splash thì dữ liệu đó sẽ bị mất. Bởi vì hệ thống Android sẽ khởi chạy màn hình chính của bạn như một task mới.

Một cách để khắc phục điều này là sử dụng class Timer vì chúng ta có thể hủy đối tượng Timer trong các phương thức onPause () hoặc onDestroy () để dừng khởi chạy theo lịch trình của màn hình Home hoặc màn hình đăng nhập.

package com.wajahatkarim.splashscreen

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.coroutines.cancel
import java.util.*
import kotlin.concurrent.schedule

class TimerSplashActivity : AppCompatActivity() {

    var timer = Timer()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_timer_splash)

        timer.schedule(3000) {
            var intent = Intent(this@TimerSplashActivity, HomeActivity::class.java)
            startActivity(intent)
            finish()
        }
    }

    override fun onPause() {
        timer.cancel()
        super.onPause()
    }
}

Cách này sẽ tránh rò rỉ bộ nhớ hoặc bật lên đột ngột của màn hình vì bộ đếm thời gian bị hủy trong onPause () và do đó, việc khởi chạy tác vụ màn hình Home cũng bị chấm dứt.

Cần lưu ý rằng Timer tạo ra một luồng hoàn toàn mới trong background. Và bạn không thể tương tác với các thành phần UI, Xem các đối tượng,... Ví dụ: bạn không thể cập nhật hình động của chế độ xem hoặc thay đổi giá trị văn bản của TextView,... Trong đối tượng TimerTask. Memory-wise là một cách tiếp cận hoạt động nặng và do chuyển đổi luồng, nó cũng có thể bị chậm.

Tôi đã thấy một số nơi sử dụng Thread thay vì Timer. Và Thread.sleep() được sử dụng cho mục đích trì hoãn. Tôi đặc biệt khuyên bạn nên tránh phương pháp này vì nó sẽ không chỉ gây rò rỉ bộ nhớ và gần như tương tự với phương pháp Handler trước đây mà chúng ta đã thảo luận.

Sử dụng Kotlin Coroutines

Mình chỉ đề xuất phương pháp này nếu bạn đã có Kotlin Coroutines trong project của bạn hoặc bạn dự định thêm vào trong tương lai gần. Bởi vì nó sẽ là một cách khá tốt để làm màn hình Splash.

package com.wajahatkarim.splashscreen

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.coroutines.*

class CoroutinesSplashActivity : AppCompatActivity() {

    val activityScope = CoroutineScope(Dispatchers.Main)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines_splash)

        activityScope.launch {
            delay(3000)

            var intent = Intent(this@CoroutinesSplashActivity, HomeActivity::class.java)
            startActivity(intent)
            finish()
        }
    }

    override fun onPause() {
        activityScope.cancel()
        super.onPause()
    }
}

Vì ActivityScope mà chúng ta đã tạo là của Dispatchers. Chính vì vậy, nó sẽ chạy trên main UI thread. Và là một coroutine, sẽ không chuyển đổi luồng vì coroutine chạy trên cùng một luồng. Điều này sẽ giúp tốc độ hiệu suất trái ngược với cách sử dụng Timer.

Khi Activity ở trạng thái onPause hoặc onDestroyed, coroutine sẽ tự động bị hủy. Chúng ta có thể dễ dàng cập nhật các yếu tố UI, xem hoạt hình,... khi chúng ta sử dụng cách này.

Nhìn chung, cách sử dụng coroutines là nhanh, đáng tin cậy, ít tốn bộ nhớ, mang lại lợi thế cho việc truy cập main UI thread và hỗ trợ logic khởi chạy tùy chỉnh.