ลดผลกระทบของการอัปเดตเป็นประจำ

คำขอที่แอปของคุณส่งไปยังเครือข่ายเป็นสาเหตุหลักที่ทำให้แบตเตอรี่หมด เนื่องจากคำขอเหล่านี้จะเปิดวิทยุเครือข่ายมือถือหรือ Wi-Fi ที่ใช้พลังงาน นอกเหนือจากพลังงานที่จำเป็นในการรับและส่งแพ็กเก็ตแล้ว วิทยุเหล่านี้ยังใช้พลังงานเพิ่มเติมเพียงแค่เปิดและคงสถานะตื่น การส่งคำขอเครือข่ายทุกๆ 15 วินาทีอาจทำให้วิทยุบนอุปกรณ์เคลื่อนที่เปิดอยู่ตลอดเวลาและใช้พลังงานแบตเตอรี่อย่างรวดเร็ว

การอัปเดตเป็นประจำมี 3 ประเภทหลักๆ ดังนี้

  • ผู้ใช้เป็นผู้เริ่ม การอัปเดตตามพฤติกรรมของผู้ใช้บางอย่าง เช่น ท่าทางดึงเพื่อรีเฟรช
  • แอปเป็นผู้เริ่ม ดำเนินการอัปเดตเป็นประจำ
  • เซิร์ฟเวอร์เป็นผู้เริ่ม การอัปเดตเพื่อตอบสนองต่อการแจ้งเตือนจาก เซิร์ฟเวอร์

หัวข้อนี้จะพิจารณาแต่ละรายการและอธิบายวิธีเพิ่มเติมในการ เพิ่มประสิทธิภาพเพื่อลดการใช้แบตเตอรี่

เพิ่มประสิทธิภาพคำขอที่ผู้ใช้เริ่มต้น

คำขอที่ผู้ใช้เริ่มต้นมักเกิดขึ้นเพื่อตอบสนองต่อพฤติกรรมบางอย่างของผู้ใช้ เช่น แอปที่ใช้เพื่ออ่านบทความข่าวล่าสุดอาจอนุญาตให้ผู้ใช้ ใช้ท่าทางดึงเพื่อรีเฟรชเพื่อตรวจสอบบทความใหม่ คุณใช้ เทคนิคต่อไปนี้เพื่อตอบคำขอที่ผู้ใช้เริ่มได้ขณะเพิ่มประสิทธิภาพ การใช้งานเครือข่าย

ควบคุมคำขอของผู้ใช้

คุณอาจต้องการละเว้นคำขอที่ผู้ใช้เริ่มต้นหากไม่จำเป็น เช่น ท่าทางดึงเพื่อรีเฟรชหลายครั้งในช่วงเวลาสั้นๆ เพื่อตรวจสอบข้อมูลใหม่ในขณะที่ข้อมูลปัจจุบันยังคงเป็นข้อมูลล่าสุด การดำเนินการตามคำขอแต่ละรายการอาจทำให้สิ้นเปลืองพลังงานเป็นอย่างมากเนื่องจากต้องเปิดวิทยุไว้ แนวทางที่มีประสิทธิภาพมากกว่าคือการควบคุมคำขอที่ผู้ใช้เริ่มต้นเพื่อให้ ส่งคำขอได้เพียงครั้งเดียวในช่วงระยะเวลาหนึ่ง ซึ่งจะช่วยลดความถี่ในการใช้ วิทยุ

ใช้แคช

การแคชข้อมูลของแอปเป็นการสร้างสำเนาข้อมูลในเครื่อง ที่แอปต้องอ้างอิง จากนั้นแอปจะเข้าถึงสำเนาข้อมูลเดียวกันในเครื่องได้หลายครั้งโดยไม่ต้องเปิดการเชื่อมต่อเครือข่ายเพื่อส่งคำขอใหม่

