নিয়মিত আপডেটের প্রভাব কমিয়ে দিন

আপনার অ্যাপ নেটওয়ার্কে যে অনুরোধগুলো পাঠায়, তা ব্যাটারি দ্রুত শেষ হওয়ার একটি প্রধান কারণ, কারণ এগুলো শক্তি-ব্যয়কারী সেলুলার বা ওয়াই-ফাই রেডিও চালু করে দেয়। প্যাকেট পাঠানো ও গ্রহণ করার জন্য প্রয়োজনীয় শক্তির বাইরেও, এই রেডিওগুলো শুধু চালু হতে এবং সজাগ থাকতেই অতিরিক্ত শক্তি খরচ করে। প্রতি ১৫ সেকেন্ডে একটি সাধারণ নেটওয়ার্ক অনুরোধের মতো বিষয়ও মোবাইল রেডিওকে ক্রমাগত চালু রাখতে পারে এবং দ্রুত ব্যাটারির শক্তি শেষ করে দিতে পারে।

নিয়মিত আপডেটের তিনটি সাধারণ ধরন রয়েছে:

  • ব্যবহারকারী-প্রবর্তিত। ব্যবহারকারীর কোনো আচরণের উপর ভিত্তি করে আপডেট সম্পাদন করা, যেমন পুল-টু-রিফ্রেশ অঙ্গভঙ্গি।
  • অ্যাপ থেকে শুরু করা হয়েছে। নিয়মিতভাবে একটি আপডেট করা হচ্ছে।
  • সার্ভার-প্রবর্তিত। সার্ভার থেকে প্রাপ্ত একটি বিজ্ঞপ্তির প্রতিক্রিয়ায় একটি আপডেট সম্পাদন করা হচ্ছে।

এই অধ্যায়ে এই প্রতিটি বিষয় পর্যালোচনা করা হয়েছে এবং ব্যাটারির চার্জ ক্ষয় কমাতে সেগুলোকে আরও উন্নত করার উপায় নিয়ে আলোচনা করা হয়েছে।

ব্যবহারকারী-প্রবর্তিত অনুরোধগুলি অপ্টিমাইজ করুন

ব্যবহারকারীর স্বতঃপ্রণোদিত অনুরোধগুলো সাধারণত ব্যবহারকারীর কোনো আচরণের প্রতিক্রিয়ায় ঘটে থাকে। উদাহরণস্বরূপ, সর্বশেষ সংবাদ নিবন্ধগুলো পড়ার জন্য ব্যবহৃত একটি অ্যাপ ব্যবহারকারীকে নতুন নিবন্ধ আছে কিনা তা দেখার জন্য একটি পুল-টু-রিফ্রেশ অঙ্গভঙ্গি করার অনুমতি দিতে পারে। নেটওয়ার্ক ব্যবহার অপ্টিমাইজ করার পাশাপাশি ব্যবহারকারীর স্বতঃপ্রণোদিত অনুরোধগুলোর প্রতিক্রিয়া জানাতে আপনি নিম্নলিখিত কৌশলগুলো ব্যবহার করতে পারেন।

থ্রটল ব্যবহারকারীর অনুরোধ

প্রয়োজন না থাকলে আপনি ব্যবহারকারীর পাঠানো কিছু অনুরোধ উপেক্ষা করতে পারেন; যেমন, বর্তমান ডেটা সতেজ থাকা অবস্থায় নতুন ডেটা পরীক্ষা করার জন্য অল্প সময়ের মধ্যে একাধিকবার টেনে রিফ্রেশ করা। প্রতিটি অনুরোধে কাজ করলে রেডিওকে সজাগ রাখার মাধ্যমে প্রচুর পরিমাণে শক্তি অপচয় হতে পারে। এর চেয়ে আরও কার্যকর একটি উপায় হলো ব্যবহারকারীর অনুরোধগুলোকে নিয়ন্ত্রণ করা, যাতে একটি নির্দিষ্ট সময় ধরে কেবল একটি অনুরোধই করা যায় এবং এর ফলে রেডিওর ব্যবহার কমে আসে।

ক্যাশে ব্যবহার করুন

আপনার অ্যাপের ডেটা ক্যাশ করার মাধ্যমে, আপনি সেই তথ্যের একটি স্থানীয় অনুলিপি তৈরি করেন যা আপনার অ্যাপের প্রয়োজনে ব্যবহার করতে হয়। এর ফলে, নতুন অনুরোধ করার জন্য নেটওয়ার্ক সংযোগ খোলার প্রয়োজন ছাড়াই আপনার অ্যাপ একই স্থানীয় অনুলিপিটি একাধিকবার ব্যবহার করতে পারে।

