Mengelola penggunaan jaringan

Pelajaran ini menjelaskan cara membuat aplikasi yang memiliki kendali penuh dalam penggunaan referensi jaringan. Jika aplikasi Anda melakukan banyak operasi jaringan, Anda harus menyediakan setelan pengguna yang memungkinkan pengguna untuk mengontrol kebiasaan data aplikasi Anda, misalnya seberapa sering aplikasi Anda menyinkronkan data, apakah akan melakukan upload/download hanya saat terhubung dengan Wi-Fi, apakah akan menggunakan data saat roaming, dan sebagainya. Dengan menyediakan kontrol ini, pengguna jauh lebih kecil kemungkinannya untuk menonaktifkan akses aplikasi Anda ke data latar belakang ketika mendekati batas, karena mereka justru dapat mengontrol dengan tepat berapa banyak data yang digunakan aplikasi Anda.

Untuk mempelajari lebih lanjut tentang penggunaan jaringan aplikasi Anda, termasuk jumlah dan jenis koneksi jaringan selama periode waktu tertentu, baca Aplikasi web dan Memeriksa Traffic Jaringan dengan Network Profiler. Untuk panduan umum tentang cara menulis aplikasi yang meminimalkan dampak masa pakai baterai karena download dan koneksi jaringan, lihat Mengoptimalkan masa pakai baterai dan Mentransfer data tanpa menguras baterai.

Anda juga dapat melihat Contoh Network Connect.

Memeriksa koneksi jaringan perangkat

Perangkat dapat memiliki berbagai jenis koneksi jaringan. Pelajaran ini berfokus pada penggunaan Wi-Fi atau koneksi jaringan seluler. Untuk daftar lengkap kemungkinan jenis jaringan, lihat ConnectivityManager.

Wi-Fi biasanya lebih cepat. Selain itu, data seluler sering diukur, yang bisa menjadi mahal. Strategi umum untuk aplikasi adalah hanya mengambil data besar jika jaringan Wi-Fi tersedia.

Sebelum melakukan operasi jaringan, sebaiknya Anda memeriksa keadaan konektivitas jaringan. Antara lain, ini bisa mencegah aplikasi Anda secara tidak sengaja menggunakan jaringan yang salah. Jika koneksi jaringan tidak tersedia, aplikasi Anda harus merespons dengan baik. Untuk memeriksa koneksi jaringan, Anda biasanya menggunakan kelas-kelas berikut:

  • ConnectivityManager: Menjawab pertanyaan tentang status konektivitas jaringan Juga memberi tahu aplikasi bila konektivitas jaringan berubah.
  • NetworkInfo: Menjelaskan status antarmuka jaringan dari tipe yang diberikan (saat ini Seluler atau Wi-Fi).

Cuplikan kode ini menguji konektivitas jaringan untuk Wi-Fi dan seluler. Ini menentukan apakah antarmuka jaringan ini tersedia atau tidak (yaitu, apakah konektivitas jaringannya memungkinkan) dan/atau terhubung (yaitu, apakah konektivitas jaringan ada dan apakah mungkin untuk membuat soket dan meneruskan data):

Kotlin

private const val DEBUG_TAG = "NetworkStatusExample"
...
val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
var isWifiConn: Boolean = false
var isMobileConn: Boolean = false
connMgr.allNetworks.forEach { network ->
    connMgr.getNetworkInfo(network).apply {
        if (type == ConnectivityManager.TYPE_WIFI) {
            isWifiConn = isWifiConn or isConnected
        }
        if (type == ConnectivityManager.TYPE_MOBILE) {
            isMobileConn = isMobileConn or isConnected
        }
    }
}
Log.d(DEBUG_TAG, "Wifi connected: $isWifiConn")
Log.d(DEBUG_TAG, "Mobile connected: $isMobileConn")

Java