คุณควรแคชข้อมูลอย่างเต็มที่ที่สุดเท่าที่จะเป็นไปได้ รวมถึงทรัพยากรแบบคงที่ และการดาวน์โหลดตามต้องการ เช่น รูปภาพขนาดเต็ม คุณใช้ส่วนหัวของแคช HTTP เพื่อให้มั่นใจว่ากลยุทธ์การแคชจะไม่ส่งผลให้แอปแสดงข้อมูลที่ล้าสมัย ดูข้อมูลเพิ่มเติมเกี่ยวกับการแคชการตอบกลับของเครือข่ายได้ที่ หลีกเลี่ยงการดาวน์โหลด ที่ซ้ำกัน

ใน Android 11 ขึ้นไป แอปของคุณสามารถใช้ชุดข้อมูลขนาดใหญ่ชุดเดียวกันกับที่แอปอื่นๆ ใช้สำหรับกรณีการใช้งานต่างๆ เช่น แมชชีนเลิร์นนิงและการเล่นสื่อ เมื่อแอปต้องเข้าถึงชุดข้อมูลที่แชร์ แอปจะตรวจสอบเวอร์ชันที่แคชไว้ก่อนได้ ก่อนที่จะพยายามดาวน์โหลดสำเนาใหม่ ดูข้อมูลเพิ่มเติมเกี่ยวกับชุดข้อมูลที่แชร์ได้ที่เข้าถึงชุดข้อมูลที่แชร์

ใช้แบนด์วิดท์ที่มากขึ้นเพื่อดาวน์โหลดข้อมูลเพิ่มเติมได้บ่อยขึ้น

เมื่อเชื่อมต่อผ่านคลื่นวิทยุไร้สาย โดยทั่วไปแล้วแบนด์วิดท์ที่สูงขึ้นจะมาพร้อมกับ ราคาที่สูงขึ้นของแบตเตอรี่ ซึ่งหมายความว่าโดยปกติแล้ว 5G จะใช้พลังงานมากกว่า LTE ซึ่งมีราคาแพงกว่า 3G

ซึ่งหมายความว่าแม้ว่าสถานะวิทยุพื้นฐานจะแตกต่างกันไปตามเทคโนโลยีวิทยุ แต่โดยทั่วไปแล้ว ผลกระทบต่อแบตเตอรี่ที่เกี่ยวข้องของเวลาสิ้นสุดการเปลี่ยนแปลงสถานะจะมากกว่าสำหรับวิทยุที่มีแบนด์วิดท์สูงกว่า ดูข้อมูลเพิ่มเติมเกี่ยวกับ ช่วงเวลาท้ายได้ที่เครื่องสถานะวิทยุ

ในขณะเดียวกัน แบนด์วิดท์ที่สูงขึ้นหมายความว่าคุณสามารถดึงข้อมูลล่วงหน้าได้มากขึ้น อย่างรวดเร็ว โดยดาวน์โหลดข้อมูลได้มากขึ้นในช่วงเวลาเดียวกัน และอาจดูไม่ชัดเจนนัก เนื่องจากต้นทุนแบตเตอรี่ในช่วงท้ายๆ ค่อนข้างสูง การเปิดใช้สัญญาณวิทยุเป็นระยะเวลานานขึ้นในระหว่างเซสชันการโอนแต่ละครั้งจึงมีประสิทธิภาพมากกว่าเพื่อลดความถี่ในการอัปเดต

ตัวอย่างเช่น หากคลื่นวิทยุ LTE มีแบนด์วิดท์และค่าใช้จ่ายด้านพลังงานเป็น 2 เท่าของ 3G คุณควรดาวน์โหลดข้อมูลให้ได้ 4 เท่าในแต่ละเซสชัน หรืออาจดาวน์โหลดได้มากถึง 10 MB เมื่อดาวน์โหลดข้อมูลจำนวนมากเช่นนี้ คุณควร พิจารณาผลของการดึงข้อมูลล่วงหน้าต่อพื้นที่เก็บข้อมูลในเครื่องที่มีอยู่ และล้าง แคชการดึงข้อมูลล่วงหน้าเป็นประจำ

คุณใช้ ConnectivityManager เพื่อลงทะเบียน เครื่องรับสำหรับเครือข่ายเริ่มต้น และ TelephonyManager เพื่อลงทะเบียน PhoneStateListener เพื่อ ระบุประเภทการเชื่อมต่ออุปกรณ์ปัจจุบันได้ เมื่อทราบประเภทการเชื่อมต่อแล้ว คุณจะแก้ไขกิจวัตรการดึงข้อมูลล่วงหน้าได้ตามความเหมาะสม