আপনার ডেটা যথাসম্ভব জোরালোভাবে ক্যাশ করা উচিত, যার মধ্যে স্ট্যাটিক রিসোর্স এবং পূর্ণ আকারের ছবির মতো অন-ডিমান্ড ডাউনলোডও অন্তর্ভুক্ত। আপনার ক্যাশিং কৌশলের কারণে যেন অ্যাপে পুরোনো ডেটা প্রদর্শিত না হয়, তা নিশ্চিত করতে আপনি HTTP ক্যাশ হেডার ব্যবহার করতে পারেন। নেটওয়ার্ক রেসপন্স ক্যাশ করার বিষয়ে আরও তথ্যের জন্য, ‘অপ্রয়োজনীয় ডাউনলোড পরিহার করুন ’ দেখুন।

অ্যান্ড্রয়েড ১১ এবং এর পরবর্তী সংস্করণগুলিতে, আপনার অ্যাপ মেশিন লার্নিং এবং মিডিয়া প্লেব্যাকের মতো কাজের জন্য অন্যান্য অ্যাপের ব্যবহৃত একই বড় ডেটাসেট ব্যবহার করতে পারে। যখন আপনার অ্যাপের কোনো শেয়ার করা ডেটাসেট অ্যাক্সেস করার প্রয়োজন হয়, তখন এটি একটি নতুন কপি ডাউনলোড করার চেষ্টা করার আগে প্রথমে একটি ক্যাশ করা সংস্করণ আছে কিনা তা পরীক্ষা করতে পারে। শেয়ার করা ডেটাসেট সম্পর্কে আরও জানতে, ‘শেয়ার করা ডেটাসেট অ্যাক্সেস করুন’ দেখুন।

কম সময়ে বেশি ডেটা ডাউনলোড করতে বৃহত্তর ব্যান্ডউইথ ব্যবহার করুন।

ওয়্যারলেস রেডিওর মাধ্যমে সংযুক্ত হলে, উচ্চতর ব্যান্ডউইথের জন্য সাধারণত ব্যাটারির খরচও বেশি হয়, যার অর্থ হলো 5G সাধারণত LTE-এর চেয়ে বেশি শক্তি খরচ করে, যা আবার 3G-এর চেয়ে বেশি ব্যয়বহুল।

এর মানে হলো যে, যদিও রেডিও প্রযুক্তির উপর ভিত্তি করে অন্তর্নিহিত রেডিও অবস্থা পরিবর্তিত হয়, সাধারণভাবে বলতে গেলে, উচ্চ ব্যান্ডউইথের রেডিওগুলির ক্ষেত্রে অবস্থা পরিবর্তনের টেইল-টাইমের কারণে ব্যাটারির উপর আপেক্ষিক প্রভাব বেশি পড়ে। টেইল-টাইম সম্পর্কে আরও তথ্যের জন্য, ‘দ্য রেডিও স্টেট মেশিন’ দেখুন।

একই সাথে, উচ্চতর ব্যান্ডউইথের অর্থ হলো আপনি আরও আগ্রাসীভাবে প্রিফেচ করতে পারেন, যার ফলে একই সময়ে আরও বেশি ডেটা ডাউনলোড করা যায়। বিষয়টি হয়তো ততটা সহজবোধ্য নয়, কিন্তু যেহেতু টেল-টাইম ব্যাটারির খরচ তুলনামূলকভাবে বেশি, তাই আপডেটের হার কমানোর জন্য প্রতিটি ট্রান্সফার সেশনে রেডিওকে দীর্ঘ সময়ের জন্য সক্রিয় রাখাও অধিকতর কার্যকর।

উদাহরণস্বরূপ, যদি একটি LTE রেডিওর ব্যান্ডউইথ এবং শক্তি খরচ 3G-এর দ্বিগুণ হয়, তাহলে আপনার প্রতিটি সেশনে চারগুণ বেশি ডেটা ডাউনলোড করা উচিত—বা সম্ভবত ১০ মেগাবাইট পর্যন্ত। এত বেশি ডেটা ডাউনলোড করার সময়, উপলব্ধ লোকাল স্টোরেজের উপর আপনার প্রিফেচিং-এর প্রভাব বিবেচনা করা এবং নিয়মিত আপনার প্রিফেচ ক্যাশ ফ্লাশ করা গুরুত্বপূর্ণ।

আপনি ডিফল্ট নেটওয়ার্কের জন্য একটি লিসেনার রেজিস্টার করতে ConnectivityManager এবং বর্তমান ডিভাইস সংযোগের ধরণ নির্ধারণ করতে একটি PhoneStateListener রেজিস্টার করার জন্য TelephonyManager ব্যবহার করতে পারেন। সংযোগের ধরণ জানা গেলে, আপনি সেই অনুযায়ী আপনার প্রিফেচিং রুটিনগুলো পরিবর্তন করতে পারেন:

কোটলিন

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
    }
}

}

জাভা

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 লাইব্রেরি ব্যবহার করে একটি কার্যকর সময়সূচীতে কাজ সম্পাদন করতে পারেন, যা নেটওয়ার্কের প্রাপ্যতা এবং পাওয়ার স্ট্যাটাসের মতো নির্দিষ্ট শর্ত পূরণ হয়েছে কিনা তা বিবেচনা করে। উদাহরণস্বরূপ, ধরুন আপনার DownloadHeadlinesWorker নামে একটি Worker সাবক্লাস আছে যা সর্বশেষ খবরের শিরোনামগুলো সংগ্রহ করে। এই ওয়ার্কারটিকে প্রতি ঘন্টায় চালানোর জন্য সময়সূচী নির্ধারণ করা যেতে পারে, যদি ডিভাইসটি একটি আনমিটারড নেটওয়ার্কের সাথে সংযুক্ত থাকে এবং ডিভাইসের ব্যাটারি কম না থাকে। ডেটা সংগ্রহে কোনো সমস্যা হলে একটি কাস্টম রিট্রাই স্ট্র্যাটেজি ব্যবহার করা যেতে পারে, যেমনটি নিচে দেখানো হয়েছে:

কোটলিন

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)

জাভা

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 ছাড়াও, অ্যান্ড্রয়েড প্ল্যাটফর্ম নেটওয়ার্কিং টাস্কগুলো সম্পন্ন করার জন্য একটি কার্যকর শিডিউল তৈরি করতে পোলিং-এর মতো আরও বেশ কিছু টুল প্রদান করে। এই টুলগুলো ব্যবহার সম্পর্কে আরও জানতে, ব্যাকগ্রাউন্ড প্রসেসিং গাইডটি দেখুন।

সার্ভার-প্রবর্তিত অনুরোধগুলি অপ্টিমাইজ করুন

সার্ভার-প্রবর্তিত অনুরোধগুলি সাধারণত সার্ভার থেকে আসা কোনো বিজ্ঞপ্তির প্রতিক্রিয়ায় ঘটে থাকে। উদাহরণস্বরূপ, সর্বশেষ সংবাদ নিবন্ধগুলি পড়ার জন্য ব্যবহৃত একটি অ্যাপ ব্যবহারকারীর ব্যক্তিগত পছন্দের সাথে মেলে এমন নতুন একগুচ্ছ নিবন্ধ সম্পর্কে একটি বিজ্ঞপ্তি পেতে পারে, যা অ্যাপটি তখন ডাউনলোড করে।

ফায়ারবেস ক্লাউড মেসেজিং ব্যবহার করে সার্ভার আপডেট পাঠান

ফায়ারবেস ক্লাউড মেসেজিং (FCM) হলো একটি হালকা ব্যবস্থা যা সার্ভার থেকে কোনো নির্দিষ্ট অ্যাপ ইনস্ট্যান্সে ডেটা প্রেরণের জন্য ব্যবহৃত হয়। FCM ব্যবহার করে, আপনার সার্ভার কোনো নির্দিষ্ট ডিভাইসে চলমান অ্যাপকে জানাতে পারে যে সেটির জন্য নতুন ডেটা উপলব্ধ হয়েছে।

পোলিং-এর তুলনায়, যেখানে নতুন ডেটার জন্য আপনার অ্যাপকে নিয়মিত সার্ভারকে পিং করতে হয়, এই ইভেন্ট-ড্রাইভেন মডেলটি আপনার অ্যাপকে কেবল তখনই একটি নতুন সংযোগ তৈরি করার সুযোগ দেয়, যখন এটি জানতে পারে যে ডাউনলোড করার মতো ডেটা রয়েছে। এই মডেলটি অপ্রয়োজনীয় সংযোগ কমিয়ে আনে এবং আপনার অ্যাপের মধ্যে তথ্য আপডেট করার সময় ল্যাটেন্সি হ্রাস করে।

FCM একটি স্থায়ী TCP/IP সংযোগ ব্যবহার করে বাস্তবায়িত হয়। এটি স্থায়ী সংযোগের সংখ্যা কমিয়ে আনে এবং প্ল্যাটফর্মকে ব্যান্ডউইথ অপ্টিমাইজ করতে ও ব্যাটারি লাইফের উপর এর সংশ্লিষ্ট প্রভাব কমাতে সাহায্য করে।