android 关于连接到网络的知识

声明:本文主要是参考 android官方API说明文档来的。

本课程主要介绍了如何连接网络,如何监控网络连接(包括网络连接的更改),还介绍了如何让用户控制网络使用率。同时也介绍了如何解析和使用XML数据。

通过本课程的学习,你将能够创建高效的网络使用率(在下载内容和解析数据的时候)的Android程序,能够最大限度的减少网络流量。

-------------------------------------------------------------------------

一、连接到网络

本节通过讲述一个简单的程序来阐述Android的网络连接。它讲述了网络连接的最佳实践,在你创建简单的网络连接时应该遵循。

请注意,执行本节课所描述的网络操作,应用程序清单必须包括下列权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
①选择HTTP Client

大多数Android程序的网络连接使用HTTP来接收和发送数据。Android包括两个HTTP 客户端:HttpURLConnection和Apache 的 HttpClient。这个两个客户端都支持HTTPS、流媒体上传和下载、配置超时时间、IPv6和连接池(connection pooling)。我们(官方)推荐在android 2.3和更高版本中使用HttpURLConnection。

②检查网络连接

在你的app尝试连接网络前,app应该使用getActiveNetworkInfo() 和isConnected()检测网络连接是否可用。需要注意,用户的设备可能离开了网络连接范围或者用户关闭了WI-FI和移动数据连接。更多关于网络连接的内容请查看: Managing Network Usage

public void myClickHandler(View view) {
    ...
    ConnectivityManager connMgr = (ConnectivityManager) 
        getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isConnected()) {
        // 联网取得数据
    } else {
        //显示网络连接错误
    }
    ...
}
③在单独的线程中执行网络操作

为了提高用户体验,网络操作需要在单独的线程中进行(不能在UI线程中)。异步任务 AsyncTask类提供了一个简单的方法来在UI线程外生成一个新的线程(任务)。

在下面的例子中,myClickHandler()方法提供了new DownloadWebpageTask().execute(stringUrl)   DownloadWebpageTask     是AsyncTask的子类 DownloadWebpageTask   实现了AsyncTask中的几个方法:

doInBackground() 方法执行 downloadUrl()       ,这个方法来根据URL下载web页面需要的数据,当执行完后返回一个字符串。

     onPostExecute()    获得字符串结果,并把它显示到UI上。

public class HttpExampleActivity extends Activity {
    private static final String DEBUG_TAG = "HttpExample";
    private EditText urlText;
    private TextView textView;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);   
        urlText = (EditText) findViewById(R.id.myUrl);
        textView = (TextView) findViewById(R.id.myText);
    }

    // When user clicks button, calls AsyncTask.
    // Before attempting to fetch the URL, makes sure that there is a network connection.
    public void myClickHandler(View view) {
        // Gets the URL from the UI's text field.
        String stringUrl = urlText.getText().toString();
        ConnectivityManager connMgr = (ConnectivityManager) 
            getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
        if (networkInfo != null && networkInfo.isConnected()) {
            new DownloadWebpageTask().execute(stringUrl);
        } else {
            textView.setText("No network connection available.");
        }
    }

     // Uses AsyncTask to create a task away from the main UI thread. This task takes a 
     // URL string and uses it to create an HttpUrlConnection. Once the connection
     // has been established, the AsyncTask downloads the contents of the webpage as
     // an InputStream. Finally, the InputStream is converted into a string, which is
     // displayed in the UI by the AsyncTask's onPostExecute method.
     private class DownloadWebpageTask extends AsyncTask<String, Void, String> {
        @Override
        protected String doInBackground(String... urls) {
              
            // params comes from the execute() call: params[0] is the url.
            try {
                return downloadUrl(urls[0]);
            } catch (IOException e) {
                return "Unable to retrieve web page. URL may be invalid.";
            }
        }
        // onPostExecute displays the results of the AsyncTask.
        @Override
        protected void onPostExecute(String result) {
            textView.setText(result);
       }
    }
    ...
}

④连接和下载数据