Kotlin

val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val tm = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager

private var hasWifi = false
private var hasCellular = false
private var cellModifier: Float = 1f

private val networkCallback = object : ConnectivityManager.NetworkCallback() {
    // Network capabilities have changed for the network
    override fun onCapabilitiesChanged(
            network: Network,
            networkCapabilities: NetworkCapabilities
    ) {
        super.onCapabilitiesChanged(network, networkCapabilities)
        hasCellular = networkCapabilities
    .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
        hasWifi = networkCapabilities
    .hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
    }
}

private val phoneStateListener = object : PhoneStateListener() {
override fun onPreciseDataConnectionStateChanged(
    dataConnectionState: PreciseDataConnectionState
) {
  cellModifier = when (dataConnectionState.networkType) {
      TelephonyManager.NETWORK_TYPE_LTE or TelephonyManager.NETWORK_TYPE_HSPAP -> 4f
      TelephonyManager.NETWORK_TYPE_EDGE or TelephonyManager.NETWORK_TYPE_GPRS -> 1/2f
      else -> 1f

  }
}

private class NetworkState {
    private var defaultNetwork: Network? = null
    private var defaultCapabilities: NetworkCapabilities? = null
    fun setDefaultNetwork(network: Network?, caps: NetworkCapabilities?) = synchronized(this) {
        defaultNetwork = network
        defaultCapabilities = caps
    }
    val isDefaultNetworkWifi
        get() = synchronized(this) {
            defaultCapabilities?.hasTransport(TRANSPORT_WIFI) ?: false
        }
    val isDefaultNetworkCellular
        get() = synchronized(this) {
            defaultCapabilities?.hasTransport(TRANSPORT_CELLULAR) ?: false
        }
    val isDefaultNetworkUnmetered
        get() = synchronized(this) {
            defaultCapabilities?.hasCapability(NET_CAPABILITY_NOT_METERED) ?: false
        }
    var cellNetworkType: Int = TelephonyManager.NETWORK_TYPE_UNKNOWN
        get() = synchronized(this) { field }
        set(t) = synchronized(this) { field = t }
    private val cellModifier: Float
        get() = synchronized(this) {
            when (cellNetworkType) {
                TelephonyManager.NETWORK_TYPE_LTE or TelephonyManager.NETWORK_TYPE_HSPAP -> 4f
                TelephonyManager.NETWORK_TYPE_EDGE or TelephonyManager.NETWORK_TYPE_GPRS -> 1 / 2f
                else -> 1f
            }
        }
    val prefetchCacheSize: Int
        get() = when {
            isDefaultNetworkWifi -> MAX_PREFETCH_CACHE
            isDefaultNetworkCellular -> (DEFAULT_PREFETCH_CACHE * cellModifier).toInt()
            else -> DEFAULT_PREFETCH_CACHE
        }
}
private val networkState = NetworkState()
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
    // Network capabilities have changed for the network
    override fun onCapabilitiesChanged(
            network: Network,
            networkCapabilities: NetworkCapabilities
    ) {
        networkState.setDefaultNetwork(network, networkCapabilities)
    }

    override fun onLost(network: Network?) {
        networkState.setDefaultNetwork(null, null)
    }
}

private val telephonyCallback = object : TelephonyCallback(), TelephonyCallback.PreciseDataConnectionStateListener {
    override fun onPreciseDataConnectionStateChanged(dataConnectionState: PreciseDataConnectionState) {
        networkState.cellNetworkType = dataConnectionState.networkType
    }
}

connectivityManager.registerDefaultNetworkCallback(networkCallback)
telephonyManager.registerTelephonyCallback(telephonyCallback)


private val prefetchCacheSize: Int
get() {
    return when {
        hasWifi -> MAX_PREFETCH_CACHE
        hasCellular -> (DEFAULT_PREFETCH_CACHE * cellModifier).toInt()
        else -> DEFAULT_PREFETCH_CACHE
    }
}

}

Java

ConnectivityManager cm =
 (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
TelephonyManager tm =
  (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);

private boolean hasWifi = false;
private boolean hasCellular = false;
private float cellModifier = 1f;

private ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onCapabilitiesChanged(
    @NonNull Network network,
    @NonNull NetworkCapabilities networkCapabilities
) {
        super.onCapabilitiesChanged(network, networkCapabilities);
        hasCellular = networkCapabilities
    .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
        hasWifi = networkCapabilities
    .hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
}
};

private PhoneStateListener phoneStateListener = new PhoneStateListener() {
@Override
public void onPreciseDataConnectionStateChanged(
    @NonNull PreciseDataConnectionState dataConnectionState
    ) {
    switch (dataConnectionState.getNetworkType()) {
        case (TelephonyManager.NETWORK_TYPE_LTE |
            TelephonyManager.NETWORK_TYPE_HSPAP):
            cellModifier = 4;
            Break;
        case (TelephonyManager.NETWORK_TYPE_EDGE |
            TelephonyManager.NETWORK_TYPE_GPRS):
            cellModifier = 1/2.0f;
            Break;
        default:
            cellModifier = 1;
            Break;
    }
}
};

cm.registerDefaultNetworkCallback(networkCallback);
tm.listen(
phoneStateListener,
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE
);

public int getPrefetchCacheSize() {
if (hasWifi) {
    return MAX_PREFETCH_SIZE;
}
if (hasCellular) {
    return (int) (DEFAULT_PREFETCH_SIZE * cellModifier);
    }
return DEFAULT_PREFETCH_SIZE;
}

เพิ่มประสิทธิภาพคำขอที่เริ่มจากแอป

คำขอที่แอปเริ่มต้นมักเกิดขึ้นตามกำหนดเวลา เช่น แอปที่ส่งบันทึกหรือข้อมูลวิเคราะห์ไปยังบริการแบ็กเอนด์ เมื่อจัดการกับคำขอที่แอปเริ่มต้น ให้พิจารณาลำดับความสำคัญของคำขอเหล่านั้น ไม่ว่าจะจัดกลุ่มคำขอ ด้วยกันได้หรือไม่ และจะเลื่อนคำขอเหล่านั้นออกไปจนกว่าอุปกรณ์จะชาร์จหรือ เชื่อมต่อกับเครือข่ายที่ไม่จำกัดปริมาณการใช้งานได้หรือไม่ คุณสามารถเพิ่มประสิทธิภาพคำขอเหล่านี้ได้ด้วยการ จัดกำหนดการอย่างรอบคอบและใช้ไลบรารี เช่น WorkManager

คำขอเครือข่ายแบบกลุ่ม

ในอุปกรณ์เคลื่อนที่ กระบวนการเปิดวิทยุ สร้างการเชื่อมต่อ และเปิดวิทยุไว้จะใช้พลังงานจำนวนมาก ด้วยเหตุนี้ การประมวลผลคำขอแต่ละรายการในเวลาแบบสุ่มจึงอาจใช้พลังงานมากและทำให้อายุการใช้งานแบตเตอรี่ลดลง แนวทางที่มีประสิทธิภาพมากกว่าคือการจัดคิวชุดคำขอเครือข่าย และประมวลผลพร้อมกัน ซึ่งช่วยให้ระบบจ่ายค่าไฟฟ้าสำหรับการเปิดวิทยุเพียงครั้งเดียวและยังคงได้รับข้อมูลทั้งหมดที่แอปขอ

ใช้ WorkManager

คุณสามารถใช้WorkManagerไลบรารีเพื่อทำงานตามกำหนดการที่มีประสิทธิภาพ ซึ่งพิจารณาว่าตรงตามเงื่อนไขที่เฉพาะเจาะจงหรือไม่ เช่น ความพร้อมใช้งานของเครือข่าย และสถานะพลังงาน ตัวอย่างเช่น สมมติว่าคุณมีคลาสย่อย Worker ชื่อ DownloadHeadlinesWorker ซึ่งดึงข้อมูลพาดหัวข่าวล่าสุด สามารถตั้งเวลาให้ Worker นี้ ทำงานทุกชั่วโมงได้ โดยมีเงื่อนไขว่าอุปกรณ์ต้องเชื่อมต่อกับ เครือข่ายที่ไม่จำกัดปริมาณการใช้งานและแบตเตอรี่ของอุปกรณ์ต้องไม่เหลือน้อย พร้อมด้วยกลยุทธ์การลองใหม่ที่กำหนดเอง หากมีปัญหาในการดึงข้อมูล ดังที่แสดงด้านล่าง

