Hướng dẫn lập trình mạng trong Android - Android Networking
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Giới thiệu

Tài liệu được viết dựa trên:
  • Android Studio 1.5

2- Tổng quan lập trình mạng Android

Trong Android lập trình mạng bao gồm gửi yêu cầu tới máy chủ và lấy dữ liệu trả về. Về cơ bản bạn có 2 API để làm việc với mạng:

Apache HttpClient:

  • Đây là một thư viện mã nguồn mở cung cấp bởi Apache.

HttpURLConnection:

  • Đây là một API chính thức của Android, nó bắt đầu được đưa vào từ phiên bản Android 2.3, trước đó Android sử dụng Apache HttpClient để làm việc với mạng.
Để làm việc với mạng yêu cầu bạn cần cấp một số quyền cho ứng dụng:
<uses-permission android:name="android.permission.INTERNET"/>

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
android.permission.INTERNET:
  • khai báo permission này, cho phép ứng dụng của bạn sẽ có khả năng kết nối mạng.

android.permission.ACCESS_NETWORK_STATE:
  • Cho phép ứng dụng của bạn kiểm tra trạng thái kết nối mạng của điện thoại.

Đoạn code sau đây kiểm tra điện thoại của người dùng có kết nối mạng không, phương thức trả về true nếu đã có kết nối và trả về false trong trường hợp ngược lại.
 private boolean checkInternetConnection() {
  
      ConnectivityManager connManager =
              (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);

    
      NetworkInfo networkInfo = connManager.getActiveNetworkInfo();

      if (networkInfo == null) {
          Toast.makeText(this, "No default network is currently active", Toast.LENGTH_LONG).show();
          return false;
      }

      if (!networkInfo.isConnected()) {
          Toast.makeText(this, "Network is not connected", Toast.LENGTH_LONG).show();
          return false;
      }

      if (!networkInfo.isAvailable()) {
          Toast.makeText(this, "Network not available", Toast.LENGTH_LONG).show();
          return false;
      }
      Toast.makeText(this, "Network OK", Toast.LENGTH_LONG).show();
      return true;
  }

3- NetworkOnMainThreadException

Theo mặc định khi bạn làm việc với mạng trong Android, bạn nên tạo ra một thread mới để gửi và nhận dữ liệu trả về. Nếu bạn làm việc trên thread chính bạn sẽ nhận được android.os.NetworkOnMainThreadException, đây là chính sách mặc định của android. Tuy nhiên bạn có thể ghi đè chính sách này của Android để có thể làm việc với Network trên thread chính.
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();

StrictMode.setThreadPolicy(policy);
Theo khuyến nghị bạn nên tạo một class mở rộng từ  AsyncTask<Params, Progress, Result>, class này không phải là một Thread, nó mở rộng từ Object, tuy nhiên khi thực thi nhiệm vụ (Gọi phương thức AsyncTask.execute(params)), nó tạo ra một Thread mới để thực thi phương thức doInBackground(params). Sau khi thread hoàn thành phương thức onPostExecute(result) sẽ được gọi.

Bạn có thể xem minh họa dưới đây:

4- Ví dụ sử dụng HttpURLConnection

Trong ví dụ này, bạn download Image và Json từ URL và hiển thị nó lên ImageViewTextView.
Tạo project AndroidNetworkingDemo:
Project đã được tạo ra.
Bạn phải cấp quyền cho ứng dụng truy cập Internet và quyền kiểm tra trạng thái của mạng.
<uses-permission android:name="android.permission.INTERNET"/>

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
Thêm vào AndroidManifest.xml:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.o7planning.androidnetworkingdemo">

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

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
Giao diện ứng dụng:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:paddingBottom="@dimen/activity_vertical_margin"
   android:paddingLeft="@dimen/activity_horizontal_margin"
   android:paddingRight="@dimen/activity_horizontal_margin"
   android:paddingTop="@dimen/activity_vertical_margin"
   tools:context="org.o7planning.androidnetworkingdemo.MainActivity">

   <ImageView
       android:layout_width="fill_parent"
       android:layout_height="120dp"
       android:id="@+id/imageView"
       android:layout_marginTop="10dp" />

   <TextView
       android:layout_width="fill_parent"
       android:layout_height="220dp"
       android:textAppearance="?android:attr/textAppearanceSmall"
       android:text="Small Text"
       android:id="@+id/textView"
       android:layout_below="@+id/imageView"
       android:layout_centerHorizontal="true"
       android:layout_marginTop="10dp" />

   <Button
       style="?android:attr/buttonStyleSmall"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Download Image"
       android:id="@+id/button"
       android:layout_below="@+id/textView"
       android:layout_alignParentLeft="true"
       android:layout_alignParentStart="true"
       android:layout_marginTop="37dp"
       android:onClick="downloadAndShowImage" />

   <Button
       style="?android:attr/buttonStyleSmall"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Download Json"
       android:id="@+id/button2"
       android:layout_alignTop="@+id/button"
       android:layout_toRightOf="@+id/button"
       android:layout_toEndOf="@+id/button"
       android:onClick="downloadAndShowJson" />

</RelativeLayout>

Code:

IOUtils.java
package org.o7planning.androidnetworkingdemo;


import java.io.InputStream;
import java.io.Reader;

public class IOUtils {

   public static void closeQuietly(InputStream in)  {
       try {
           in.close();
       }catch (Exception e) {

       }
   }

   public static void closeQuietly(Reader reader)  {
       try {
           reader.close();
       }catch (Exception e) {

       }
   }

}
DownloadJsonTask.java
package org.o7planning.androidnetworkingdemo;