在异步线程中进行网络操作,你可以用HttpURLConnection  来执行GET 操作来下载数据。等调用connect()  方法后,你就可以调用getInputStream()  方法来获得下载数据的InputStream  。

在下面的代码片段中,在doInBackground()  方法中调用downloadUrl()  方法。downloadUrl() 方法使用指定的URL通过HttpURLConnection  来连接网络。一旦连接建立,app调用getInputStream()  来接受数据的InputStream  。

// Given a URL, establishes an HttpUrlConnection and retrieves
// the web page content as a InputStream, which it returns as
// a string.
private String downloadUrl(String myurl) throws IOException {
    InputStream is = null;
    // Only display the first 500 characters of the retrieved
    // web page content.
    int len = 500;
        
    try {
        URL url = new URL(myurl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setReadTimeout(10000 /* milliseconds */);
        conn.setConnectTimeout(15000 /* milliseconds */);
        conn.setRequestMethod("GET");
        conn.setDoInput(true);
        // Starts the query
        conn.connect();
        int response = conn.getResponseCode();
        Log.d(DEBUG_TAG, "The response is: " + response);
        is = conn.getInputStream();

        // Convert the InputStream into a string
        String contentAsString = readIt(is, len);
        return contentAsString;
        
    // Makes sure that the InputStream is closed after the app is
    // finished using it.
    } finally {
        if (is != null) {
            is.close();
        } 
    }
}
需要注意的是   getResponseCode()  方法返回网络连接的 status code  (状态码),这个是非常有用的,通过这个返回的状态码,能够获得连接的附加信息。比如 状态码为 200带表连接成功,服务器应答成功。

⑤将InputStream 转换成String

一个InputStream 是一个可读的bytes。一旦你得到一个InputStream ,  通常就是把它解码或者转换成目标数据类型。例如,你正在下载图片,你可以解码并把它显示出来,如下:

InputStream is = null;
...
Bitmap bitmap = BitmapFactory.decodeStream(is);
ImageView imageView = (ImageView) findViewById(R.id.image_view);
imageView.setImageBitmap(bitmap);
在上面的例子中, InputStream  代表web页面的text类型数据。下面的代码展示了怎么转换   InputStream  成字符串来让activity在UI线程中显示。

// Reads an InputStream and converts it to a String.
public String readIt(InputStream stream, int len) throws IOException, UnsupportedEncodingException {
    Reader reader = null;
    reader = new InputStreamReader(stream, "UTF-8");        
    char[] buffer = new char[len];
    reader.read(buffer);
    return new String(buffer);
}
二、管理网络连接

这个主要是介绍如何控制网络资源的使用。如果您的应用程序执行大量的网络操作,你应该提供一个网络设置来让使用者控制程序应该怎么使用网络。比如,你的程序同步数据的频率、是否只在wi-fi下来上传/下载数据、当处于漫游状态时怎么来使用网络数据,等等。当提供这些设置后,使用者就不太可能会禁用你的程序的后台数据连接当使用者

的数据流量不充足时。因为使用者能够通过设置来精确的控制你的程序对网络数据的使用。

①检测设备的网络连接

设备有很多种网络连接类型。本教程主要关注是否是 wi-fi和移动网络连接。要查看所有网络连接类型请查看ConnectivityManager

Wi-Fi连接通常更快速,移动网络通常是昂贵的,并且是限量的,通常情况下应该在Wi-Fi网络情况下进行大数据的网络传输。

当要执行网络操作时,最好进行网络状态的检测。在执行网络操作时进行网络状态检测,能够让你的程序避免得到错误的结果,并且能够省电。如果网络连接不可用,你的程序应该优雅的响应。检测网络状态通常用到下面几个类:

ConnectivityManager  :回答了关于网络连接的状态查询。它还通知应用程序时的网络连接的变化。

NetworkInfo  :描述网络连接类型,目前只有两种(Wi-Fi或者移动网络)。

这段代码的测试网络连通性的Wi-Fi和移动。它确定这些网络接口是否可用(即,网络连接是否是可能的)或连接(即,网络连接是否存在以及是否有可能建立套接字和数据传递):

private static final String DEBUG_TAG = "NetworkStatusExample";
...      
ConnectivityManager connMgr = (ConnectivityManager) 
        getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI); 