private static final String DEBUG_TAG = "NetworkStatusExample";
...
ConnectivityManager connMgr =
        (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
boolean isWifiConn = false;
boolean isMobileConn = false;
for (Network network : connMgr.getAllNetworks()) {
    NetworkInfo networkInfo = connMgr.getNetworkInfo(network);
    if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
        isWifiConn |= networkInfo.isConnected();
    }
    if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
        isMobileConn |= networkInfo.isConnected();
    }
}
Log.d(DEBUG_TAG, "Wifi connected: " + isWifiConn);
Log.d(DEBUG_TAG, "Mobile connected: " + isMobileConn);

Perhatikan bahwa Anda tidak boleh mendasarkan keputusan pada apakah suatu jaringan "tersedia" atau tidak. Anda harus selalu memeriksa isConnected() sebelum melakukan operasi jaringan, karena isConnected() menangani kasus-kasus seperti jaringan seluler yang tidak bisa diandalkan, mode pesawat, dan data latar belakang terbatas.

Cara yang lebih efisien untuk memeriksa apakah antarmuka jaringan tersedia adalah sebagai berikut. Metode getActiveNetworkInfo() mengembalikan instance NetworkInfo yang mewakili antarmuka jaringan terhubung pertama yang dapat ditemukan, atau null jika tidak ada antarmuka yang terhubung (artinya koneksi internet tidak tersedia):

Kotlin

fun isOnline(): Boolean {
    val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val networkInfo: NetworkInfo? = connMgr.activeNetworkInfo
    return networkInfo?.isConnected == true
}

Java

public boolean isOnline() {
    ConnectivityManager connMgr = (ConnectivityManager)
            getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    return (networkInfo != null && networkInfo.isConnected());
}

Untuk meminta status yang lebih detail, Anda dapat menggunakan NetworkInfo.DetailedState, tetapi ini jarang diperlukan.

Mengelola penggunaan jaringan

Anda dapat menerapkan aktivitas preferensi yang memberi pengguna kontrol eksplisit atas penggunaan resource jaringan aplikasi Anda. Sebagai contoh:

  • Anda mungkin mengizinkan pengguna untuk mengupload video hanya ketika perangkat tersebut terhubung ke jaringan Wi-Fi.
  • Anda dapat menyinkronkan (atau tidak menyinkronkan) bergantung pada kriteria tertentu, seperti ketersediaan jaringan, interval waktu, dan sebagainya.

Untuk menulis aplikasi yang mendukung akses jaringan dan mengelola penggunaan jaringan, manifes Anda harus memiliki izin dan filter intent yang tepat.

  • Manifes yang dikutip di bawah ini mencakup izin berikut:
  • Anda dapat mendeklarasikan filter intent untuk tindakan ACTION_MANAGE_NETWORK_USAGE (diperkenalkan di Android 4.0) untuk menunjukkan bahwa aplikasi Anda menentukan aktivitas yang menawarkan opsi untuk mengontrol penggunaan data. ACTION_MANAGE_NETWORK_USAGE menunjukkan pengaturan untuk mengelola penggunaan data jaringan dari aplikasi tertentu. Saat aplikasi Anda memiliki aktivitas pengaturan yang memungkinkan pengguna mengontrol penggunaan jaringan, Anda harus mendeklarasikan filter intent ini untuk aktivitas tersebut. Dalam aplikasi contoh, tindakan ini ditangani oleh kelas SettingsActivity, yang menampilkan UI preferensi untuk memungkinkan pengguna memutuskan kapan harus mendownload feed.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.networkusage"
    ...>

    <uses-sdk android:minSdkVersion="4"
           android:targetSdkVersion="14" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        ...>
        ...
        <activity android:label="SettingsActivity" android:name=".SettingsActivity">
             <intent-filter>
                <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
                <category android:name="android.intent.category.DEFAULT" />
          </intent-filter>
        </activity>
    </application>
</manifest>

Menerapkan aktivitas preferensi

Seperti yang Anda lihat dalam kutipan manifes di atas, aktivitas aplikasi contoh SettingsActivity memiliki filter intent untuk tindakan ACTION_MANAGE_NETWORK_USAGE. SettingsActivity adalah subkelas dari PreferenceActivity. Ini menampilkan layar preferensi (ditunjukkan pada gambar 1) yang memungkinkan pengguna menentukan hal-hal berikut:

  • Apakah akan menampilkan ringkasan untuk setiap entri feed XML, atau hanya link untuk setiap entri.
  • Apakah akan mendownload umpan XML jika koneksi jaringan tersedia, atau hanya jika Wi-Fi tersedia.
Panel preferensi Menyetel preferensi jaringan

Gambar 1. Aktivitas preferensi.

Berikut adalah SettingsActivity. Perhatikan bahwa ini menerapkan OnSharedPreferenceChangeListener. Ketika seorang pengguna mengubah suatu preferensi, maka akan mengaktifkan onSharedPreferenceChanged(), yang membuat refreshDisplay menjadi true. Ini menyebabkan tampilan direfresh ketika pengguna kembali ke aktivitas utama:

Kotlin

class SettingsActivity : PreferenceActivity(), OnSharedPreferenceChangeListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Loads the XML preferences file
        addPreferencesFromResource(R.xml.preferences)
    }

    override fun onResume() {
        super.onResume()

        // Registers a listener whenever a key changes
        preferenceScreen?.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
    }

    override fun onPause() {
        super.onPause()

        // Unregisters the listener set in onResume().
        // It's best practice to unregister listeners when your app isn't using them to cut down on
        // unnecessary system overhead. You do this in onPause().
        preferenceScreen?.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
    }

    // When the user changes the preferences selection,
    // onSharedPreferenceChanged() restarts the main activity as a new
    // task. Sets the refreshDisplay flag to "true" to indicate that
    // the main activity should update its display.
    // The main activity queries the PreferenceManager to get the latest settings.

    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
        // Sets refreshDisplay to true so that when the user returns to the main
        // activity, the display refreshes to reflect the new settings.
        NetworkActivity.refreshDisplay = true
    }
}

Java

public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {

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

        // Loads the XML preferences file
        addPreferencesFromResource(R.xml.preferences);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Registers a listener whenever a key changes
        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
    }

    @Override
    protected void onPause() {
        super.onPause();

       // Unregisters the listener set in onResume().
       // It's best practice to unregister listeners when your app isn't using them to cut down on
       // unnecessary system overhead. You do this in onPause().
       getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
    }

    // When the user changes the preferences selection,
    // onSharedPreferenceChanged() restarts the main activity as a new
    // task. Sets the refreshDisplay flag to "true" to indicate that
    // the main activity should update its display.
    // The main activity queries the PreferenceManager to get the latest settings.

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        // Sets refreshDisplay to true so that when the user returns to the main
        // activity, the display refreshes to reflect the new settings.
        NetworkActivity.refreshDisplay = true;
    }
}

Menanggapi perubahan preferensi

Ketika pengguna mengubah preferensi di layar pengaturan, biasanya akan memiliki konsekuensi untuk perilaku aplikasi. Dalam cuplikan ini, aplikasi memeriksa pengaturan preferensi di onStart(). jika ada kecocokan antara setelan dan koneksi jaringan perangkat (misalnya, jika setelannya adalah "Wi-Fi" dan perangkat memiliki koneksi Wi-Fi), aplikasi mendownload feed dan merefresh tampilan.

Kotlin

class NetworkActivity : Activity() {