Kotlin

val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.UNMETERED)
    .setRequiresBatteryNotLow(true)
    .build()
val request =
    PeriodicWorkRequestBuilder<DownloadHeadlinesWorker>(1, TimeUnit.HOURS)
        .setConstraints(constraints)
        .setBackoffCriteria(BackoffPolicy.LINEAR, 1L, TimeUnit.MINUTES)
        .build()
WorkManager.getInstance(context).enqueue(request)

Java

Constraints constraints = new Constraints.Builder()
        .setRequiredNetworkType(NetworkType.UNMETERED)
        .setRequiresBatteryNotLow(true)
        .build();
WorkRequest request = new PeriodicWorkRequest.Builder(DownloadHeadlinesWorker.class, 1, TimeUnit.HOURS)
        .setBackoffCriteria(BackoffPolicy.LINEAR, 1L, TimeUnit.MINUTES)
        .build();
WorkManager.getInstance(this).enqueue(request);

นอกจาก WorkManager แล้ว แพลตฟอร์ม Android ยังมีเครื่องมืออื่นๆ อีกหลายอย่าง ที่จะช่วยคุณสร้างกำหนดการที่มีประสิทธิภาพสำหรับการทำงานด้านเครือข่ายให้เสร็จสมบูรณ์ เช่น การสำรวจ ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้เครื่องมือเหล่านี้ได้ที่คู่มือการประมวลผลเบื้องหลัง

เพิ่มประสิทธิภาพคำขอที่เริ่มจากเซิร์ฟเวอร์

โดยปกติแล้ว คำขอที่เซิร์ฟเวอร์เริ่มต้นจะเกิดขึ้นเพื่อตอบสนองต่อการแจ้งเตือนจากเซิร์ฟเวอร์ เช่น แอปที่ใช้เพื่ออ่านบทความข่าวล่าสุดอาจได้รับการแจ้งเตือนเกี่ยวกับบทความชุดใหม่ที่ตรงกับค่ากำหนดการปรับเปลี่ยนในแบบของคุณของผู้ใช้ จากนั้นแอปจะดาวน์โหลดบทความดังกล่าว

ส่งข้อมูลอัปเดตเซิร์ฟเวอร์ด้วย Firebase Cloud Messaging

Firebase Cloud Messaging (FCM) เป็นกลไกที่มีน้ำหนักเบา ซึ่งใช้ในการส่งข้อมูลจากเซิร์ฟเวอร์ไปยังอินสแตนซ์แอปที่เฉพาะเจาะจง เมื่อใช้ FCM เซิร์ฟเวอร์จะแจ้งให้แอปที่ทำงานในอุปกรณ์หนึ่งๆ ทราบว่ามีข้อมูลใหม่พร้อมใช้งาน

เมื่อเทียบกับการสำรวจที่แอปต้องปิงเซิร์ฟเวอร์เป็นประจำเพื่อค้นหาข้อมูลใหม่ โมเดลที่ขับเคลื่อนด้วยเหตุการณ์นี้จะช่วยให้แอปสร้างการเชื่อมต่อใหม่ได้ก็ต่อเมื่อทราบว่ามีข้อมูลให้ดาวน์โหลดเท่านั้น โมเดลนี้จะลดการเชื่อมต่อที่ไม่จำเป็น และลดเวลาในการตอบสนองเมื่ออัปเดตข้อมูลภายในแอป

FCM ได้รับการติดตั้งใช้งานโดยใช้การเชื่อมต่อ TCP/IP แบบถาวร ซึ่งจะช่วยลด จำนวนการเชื่อมต่อแบบถาวรและช่วยให้แพลตฟอร์มเพิ่มประสิทธิภาพแบนด์วิดท์ และลดผลกระทบที่เกี่ยวข้องกับอายุการใช้งานแบตเตอรี่ได้