Gerenciar o uso da rede

Esta lição descreve como desenvolver aplicativos que têm controle preciso do uso dos recursos de rede. Caso seu aplicativo realize muitas operações de rede, forneça configurações que permitam aos usuários controlar os hábitos de dados do aplicativo. Por exemplo, a frequência de sincronização, fazer upload/download apenas quando estiver conectado ao Wi-Fi, usar dados quando estiver em roaming etc. Com esses controles, é menos provável que os usuários desativem o acesso do seu aplicativo a dados em segundo plano ao se aproximarem do limite de dados, já que poderão controlar com precisão a quantidade de dados usada pelo aplicativo.

Para saber mais sobre o uso de rede do seu aplicativo, incluindo o número e os tipos de conexões de rede durante um período, leia Apps da Web e Inspecionar o tráfego de rede com o Network Profiler. Para orientações gerais sobre como escrever aplicativos que minimizam o impacto da duração da bateria em relação a downloads e conexões de rede, consulte Otimizar a duração da bateria e Transferir dados sem descarregar a bateria.

Você também pode conferir o exemplo de conexão de rede.

Verificar a conexão de rede de um dispositivo

Um dispositivo pode ter vários tipos de conexões de rede. Esta lição se concentra no uso de uma conexão de rede Wi-Fi ou móvel. Para ver uma lista completa dos tipos de rede, consulte ConnectivityManager.

Em geral, o Wi-Fi é o tipo de rede mais rápido. Além disso, os dados móveis costumam ser medidos, o que pode gerar gastos altos. Uma estratégia comum para o uso de aplicativos é só buscar dados grandes se houver uma rede Wi-Fi disponível.

Antes de executar operações de rede, é recomendável verificar o estado da conectividade. Entre outras coisas, isso pode impedir que seu aplicativo use acidentalmente o rádio errado. Se uma conexão de rede não estiver disponível, seu aplicativo deverá responder normalmente. Em geral, você usará estas classes para verificar a conexão de rede:

  • ConnectivityManager: responde a consultas sobre o estado da conectividade de rede. Ela também notifica os aplicativos quando a conectividade de rede é alterada.
  • NetworkInfo: descreve o status de uma interface de rede de um determinado tipo (atualmente, móvel ou Wi-Fi).

Esse snippet de código testa a conectividade de redes Wi-Fi e móveis. Ele determina se essas interfaces de rede estão disponíveis, ou seja, se a conectividade de rede é possível. Além disso, ele verifica se elas estão conectadas, ou seja, se existe conectividade de rede e se é possível estabelecer soquetes e transmitir dados:

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);

Você não deve basear suas decisões somente na indicação de que uma rede está “disponível”. Sempre verifique isConnected() antes de executar operações de rede, já que isConnected() lida com casos como redes móveis inconsistentes, modo avião ativado e dados em segundo plano restritos.

Veja a seguir uma maneira mais rápida de verificar se uma interface de rede está disponível. O método getActiveNetworkInfo() retorna uma instância NetworkInfo que representa a primeira interface de rede conectada que encontra ou null, caso nenhuma das interfaces esteja conectada (o que significa que não há uma conexão com a Internet disponível):

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());
}

Para consultar mais detalhes, você pode usar NetworkInfo.DetailedState. No entanto, isso raramente será necessário.

Gerenciar o uso da rede

Você pode implementar uma atividade de preferências que fornece aos usuários controle explícito sobre o uso de recursos de rede do seu aplicativo. Por exemplo:

  • Você pode permitir que os usuários façam upload de vídeos somente quando o dispositivo estiver conectado a uma rede Wi-Fi.
  • Você pode escolher fazer a sincronização com base em critérios específicos, como disponibilidade de rede, intervalo de tempo e assim por diante.

Para criar um aplicativo compatível com o acesso à rede e o gerenciamento do uso de rede, seu manifesto precisa ter as permissões e os filtros de intents corretos.

  • O trecho do manifesto abaixo inclui as seguintes permissões:
  • Você pode declarar o filtro de intents para a ação ACTION_MANAGE_NETWORK_USAGE (introduzida no Android 4.0) e indicar que o aplicativo define uma atividade que oferece opções para controlar o uso de dados. ACTION_MANAGE_NETWORK_USAGE mostra configurações para gerenciar o uso de dados de rede de um aplicativo específico. Quando seu aplicativo tiver uma atividade de configurações que permite aos usuários controlar o uso da rede, declare o filtro de intents dessa atividade. No aplicativo de exemplo, essa ação é gerenciada pela classe SettingsActivity, que exibe uma IU de preferências para permitir que os usuários decidam quando fazer o download de um 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>

Implementar uma atividade de preferência

Como você pode ver no trecho do manifesto acima, a atividade do aplicativo de amostra SettingsActivity tem um filtro de intents para a ação ACTION_MANAGE_NETWORK_USAGE. SettingsActivity é a subclasse de PreferenceActivity. Ele exibe uma tela de preferências (mostrada na figura 1) que permite aos usuários especificar:

  • se querem exibir resumos para cada entrada de feed XML ou somente um link para cada entrada;
  • se querem fazer o download do feed XML caso haja conexão de rede disponível ou somente se o Wi-Fi estiver disponível.
Painel de preferências Configurar uma preferência de rede

Figura 1. Atividade de preferências.

Veja aqui a SettingsActivity. Isso implementa OnSharedPreferenceChangeListener. Quando um usuário altera uma preferência, ela aciona onSharedPreferenceChanged(), que define refreshDisplay como verdadeiro. Isso fará com que a exibição seja atualizada quando o usuário retornar à atividade principal:

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

Responder às alterações de preferência

Quando o usuário altera as preferências na tela de configurações, normalmente isso afeta o comportamento do aplicativo. Neste snippet, o aplicativo verifica as configurações de preferências em onStart(). Se houver uma correspondência entre a configuração e a conexão de rede do dispositivo (por exemplo, se a configuração for "Wi-Fi", e o dispositivo tiver uma conexão Wi-Fi), o aplicativo fará o download do feed e atualizará a exibição.

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();
        }
    }
...

}

Detectar alterações de conexão

A peça final do quebra-cabeça é a subclasse BroadcastReceiver, NetworkReceiver. Quando a conexão de rede do dispositivo mudar, NetworkReceiver interceptará a ação CONNECTIVITY_ACTION, determinará qual é o status da conexão de rede e definirá apropriadamente os sinalizadores wifiConnected e mobileConnected como verdadeiros/falsos. Assim, na próxima vez que o usuário retornar, o aplicativo só fará o download do feed mais recente e atualizará a exibição se NetworkActivity.refreshDisplay estiver definido como true.

Configurar um BroadcastReceiver chamado desnecessariamente pode sobrecarregar os recursos do sistema. O aplicativo de exemplo faz o registro de BroadcastReceiver NetworkReceiver em onCreate() e desfaz essa ação em onDestroy(). Isso é mais leve do que declarar um <receiver> no manifesto. Se você declarar um <receiver> no manifesto, ele poderá ativar seu aplicativo a qualquer momento, mesmo que você não o execute há semanas. Ao fazer e desfazer o registro de NetworkReceiver na atividade principal, você garante que o aplicativo não será ativado depois que o usuário sair dele. Se você declarar um <receiver> no manifesto e souber exatamente onde ele é necessário, use setComponentEnabledSetting() para ativá-lo e desativá-lo, conforme apropriado.

Este é o 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();
        }
    }
}