    // The BroadcastReceiver that tracks network connectivity changes.
    private lateinit var receiver: NetworkReceiver

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Registers BroadcastReceiver to track network connection changes.
        val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
        receiver = NetworkReceiver()
        this.registerReceiver(receiver, filter)
    }

    public override fun onDestroy() {
        super.onDestroy()
        // Unregisters BroadcastReceiver when app is destroyed.
        this.unregisterReceiver(receiver)
    }

    // Refreshes the display if the network connection and the
    // pref settings allow it.

    public override fun onStart() {
        super.onStart()

        // Gets the user's network preference settings
        val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)

        // Retrieves a string value for the preferences. The second parameter
        // is the default value to use if a preference value is not found.
        sPref = sharedPrefs.getString("listPref", "Wi-Fi")

        updateConnectedFlags()

        if (refreshDisplay) {
            loadPage()
        }
    }

    // Checks the network connection and sets the wifiConnected and mobileConnected
    // variables accordingly.
    fun updateConnectedFlags() {
        val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

        val activeInfo: NetworkInfo? = connMgr.activeNetworkInfo
        if (activeInfo?.isConnected == true) {
            wifiConnected = activeInfo.type == ConnectivityManager.TYPE_WIFI
            mobileConnected = activeInfo.type == ConnectivityManager.TYPE_MOBILE
        } else {
            wifiConnected = false
            mobileConnected = false
        }
    }

    // Uses AsyncTask subclass to download the XML feed from stackoverflow.com.
    fun loadPage() {
        if (sPref == ANY && (wifiConnected || mobileConnected) || sPref == WIFI && wifiConnected) {
            // AsyncTask subclass
            DownloadXmlTask().execute(URL)
        } else {
            showErrorPage()
        }
    }

    companion object {

        const val WIFI = "Wi-Fi"
        const val ANY = "Any"
        const val SO_URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort;=newest"

        // Whether there is a Wi-Fi connection.
        private var wifiConnected = false
        // Whether there is a mobile connection.
        private var mobileConnected = false
        // Whether the display should be refreshed.
        var refreshDisplay = true
    
        // The user's current network preference setting.
        var sPref: String? = null
    }
...

}

Java

public class NetworkActivity extends Activity {
    public static final String WIFI = "Wi-Fi";
    public static final String ANY = "Any";
    private static final String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort;=newest";

    // Whether there is a Wi-Fi connection.
    private static boolean wifiConnected = false;
    // Whether there is a mobile connection.
    private static boolean mobileConnected = false;
    // Whether the display should be refreshed.
    public static boolean refreshDisplay = true;

    // The user's current network preference setting.
    public static String sPref = null;

    // The BroadcastReceiver that tracks network connectivity changes.
    private NetworkReceiver receiver = new NetworkReceiver();

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

        // Registers BroadcastReceiver to track network connection changes.
        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        receiver = new NetworkReceiver();
        this.registerReceiver(receiver, filter);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // Unregisters BroadcastReceiver when app is destroyed.
        if (receiver != null) {
            this.unregisterReceiver(receiver);
        }
    }

    // Refreshes the display if the network connection and the
    // pref settings allow it.

    @Override
    public void onStart () {
        super.onStart();

        // Gets the user's network preference settings
        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);

        // Retrieves a string value for the preferences. The second parameter
        // is the default value to use if a preference value is not found.
        sPref = sharedPrefs.getString("listPref", "Wi-Fi");

        updateConnectedFlags();

        if(refreshDisplay){
            loadPage();
        }
    }

    // Checks the network connection and sets the wifiConnected and mobileConnected
    // variables accordingly.
    public void updateConnectedFlags() {
        ConnectivityManager connMgr = (ConnectivityManager)
                getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
        if (activeInfo != null && activeInfo.isConnected()) {
            wifiConnected = activeInfo.getType() == ConnectivityManager.TYPE_WIFI;
            mobileConnected = activeInfo.getType() == ConnectivityManager.TYPE_MOBILE;
        } else {
            wifiConnected = false;
            mobileConnected = false;
        }
    }

    // Uses AsyncTask subclass to download the XML feed from stackoverflow.com.
    public void loadPage() {
        if (((sPref.equals(ANY)) && (wifiConnected || mobileConnected))
                || ((sPref.equals(WIFI)) && (wifiConnected))) {
            // AsyncTask subclass
            new DownloadXmlTask().execute(URL);
        } else {
            showErrorPage();
        }
    }