boolean isWifiConn = networkInfo.isConnected();
networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
boolean isMobileConn = networkInfo.isConnected();
Log.d(DEBUG_TAG, "Wifi connected: " + isWifiConn);
Log.d(DEBUG_TAG, "Mobile connected: " + isMobileConn);
也可以这么写:

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

需要注意的是,在执行网络操作之前,你不能只是检测网络连接是否存在,还应该检测连接是否可用(能够连接进行数据传输) isConnected()   ,因为 isConnected() 后能够排除飞行模式、受限的后台数据连接情况。

检测网络是否可用的一个简单方法如下。该方法getActiveNetworkInfo() 该方法返回  NetworkInfo     表示一个网络连接的接口,或者返回null(如果没有网络接口,即网络连接不可用)。

public boolean isOnline() {
    ConnectivityManager connMgr = (ConnectivityManager) 
            getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    return (networkInfo != null && networkInfo.isConnected());
}  
若要查询更细粒度的状态,你可以使用 NetworkInfo.DetailedState,但是这应该很少使用的。

②管理网络使用

你可以实现一个偏好设置activity,让用户显式控制网络资源的应用程序的使用情况。例如:

  • 你可能会允许用户上传视频,只有当设备连接到Wi-Fi网络。
  • 你可能同步(或不),这取决于特定的标准,例如网络可用性,时间间隔,等等。

要编写一个支持网络访问和管理网络使用的应用程序,你的manifest文件中必须有正确的权限和意图过滤器。 

要在mainifest中声明如下权限:

在mainifest中声明意图过滤器如下:

有网络设置的activity要声明action为ACTION_MANAGE_NETWORK_USAGE ,声明了这个后,能够在系统的后台数据设置那里 打开activity。   本示例中的activity是 SettingsActivity 。如下:

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

  SettingsActivity 是  PreferenceActivity 的子类, 能够让用户进行如下设置:

  • 是否显示每一个XML feed条目的摘要或者只是显示条目的连接。
  • 是否只是在Wi-Fi下载XML提要,或者在Wi-Fi和移动网络下都可以下载。
Preferences panelSetting a network preference

 

下面是SettingsActivity   的实现,需要注意的是,它实现了 OnSharedPreferenceChangeListener 接口,当用户改变设置的时候会回调这个接口来设置refreshDisplay  为true。这将导致当用户再次回到 main activity的时候来刷新页面。

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

接下来就讲述如何相应用户的设置,当用户在设置页改变了设置后,通常就会影响程序的行为。在下面代码片段中程序会在onStart() 方法中检测设置的改变 。如果用户设置项和当前设备网络连接状态相匹配,程序会根据用户的设置来下载XML和刷新显示。

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

③监听设备网络连接的改变

最后一块要讲的是BroadcastReceiver 的子类NetworkReceiver    当设备网络连接改变后,系统会发送广播,NetworkReceiver  会拦截action为 CONNECTIVITY_ACTION  的广播(就是设备网络连接变化的广播),来获得当前的网络连接是什么状态,相应的改变wifiConnected 和mobileConnected  标志为true或者false,并根据用户的设置来标记NetworkActivity.refreshDisplay 为true或这false,当用户再次回到程序的时候,能够更新显示内容。

建立不必要的BrodcastReceiver会消耗系统资源该实例中,示例中的NetworkReceiver  在 onCreate() 方法中进行 注册,在onDestroy() 方法中接触注册。它比在manifest中声明的更轻巧,如果在manifest中声明BroadcastReceiver,它会在任何必要的时候唤醒你的应用程序,即使你的程序没有启动,通过在activity的onCreate() 中注册,在 onDestroy() 解除注册,可以确保程序不会在用户离开后被唤醒。如果确实需要在manifest中通过<receiver>标签来声明BroadcastReceiver,你可以调用setComponentEnabledSetting() 方法来启用或者禁用它。

 NetworkReceiver 的代码实现:

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









  

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值