openplanning

Hướng dẫn lập trình mạng trong Android - Android Networking

  1. Tổng quan lập trình mạng trong Android
  2. NetworkOnMainThreadException
  3. Ví dụ sử dụng HttpURLConnection
  4. Phụ lục: Thiết kế giao diện

1. Tổng quan lập trình mạng trong 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 (sự cho phép) 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;
  }

2. NetworkOnMainThreadException

Theo mặc định khi bạn làm việc với mạng (network) 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 lỗi 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:

3. Ví dụ sử dụng HttpURLConnection

Trong ví dụ này, bạn download ảnh và Json từ URL và hiển thị nó lên ImageViewTextView.
Tạo project AndroidNetworkingDemo:
  • File > New > New Project > Empty Activity
    • Name: AndroidNetworkingDemo
    • Package name: org.o7planning.androidnetworkingdemo
    • Language: Java
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 đoạn 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:roundIcon="@mipmap/ic_launcher_round"
        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:
Nếu bạn quan tâm đến các bước để thiết kế giao diện ứng dụng này xin hãy xem ở phần phụ lục phía cuối bài viết.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="0dp"
        android:layout_height="171dp"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="18dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:contentDescription="Image"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_launcher_foreground" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="186dp"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="19dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView">

        <Button
            android:id="@+id/button_img"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0"
            android:text="Download Image" />

        <Button
            android:id="@+id/button_json"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0"
            android:text="Download JSON" />
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
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;

// A task with String input parameter, and returns the result as 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;
    }

    // When the task is completed, this method will be called
    // Download complete. Lets update UI
    @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;

// A task with String input parameter, and returns the result as 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;
    }

    // When the task is completed, this method will be called
    // Download complete. Lets update UI
    @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 androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private ImageView imageView;
    private TextView textView;
    private Button buttonImg;
    private Button buttonJson;

    @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);
        this.buttonImg = (Button) this.findViewById(R.id.button_img);
        this.buttonJson = (Button) this.findViewById(R.id.button_json);

        this.buttonImg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                downloadAndShowImage(v);
            }
        });
        this.buttonJson.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                downloadAndShowJson(v);
            }
        });
    }


    private boolean checkInternetConnection() {
        // Get Connectivity Manager
        ConnectivityManager connManager =
                (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);

        // Details about the currently active default data network
        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;
    }

    // When user click on the "Download Image".
    public void downloadAndShowImage(View view) {
        boolean networkOK = this.checkInternetConnection();
        if (!networkOK) {
            return;
        }
        String imageUrl = "https://o7planning.org/download/static/default/demo-data/logo.png";

        // Create a task to download and display image.
        DownloadImageTask task = new DownloadImageTask(this.imageView);

        // Execute task (Pass imageUrl).
        task.execute(imageUrl);
    }

    // When user click on the "Download Json".
    public void downloadAndShowJson(View view) {
        boolean networkOK = this.checkInternetConnection();
        if (!networkOK) {
            return;
        }
        String jsonUrl = "https://o7planning.org/download/static/default/demo-data/company.json";

        // Create a task to download and display json content.
        DownloadJsonTask task = new DownloadJsonTask(this.textView);

        // Execute task (Pass jsonUrl).
        task.execute(jsonUrl);
    }


}

4. Phụ lục: Thiết kế giao diện

Thêm ImageView, TextView vào giao diện.
Thêm các Button vào giao diện.
Sét đặt ID, Text cho các thành phần trên giao diện.

Các hướng dẫn lập trình Android

Show More