...

}

Mendeteksi perubahan koneksi

Bagian terakhir masalah ini adalah subkelas BroadcastReceiver, NetworkReceiver. Ketika koneksi jaringan perangkat berubah, NetworkReceiver mengintersepsi tindakan CONNECTIVITY_ACTION, menentukan apa status koneksi jaringan itu, dan menyetel tanda wifiConnected dan mobileConnected akan menjadi true/false. Hasilnya adalah saat berikutnya pengguna kembali ke aplikasi, aplikasi tersebut hanya akan mendownload feed terbaru dan memperbarui tampilan jika NetworkActivity.refreshDisplay disetel ke true.

Menyiapkan BroadcastReceiver yang dipanggil tidak perlu menguras resource sistem. Aplikasi contoh mendaftarkan BroadcastReceiver NetworkReceiver di onCreate(), dan membatalkan registrasi dalam onDestroy(). Ini lebih ringan daripada mendeklarasikan <receiver> dalam manifes. Saat Anda mendeklarasikan <receiver> dalam manifes, maka dapat mengaktifkan aplikasi Anda kapan saja, meskipun Anda belum menjalankannya selama berminggu-minggu. Dengan mendaftar dan batal mendaftar NetworkReceiver dalam aktivitas utama, Anda memastikan bahwa aplikasi tidak akan diaktifkan setelah pengguna meninggalkan aplikasi. Jika Anda mendeklarasikan <receiver> di manifes dan Anda tahu persis di mana Anda membutuhkannya, Anda dapat menggunakan setComponentEnabledSetting() untuk mengaktifkan dan menonaktifkannya sesuai keperluan.

Berikut adalah NetworkReceiver.

Kotlin

class NetworkReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        val conn = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val networkInfo: NetworkInfo? = conn.activeNetworkInfo

        // Checks the user prefs and the network connection. Based on the result, decides whether
        // to refresh the display or keep the current display.
        // If the userpref is Wi-Fi only, checks to see if the device has a Wi-Fi connection.
        if (WIFI == sPref && networkInfo?.type == ConnectivityManager.TYPE_WIFI) {
            // If device has its Wi-Fi connection, sets refreshDisplay
            // to true. This causes the display to be refreshed when the user
            // returns to the app.
            refreshDisplay = true
            Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show()

            // If the setting is ANY network and there is a network connection
            // (which by process of elimination would be mobile), sets refreshDisplay to true.
        } else if (ANY == sPref && networkInfo != null) {
            refreshDisplay = true

            // Otherwise, the app can't download content--either because there is no network
            // connection (mobile or Wi-Fi), or because the pref setting is WIFI, and there
            // is no Wi-Fi connection.
            // Sets refreshDisplay to false.
        } else {
            refreshDisplay = false
            Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show()
        }
    }
}

Java

public class NetworkReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        ConnectivityManager conn =  (ConnectivityManager)
            context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = conn.getActiveNetworkInfo();

        // Checks the user prefs and the network connection. Based on the result, decides whether
        // to refresh the display or keep the current display.
        // If the userpref is Wi-Fi only, checks to see if the device has a Wi-Fi connection.
        if (WIFI.equals(sPref) && networkInfo != null
            && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
            // If device has its Wi-Fi connection, sets refreshDisplay
            // to true. This causes the display to be refreshed when the user
            // returns to the app.
            refreshDisplay = true;
            Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show();

        // If the setting is ANY network and there is a network connection
        // (which by process of elimination would be mobile), sets refreshDisplay to true.
        } else if (ANY.equals(sPref) && networkInfo != null) {
            refreshDisplay = true;

        // Otherwise, the app can't download content--either because there is no network
        // connection (mobile or Wi-Fi), or because the pref setting is WIFI, and there
        // is no Wi-Fi connection.
        // Sets refreshDisplay to false.
        } else {
            refreshDisplay = false;
            Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show();
        }
    }
}