คำขอที่แอปส่งไปยังเครือข่ายเป็นสาเหตุหลักที่ทำให้แบตเตอรี่หมดเร็ว เนื่องจากคำขอเหล่านี้จะเปิดใช้เครือข่ายมือถือหรือ Wi-Fi ที่ใช้พลังงานมาก นอกเหนือจากพลังงานที่จำเป็นในการส่งและรับแพ็กเก็ตแล้ว วิทยุเหล่านี้ยังใช้พลังงานเพิ่มเติมเพียงเพื่อเปิดและตื่นอยู่เสมอ การดำเนินการง่ายๆ อย่างคำขอเครือข่ายทุกๆ 15 วินาทีอาจทำให้วิทยุมือถือเปิดอยู่ตลอดเวลาและใช้พลังงานแบตเตอรี่อย่างรวดเร็ว
การอัปเดตเป็นประจำมี 3 ประเภททั่วไป ดังนี้
- ผู้ใช้เป็นผู้เริ่ม ทำการอัปเดตตามพฤติกรรมของผู้ใช้ เช่น ท่าทางสัมผัสดึงเพื่อรีเฟรช
- เริ่มต้นโดยแอป การอัปเดตเป็นประจำ
- เซิร์ฟเวอร์เป็นผู้เริ่ม กำลังทำการอัปเดตเพื่อตอบสนองต่อการแจ้งเตือนจากเซิร์ฟเวอร์
หัวข้อนี้จะพิจารณาแต่ละรายการและพูดคุยถึงวิธีเพิ่มเติมที่สามารถเพิ่มประสิทธิภาพเพื่อลดการสิ้นเปลืองแบตเตอรี่
เพิ่มประสิทธิภาพคำขอที่ผู้ใช้เริ่ม
โดยปกติแล้ว คำขอที่ผู้ใช้เริ่มจะเกิดขึ้นเพื่อตอบสนองต่อพฤติกรรมบางอย่างของผู้ใช้ ตัวอย่างเช่น แอปที่ใช้อ่านบทความข่าวล่าสุดอาจอนุญาตให้ผู้ใช้ใช้ท่าทางสัมผัสดึงเพื่อรีเฟรชเพื่อตรวจสอบบทความใหม่ คุณสามารถใช้เทคนิคต่อไปนี้เพื่อตอบสนองต่อคำขอที่ผู้ใช้เริ่มขึ้นพร้อมกับเพิ่มประสิทธิภาพการใช้งานเครือข่าย
ควบคุมคำขอของผู้ใช้
คุณอาจต้องละเว้นคำขอที่ผู้ใช้เริ่มบางรายการหากไม่จำเป็น เช่น ท่าทางสัมผัสดึงเพื่อรีเฟรชหลายครั้งในช่วงเวลาสั้นๆ เพื่อตรวจสอบข้อมูลใหม่ขณะที่ข้อมูลปัจจุบันยังใหม่อยู่ การดำเนินการตามคำขอแต่ละรายการอาจทำให้สิ้นเปลืองพลังงานจำนวนมากเนื่องจากต้องเปิดวิทยุไว้ตลอดเวลา วิธีที่มีประสิทธิภาพมากกว่าคือการจำกัดคำขอที่ผู้ใช้เริ่ม เพื่อให้สามารถส่งคำขอได้เพียงรายการเดียวในช่วงระยะเวลาหนึ่ง ซึ่งจะช่วยลดความถี่ในการใช้คลื่นวิทยุ
ใช้แคช
การแคชข้อมูลของแอปจะสร้างสำเนาข้อมูลในเครื่องที่แอปต้องใช้อ้างอิง จากนั้นแอปของคุณจะสามารถเข้าถึงสำเนาข้อมูลในเครื่องเดียวกันได้หลายครั้งโดยไม่ต้องเปิดการเชื่อมต่อเครือข่ายเพื่อส่งคำขอใหม่
คุณควรแคชข้อมูลอย่างเต็มที่ รวมถึงทรัพยากรแบบคงที่และการดาวน์โหลดแบบออนดีมานด์ เช่น รูปภาพขนาดเต็ม คุณสามารถใช้ส่วนหัวแคช HTTP เพื่อให้มั่นใจว่ากลยุทธ์การแคชจะไม่ส่งผลให้แอปแสดงข้อมูลที่ล้าสมัย ดูข้อมูลเพิ่มเติมเกี่ยวกับการแคชการตอบสนองของเครือข่ายได้ที่หลีกเลี่ยงการดาวน์โหลดซ้ำซ้อน
ใน Android 11 ขึ้นไป แอปของคุณสามารถใช้ชุดข้อมูลขนาดใหญ่ชุดเดียวกับที่แอปอื่นๆ ใช้สำหรับกรณีการใช้งานต่างๆ เช่น แมชชีนเลิร์นนิงและการเล่นสื่อ เมื่อแอปของคุณจำเป็นต้องเข้าถึงชุดข้อมูลที่แชร์ แอปจะตรวจสอบเวอร์ชันที่แคชไว้ก่อนได้ ก่อนที่จะพยายามดาวน์โหลดสำเนาใหม่ ดูข้อมูลเพิ่มเติมเกี่ยวกับชุดข้อมูลที่แชร์ได้ที่หัวข้อเข้าถึงชุดข้อมูลที่แชร์
ใช้แบนด์วิดท์มากขึ้นเพื่อดาวน์โหลดข้อมูลมากขึ้นแต่ไม่บ่อยนัก
เมื่อเชื่อมต่อผ่านวิทยุไร้สาย แบนด์วิดท์ที่สูงขึ้นมักจะมาพร้อมกับราคาแบตเตอรี่ที่สูงขึ้น ซึ่งหมายความว่าโดยทั่วไปแล้ว 5G จะใช้พลังงานมากกว่า LTE ซึ่งมีราคาแพงกว่า 3G
ซึ่งหมายความว่าแม้ว่าสถานะวิทยุที่สำคัญจะแตกต่างกันไปตามเทคโนโลยีวิทยุ แต่โดยทั่วไปแล้ว ผลกระทบต่อแบตเตอรี่ที่เกี่ยวข้องของเวลาส่วนท้ายของการเปลี่ยนสถานะจะมากกว่าสำหรับวิทยุที่มีแบนด์วิดท์สูงกว่า ดูข้อมูลเพิ่มเติมเกี่ยวกับเวลาที่ใช้ในการปิดการเชื่อมต่อได้ที่เครื่องมือแสดงสถานะวิทยุ
ในขณะเดียวกัน แบนด์วิดท์ที่สูงขึ้นหมายความว่าคุณสามารถดึงข้อมูลล่วงหน้าได้มากขึ้น โดยดาวน์โหลดข้อมูลได้มากขึ้นในเวลาเดียวกัน สิ่งที่อาจไม่ชัดเจนนักคือ เนื่องจากต้นทุนแบตเตอรี่ในช่วงเวลาท้ายค่อนข้างสูง การเปิดใช้งานวิทยุเป็นเวลานานขึ้นในระหว่างเซสชันการโอนแต่ละครั้งจึงมีประสิทธิภาพมากกว่าเพื่อลดความถี่ในการอัปเดต
ตัวอย่างเช่น หากวิทยุ LTE มีแบนด์วิดท์และค่าใช้จ่ายด้านพลังงานเป็น 2 เท่าของ 3G คุณควรดาวน์โหลดข้อมูลมากกว่า 4 เท่าในแต่ละเซสชัน หรืออาจมากถึง 10 MB เมื่อดาวน์โหลดข้อมูลจำนวนมากเช่นนี้ คุณต้องพิจารณาผลกระทบของการดึงข้อมูลล่วงหน้าที่มีต่อพื้นที่เก็บข้อมูลในเครื่องที่พร้อมใช้งาน และล้างแคชการดึงข้อมูลล่วงหน้าเป็นประจำ
คุณสามารถใช้ ConnectivityManager เพื่อลงทะเบียน Listener สำหรับเครือข่ายเริ่มต้น และใช้ 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 ที่ดึงพาดหัวข่าวล่าสุด คุณสามารถกำหนดเวลาให้ผู้ปฏิบัติงานนี้ทำงานทุกชั่วโมงได้ โดยอุปกรณ์ต้องเชื่อมต่อกับเครือข่ายที่ไม่มีการวัดปริมาณอินเทอร์เน็ตและแบตเตอรี่ของอุปกรณ์ไม่ต่ำ พร้อมกลยุทธ์การลองใหม่ที่กำหนดเองหากมีปัญหาในการดึงข้อมูล ดังที่แสดงด้านล่าง
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 เซิร์ฟเวอร์จะแจ้งเตือนแอปที่ทำงานในอุปกรณ์หนึ่งๆ ได้ว่ามีข้อมูลใหม่พร้อมใช้งาน
เมื่อเทียบกับการโพลที่แอปต้องส่งคําสั่ง ping ไปยังเซิร์ฟเวอร์เป็นประจําเพื่อค้นหาข้อมูลใหม่ โมเดลที่ขับเคลื่อนด้วยเหตุการณ์นี้ช่วยให้แอปสร้างการเชื่อมต่อใหม่ได้เฉพาะเมื่อทราบว่ามีข้อมูลให้ดาวน์โหลด โมเดลนี้จะลดการเชื่อมต่อที่ไม่จำเป็นและลดเวลาในการตอบสนองเมื่ออัปเดตข้อมูลภายในแอป
FCM จะดำเนินการโดยใช้การเชื่อมต่อ TCP/IP แบบถาวร ซึ่งจะช่วยลดจำนวนการเชื่อมต่อแบบถาวรและช่วยให้แพลตฟอร์มเพิ่มประสิทธิภาพแบนด์วิดท์และลดผลกระทบที่เกี่ยวข้องกับอายุการใช้งานแบตเตอรี่