Khóa học lập trình Android cơ bản

Serial tutorial hướng dẫn lập trình Android cơ bản

Xử lý bất đồng bộ trong lập trình Android cơ bản Xử lý bất đồng bộ trong lập trình Android cơ bản Xử lý bất đồng bộ trong lập trình Android cơ bản Xử lý bất đồng bộ trong lập trình Android cơ bản Xử lý bất đồng bộ trong lập trình Android cơ bản 5/5 (68 reviews)

Xử lý bất đồng bộ trong lập trình Android cơ bản

Đã đăng 2016-10-17 09:13:03 bởi HowKteam
2 bình luận 12369 lượt xem
Xử lý bất đồng bộ trong lập trình Android cơ bản 5 /5 stars (2 reviews)
 

Dẫn nhập

Ở các bài học trước, chúng ta đã cùng nhau tìm hiểu về ANIMATION & CÁC HIỆU ỨNG ĐƠN GIẢN TRONG ANDROID. Tiếp theo ở bài học này chúng ta sẽ đề cập đến một khía cạnh quan trọng khác trong lập trình Android, đó là xử lý bất đồng bộ. Bài tiếp sau nữa cũng sẽ liên quan nên các bạn hãy chú ý kỹ nhé!


Nội dung

Để đọc hiểu bài này tốt nhất các bạn nên có kiến thức cơ bản về các phần:

Trong bài học này, chúng ta sẽ cùng tìm hiểu các vấn đề:

  • ANR.
  • Làm quen với lỗi thao tác mạng phổ biến.
  • Làm ví dụ xử lý download thông tin trên mạng và hiển thị về máy.

ANR

ANR là viết tắt của Application Not Responding (Ứng dụng không phản hồi).

Nếu đã từng sử dụng máy Android chắc hẳn bạn cũng đã ít nhất một lần gặp phải cái gì đó tương tự thế này:

Nguyên nhân có thể do rất nhiều: Lỗi null, các exception được bắn ra nhưng không xử lý,… Hoặc là:

  • Không có phản hồi cho các sự kiện xuất / nhập thông tin (ghi dữ liệu vào bộ nhớ, nhập văn bản,…). Nói đơn giản là TREO máy.
  • Broadcast Receiver không hoàn thành trong vòng 10 giây.
  • Với các máy Androdi 3.1 về trước: Thao tác liên quan đến mạng quá 5 giây. Sau phiên bản 3.1 thì Google đã cấm tiệt thao tác mạng trên Main Thread.

Về cơ bản là như vậy. Tuy nhiên, hẳn các bạn sẽ tự hỏi…


Main Thread và Worker Thread là gì?

Mình đã giới thiệu ở bài đầu tiên, Android là hệ điều hành đa tiến trình, đa luồng. Các ứng dụng Android cũng có thể sử dụng được nhiều thread (luồng) tài nguyên hệ thống.

Tuy nhiên các thành phần liên quan đến giao diện người dùng đều được nhét chung vào một thread chính, gọi là UI Thread (hay Main Thread).

Các thao tác xử lý nặng hầu hết đều bị cấm thực thi trên Main Thread do sẽ gây ra trải nghiệm xấu cho người dùng (bấm nút mãi không lên, chạy mãi cứ treo là hỏng rồi, người ta sẽ xóa app ngay). Do đó chúng ta cần đưa các tác vụ này vào một Thread khác, gọi là Worker Thread.

Và sau đó là cập nhật vào Main Thread. Cách làm này được gọi là thao tác xử lý bất đồng bộ (Asynchronous).

Trăm nghe không bằng một thấy, chúng ta sẽ làm thử ví dụ nho nhỏ. Là download thông tin dạng String từ một site bất kỳ, sau đó in ra màn hình Android, từ trên Main Thread lẫn Worker Thread xem chúng thực sự ra sao nhé.


ANR với thao tác mạng trên Main Thread.

Bước 1: Như mọi khi, chúng ta tạo một project mới với tên là AsyncExample với API là 13 và loại Activity là Empty Activity.

Bước 2: Để cho đơn giản, dữ liệu của chúng ta sẽ lấy từ trang icanhazip.com. Vào trang này các bạn sẽ thấy địa chỉ IP của mình hiện ra. Và chúng ta sẽ đặt một TextView cho hiển thị địa chỉ này lên Activity.

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:id="@+id/activity_main"
    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="com.howkteam.asyncexample.MainActivity">

    <TextView
        android:id="@+id/tv_ip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</RelativeLayout>

 

Bước 3: Trong MainActivity.java, chúng ta sẽ viết một hàm đơn giản, download thông tin từ icanhazip về và đổ vào TextView, tên hàm là testNetwork():

MainActivity.java

package com.howkteam.asyncexample;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;

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

public class MainActivity extends AppCompatActivity {

    TextView tvIP;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvIP = (TextView) findViewById(R.id.tv_ip);

        testNetwork();
    }

    private void testNetwork() {
        try {
            StringBuilder sb;
            // Website này sẽ cho biết địa chỉ IP của bạn.
            URL url = new URL("http://icanhazip.com/");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                sb = new StringBuilder();
                String line;
                while ((line = br.readLine()) != null) {
                    sb.append(line).append("\n");
                }

                tvIP.setText(sb.toString());
                Log.e("Dia chi IP ", sb.toString());

                br.close();

            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

Nhớ đặt Permission Internet trong file AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.howkteam.asyncexample">

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

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

 

Và tất nhiên là chương trình sẽ bị crash rồi, do hàm testNetwork được gọi ngay trong UI Thread mà:

 

Và khi nhìn vào log, bạn sẽ thấy NetworkOnMainThreadException:

Vậy phải làm sao đây?


AsyncTask

Nói về xử lý bất đồng bộ thì quả thực không đơn giản. Mình cũng thừa nhận là mình cực kỳ gà ở khoản này. Nhưng may mắn thay là Android có một cơ chế giúp đơn giản hóa quá trình xử lý bất đồng bộ hiệu quả, phù hợp với ứng dụng nhỏ.

Đó chính là AsyncTask.

Bước 1: Chúng ta tạo một class mới, extends từ lớp AsyncTask, tạm gọi là AsyncTaskNetwork và nó có dạng ban đầu như sau:

AsyncTaskNetwork.java

package com.howkteam.asyncexample;

import android.os.AsyncTask;

public class AsyncTaskNetwork extends AsyncTask<String, Void, Boolean> {
    
}

Vào menu Code > Override Methods để tạo các method cần thiết. Thật ra chỉ cần doInBackground nhưng theo mình, nên tạo method theo thứ tự sau: onPreExecute, doInBackground onPostExecute:

Và nó có dạng như này:

AsyncTaskNetwork.java

package com.howkteam.asyncexample;

import android.os.AsyncTask;

public class AsyncTaskNetwork extends AsyncTask<String, Void, Boolean> {
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected Boolean doInBackground(String... strings) {
        return null;
    }

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        super.onPostExecute(aBoolean);
    }
}

 

Phần này tương đối khó hiểu. Các bạn lưu ý nhé: Chúng ta có 3 phần:

  • onPreExecute: Tác vụ này vẫn chạy ở UI Thread, nó sẽ chạy trước khi bắt đầu tác vụ bất kỳ nào đó trong doInBackground. Thích hợp nhất là đặt ở đây một ProgressDialog (một thông báo nhỏ quay quay) để cho người dùng biết là đang có tác vụ xử lý.
  • doInBackground: Đây chính là cái chúng ta cần. Các thao tác về mạng sẽ được đặt vô đây. doInBackground là task chạy trên Worker Thread.
  • onPostExecute: Tác vụ này chạy ở UI Thread, chúng ta sẽ cho dừng ProgressDialog ở đây, đồng thời lấy dữ liệu từ doInBackground về sau khi xử lý.

Các bạn có để ý đến các chỗ bôi màu chữ không? Đó chính là:

  • String: Đây là kiểu dữ liệu được truyền vào doInBackground.
  • Void: Đây là kiểu dữ liệu mà ta truyền vào một hàm đặc biệt có tên publishProgress(). Hàm này có sẵn trong lớp AsyncTask nhưng chúng ta không sử dụng đến. Nó dùng để cập nhật xem tác vụ đã chạy đến đâu rồi.
  • Boolean: Kiểu dữ liệu trả về sau khi hoàn thành AsyncTask. Ở đây mình đặt Boolean để thông báo: Tác vụ xử lý mạng có thành công hay không.

Và chúng ta có class AsyncTask hoàn chỉnh như thế này, để ý kỹ chỗ string[0]:

package com.howkteam.asyncexample;

import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.TextView;

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

public class AsyncTaskNetwork extends AsyncTask<String, Void, String> {

    ProgressDialog progressDialog;
    Context context;
    TextView textView;

    public AsyncTaskNetwork(Context context, TextView textView) {
        this.context = context;
        this.textView = textView;
    }

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

        // Hiển thị Dialog khi bắt đầu xử lý.
        progressDialog = new ProgressDialog(context);
        progressDialog.setTitle("HowKteam");
        progressDialog.setMessage("Dang xu ly...");
        progressDialog.show();
    }

    @Override
    protected String doInBackground(String... strings) {
        try {
            StringBuilder sb;
            // Website này sẽ cho biết địa chỉ IP của bạn.
            URL url = new URL(strings[0]);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                sb = new StringBuilder();
                String line;
                while ((line = br.readLine()) != null) {
                    sb.append(line).append("\n");
                }

                Log.e("Dia chi IP ", sb.toString());
                br.close();
                return sb.toString();
            }

        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        return null;
    }

    @Override
    protected void onPostExecute(String aString) {
        super.onPostExecute(aString);
        // Hủy dialog đi.
        progressDialog.dismiss();
        // Hiển thị IP lên TextView.
        textView.setText(aString);
    }
}

 

Quay lại MainActivity. Cách áp dụng AsyncTask rất đơn giản như sau, chú ý phần tham số của execute:

package com.howkteam.asyncexample;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    TextView tvIP;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvIP = (TextView) findViewById(R.id.tv_ip);

        new AsyncTaskNetwork(this, tvIP).execute("http://icanhazip.com/");
    }
}

 

Đó, 2 phần tham số mình đã đánh dấu tô đậm màu vàng. Cái địa chỉ kia chính là tham số thứ nhất trong mảng string… của AsyncTask (phần tử 0). Bạn có thể truyền vào bao nhiêu tham số cũng được, nhưng phải cùng kiểu và xử lý chu đáo.

Chạy app, và chúng ta có được:

Lưu ý là IP trên là của mình, của các bạn có thể khác.


Kết luận

Qua bài này chúng ta đã nắm được AsyncTask là gì, cơ chế bắt buộc xử lý tác vụ mạng trên Worker Thread của Android.

Chúng ta sẽ tìm hiểu sâu hơn về thao tác với mạng bằng Moshi và thư viện OkHttp qua bài JSON & WEB API TRONG LẬP TRINH ANDROID 

Cảm ơn các bạn đã theo dõi bài viết. Hãy để lại bình luận hoặc góp ý của mình để phát triển bài viết tốt hơn. Đừng quên “Luyện tập – Thử thách – Không ngại khó”.


Tài liệu 

Nhằm phục vụ mục đích học tập Offline của cộng đồng, Kteam hỗ trợ tính năng lưu trữ nội dung bài học Xử lý bất đồng bộ trong lập trình Android cơ bản dưới dạng file PDF trong link bên dưới.

Ngoài ra, bạn cũng có thể tìm thấy các tài liệu được đóng góp từ cộng đồng ở mục TÀI LIỆU trên thư viện Howkteam.com

Đừng quên like hoặc +1 Google để ủng hộ Kteam và tác giả nhé! 


Thảo luận

Nếu bạn có bất kỳ khó khăn hay thắc mắc gì về khóa học, đừng ngần ngại đặt câu hỏi trong phần BÌNH LUẬN bên dưới hoặc trong mục HỎI & ĐÁP trên thư viện Howkteam.com để nhận được sự hỗ trợ từ cộng đồng. 

 

Chia sẻ:
Thảo luận Hỏi và đáp Báo lỗi bài viết
Hủy bỏ   hoặc  
Xử lý bất đồng bộ trong lập trình Android cơ bản
tranc1710 2017-11-21 12:42:28

anh ơi cho em hỏi, em có thể viết hàm getResult từ Api trong doInBackground được không anh?

0 bình chọn
Reply
Xử lý bất đồng bộ trong lập trình Android cơ bản
phanvanson.info 2017-10-17 20:52:31

Lúc đầu Admin tạo class là 

public class AsinctaskNetWork extends AsyncTask<String, Void, String>

sau đó copy code vào lại là 

public class AsinctaskNetWork extends AsyncTask<String, Void, String>

làm em hoang mang quá xem đi xem lại mới biết khác nhau chỗ đó bảo không hiểu tại sao code mình lại khác code Admin 

0 bình chọn
Reply
Hủy bỏ   hoặc  
Hủy bỏ   hoặc  

Chiến dịch

Kteam - Howkteam Free Education