import android.os.AsyncTask;
import android.util.Log;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;


// Một nhiệm vụ với tham số đầu vào String, và kết quả trả về là String.
public class DownloadJsonTask
     // AsyncTask<Params, Progress, Result>
      extends AsyncTask<String, Void, String> {

  private TextView textView;

  public DownloadJsonTask(TextView textView)  {
      this.textView= textView;
  }

  @Override
  protected String doInBackground(String... params) {
      String textUrl = params[0];

      InputStream in = null;
      BufferedReader br= null;
      try {
          URL url = new URL(textUrl);
          HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();

          httpConn.setAllowUserInteraction(false);
          httpConn.setInstanceFollowRedirects(true);
          httpConn.setRequestMethod("GET");
          httpConn.connect();
          int resCode = httpConn.getResponseCode();

          if (resCode == HttpURLConnection.HTTP_OK) {
              in = httpConn.getInputStream();
              br= new BufferedReader(new InputStreamReader(in));

              StringBuilder sb= new StringBuilder();
              String s= null;
              while((s= br.readLine())!= null) {
                  sb.append(s);
                  sb.append("\n");
              }
              return sb.toString();
          } else {
              return null;
          }
      } catch (Exception e) {
          e.printStackTrace();
      } finally {
          IOUtils.closeQuietly(in);
          IOUtils.closeQuietly(br);
      }
      return null;
  }


  // Khi nhiệm vụ hoàn thành, phương thức này sẽ được gọi.
  // Download thành công, update kết quả lên giao diện.
  @Override
  protected void onPostExecute(String result) {
      if(result  != null){
          this.textView.setText(result);
      } else{
          Log.e("MyMessage", "Failed to fetch data!");
      }
  }
}
DownloadImageTask.java
package org.o7planning.androidnetworkingdemo;


import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
 
// Một nhiệm vụ với tham số đầu vào String, và kết quả trả về là Bitmap.
public class DownloadImageTask
       // AsyncTask<Params, Progress, Result>
       extends AsyncTask<String, Void, Bitmap> {

   private ImageView imageView;

   public DownloadImageTask(ImageView imageView)  {
       this.imageView= imageView;
   }

   @Override
   protected Bitmap doInBackground(String... params) {
       String imageUrl = params[0];

       InputStream in = null;
       try {
           URL url = new URL(imageUrl);
           HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();

           httpConn.setAllowUserInteraction(false);
           httpConn.setInstanceFollowRedirects(true);
           httpConn.setRequestMethod("GET");
           httpConn.connect();
           int resCode = httpConn.getResponseCode();

           if (resCode == HttpURLConnection.HTTP_OK) {
               in = httpConn.getInputStream();
           } else {
               return null;
           }

           Bitmap bitmap = BitmapFactory.decodeStream(in);
           return bitmap;
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           IOUtils.closeQuietly(in);
       }
       return null;
   }

 
   // Khi nhiệm vụ hoàn thành, phương thức này sẽ được gọi.
   // Download thành công, update kết quả lên giao diện.
   @Override
   protected void onPostExecute(Bitmap result) {
       if(result  != null){
           this.imageView.setImageBitmap(result);
       } else{
           Log.e("MyMessage", "Failed to fetch data!");
       }
   }
}
MainActivity.java
package org.o7planning.androidnetworkingdemo;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

   private ImageView imageView;
   private TextView textView;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       this.imageView = (ImageView) this.findViewById(R.id.imageView);
       this.textView = (TextView) this.findViewById(R.id.textView);
   }


   private boolean checkInternetConnection() {
       // Lấy ra bộ quản lý kết nối.
       ConnectivityManager connManager =
               (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
 
       // Thông tin mạng đang kích hoạt.
       NetworkInfo networkInfo = connManager.getActiveNetworkInfo();

       if (networkInfo == null) {
           Toast.makeText(this, "No default network is currently active", Toast.LENGTH_LONG).show();
           return false;
       }

       if (!networkInfo.isConnected()) {
           Toast.makeText(this, "Network is not connected", Toast.LENGTH_LONG).show();
           return false;
       }

       if (!networkInfo.isAvailable()) {
           Toast.makeText(this, "Network not available", Toast.LENGTH_LONG).show();
           return false;
       }
       Toast.makeText(this, "Network OK", Toast.LENGTH_LONG).show();
       return true;
   }

 
   // Khi người dùng click vào nút "Download Image".
   public void downloadAndShowImage(View view) {
       boolean networkOK = this.checkInternetConnection();
       if (!networkOK) {
           return;
       }
       String imageUrl = "http://o7planning.org/download/static/default/demo-data/logo.png";

 
       // Tạo một đối tượng làm nhiệm vụ download image.
       DownloadImageTask task = new DownloadImageTask(this.imageView);
 
       // Thực thi nhiệm vụ (Truyền vào URL).
       task.execute(imageUrl);
   }
 
   // Khi người dùng click vào nút "Download Json".
   public void downloadAndShowJson(View view) {
       boolean networkOK = this.checkInternetConnection();
       if (!networkOK) {
           return;
       }
       String jsonUrl = "http://o7planning.org/download/static/default/demo-data/company.json";
 
       // Tạo một đối tượng làm nhiệm vụ download nội dung json từ URL.
       DownloadJsonTask task = new DownloadJsonTask(this.textView);

 
       // Thực thi nhiệm vụ (Truyền vào URL).
       task.execute(jsonUrl);
   }


}
Chạy ứng dụng: