Lập trình xử lý đăng nhập với api trong android

Vietnamese (Tiếng Việt) translation by Dai Phong (you can also view the original English article)

Hầu hết chúng ta đều trưởng thành nhờ tìm hiểu các thông tin mới, những thứ như Internet là một phần quan trọng trong cuộc sống. Mọi thứ đang trở nên gắn kết hơn bao giờ hết, vì vậy xây dựng những ứng dụng Android có nội dung tĩnh có thể là một ý tưởng tồi. Thay vào đó, bạn nên xem xét việc xây dựng các ứng dụng có thể hiển thị nội dung mới mỗi khi người dùng mở chúng.

Điều đó nghe có vẻ khó khăn, nhưng với ngày càng nhiều những trang web phơi bày tài nguyên của họ thông qua REST API, điều đó trở nên khá dễ dàng. (Hãy xem Hướng dẫn cho người mới bắt đầu với HTTP và REST để có một sự hiểu biết cơ bản).

Trong hướng dẫn này, tôi sẽ cho bạn thấy cách làm thế nào để sử dụng các lớp và phương thức có sẵn trong Android SDK để kết nối với máy chủ web từ xa và tương tác với chúng bằng cách sử dụng REST API của chúng.

1. Bật truy cập Internet

Tận dụng một REST API rõ ràng là liên quan đến việc sử dụng Internet. Tuy nhiên, ứng dụng Android chỉ có thể truy cập Internet khi chúng có quyền android.permission.INTERNET. Vì vậy, trước khi bạn bắt đầu viết bất kỳ code truy cập mạng, bạn phải chắc chắn rằng thẻ uses-permission sau đây có ở trong tập tin manifest của dự án:

Bởi vì android.permission.INTERNET không được xem là một quyền nguy hiểm, nên bạn không cần phải yêu cầu cho nó trong runtime trên các thiết bị chạy API Level 23 hoặc cao hơn.

2. Tạo các tiến trình nền

Nền tảng Android không cho phép bạn chạy các các tác vụ kết nối mạng trên tiến trình chính của ứng dụng. Vì vậy, tất cả các code kết nối mạng của bạn phải thuộc về một tiến trình nền. Cách dễ nhất để tạo ra một tiến trình là sử dụng phương thức execute() của lớp AsyncTask. Đối số duy nhất của execute() là một đối tượng Runnable.

AsyncTask.execute(new Runnable() {
    @Override
    public void run() {
        // All your networking logic
        // should be here
    }
});

Nếu bạn muốn tìm hiểu thêm về chạy các tác vụ trong nền, tôi đề nghị bạn đọc hướng dẫn về các tác vụ nền này từ loạt bài Android từ đầu.

3. Tạo một kết nối HTTP

Bằng cách sử dụng phương thức openConnection() của lớp URL, bạn có thể nhanh chóng thiết lập một kết nối đến bất kỳ endpoint của REST. Giá trị trả về của openConnection() phải được chuyển cho một đối tượng của HttpURLConnection hoặc HttpsURLConnection, tùy thuộc vào endpoint được truy cập thông qua HTTP hay HTTPS. HttpURLConnectionHttpsURLConnection đều cho phép bạn thực hiện các thao tác như thêm thông tin header và đọc các phản hồi.

Đoạn code sau đây cho bạn thấy cách thiết lập kết nối với root endpoint của GitHub API:

// Create URL
URL githubEndpoint = new URL("https://api.github.com/");

// Create connection
HttpsURLConnection myConnection =
        (HttpsURLConnection) githubEndpoint.openConnection();

Lưu ý rằng lớp HttpsURLConnection là một lớp con của lớp HttpURLConnection.

4. Thêm Header cho yêu cầu

Hầu hết các trang web cung cấp REST API muốn có khả năng xác định ứng dụng của bạn là duy nhất. Cách dễ nhất để giúp họ làm như vậy là thêm header User-Agent duy nhất trong tất cả các yêu cầu của bạn.

Để thêm một header User-Agent vào yêu cầu của bạn, bạn phải sử dụng phương thức setRequestProperty() của đối tượng HttpURLConnection. Ví dụ, ở đây là cách bạn thiết lập header User-Agent vào my-rest-app-v0.1:

myConnection.setRequestProperty("User-Agent", "my-rest-app-v0.1");

Bạn có thể thêm nhiều header vào yêu cầu của bạn bằng cách gọi phương thức setRequestProperty() nhiều lần. Ví dụ, đoạn code sau đây thêm một header Accept và một header Contact-Me tuỳ biến:

myConnection.setRequestProperty("Accept", 
        "application/vnd.github.v3+json");
myConnection.setRequestProperty("Contact-Me", 
        "");

5. Đọc các phẩn hồi

Một khi bạn đã truyền vào tất cả các header cho yêu cầu, bạn có thể kiểm tra xem bạn có một phản hồi hợp lệ hay không bằng cách sử dụng phương thức getResponseCode() của đối tượng HttpURLConnection.

if (myConnection.getResponseCode() == 200) {
    // Success
    // Further processing here
} else {
    // Error handling code goes here
}

Nếu lớp HttpURLConnection lấy được một mã số phản hồi, chẳng hạn như 301, nó xử lý mã số một cách tự động và chuyển hướng sau đó. Vì vậy, thông thường, bạn sẽ không cần phải viết thêm bất kỳ code nào để kiểm tra chuyển hướng.

Trong trường hợp không có lỗi, bạn có thể gọi phương thức getInputStream() để có được một tham chiếu đến Input Stream của kết nối.

InputStream responseBody = myConnection.getInputStream();

Hầu hết các RESST API ngày nay trả về dữ liệu dưới định dạng JSON hợp lệ. Vì vậy, thay vì đọc trực tiếp từ đối tượng InputStream, tôi khuyên bạn tạo ra một InputStreamReader cho nó.

InputStreamReader responseBodyReader = 
        new InputStreamReader(responseBody, "UTF-8");

6. Phân tích JSON trả về

Android SDK có một lớp gọi là JsonReader, nó giúp bạn dễ dàng có thể phân tích tài liệu JSON. Bạn có thể tạo ra một đối tượng mới của lớp JsonReader bằng truyền đối tượng InputStreamReader vào hàm xây dựng của nó.

JsonReader jsonReader = new JsonReader(responseBodyReader);

Cách mà bạn trích xuất một phần cụ thể của thông tin từ tài liệu JSON phụ thuộc vào cấu trúc của nó. Ví dụ, tài liệu JSON trả về bởi root endpoint từ REST API của GitHub trông như thế này:

{
  "current_user_url": "https://api.github.com/user",
  "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
  "authorizations_url": "https://api.github.com/authorizations",
  "code_search_url": "https://api.github.com/search/code?q=1{&page,per_page,sort,order}",
  "emails_url": "https://api.github.com/user/emails",
  "emojis_url": "https://api.github.com/emojis",
  "events_url": "https://api.github.com/events",
  "feeds_url": "https://api.github.com/feeds",
  "followers_url": "https://api.github.com/user/followers",
  "following_url": "https://api.github.com/user/following{/target}",
  "gists_url": "https://api.github.com/gists{/gist_id}",
  "hub_url": "https://api.github.com/hub",
  "issue_search_url": "https://api.github.com/search/issues?q=1{&page,per_page,sort,order}",
  "issues_url": "https://api.github.com/issues",
  "keys_url": "https://api.github.com/user/keys",
  "notifications_url": "https://api.github.com/notifications",
  "organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}",
  "organization_url": "https://api.github.com/orgs/{org}",
  "public_gists_url": "https://api.github.com/gists/public",
  "rate_limit_url": "https://api.github.com/rate_limit",
  "repository_url": "https://api.github.com/repos/{owner}/{repo}",
  "repository_search_url": "https://api.github.com/search/repositories?q=1{&page,per_page,sort,order}",
  "current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}",
  "starred_url": "https://api.github.com/user/starred{/owner}{/repo}",
  "starred_gists_url": "https://api.github.com/gists/starred",
  "team_url": "https://api.github.com/teams",
  "user_url": "https://api.github.com/users/{user}",
  "user_organizations_url": "https://api.github.com/user/orgs",
  "user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}",
  "user_search_url": "https://api.github.com/search/users?q=1{&page,per_page,sort,order}"
}

Như bạn có thể thấy, các phản hồi chỉ là một đối tượng JSON lớn có chứa một số khoá. Để trích xuất giá trị của khóa được gọi là organization_url từ nó, bạn sẽ phải viết code sau đây:

jsonReader.beginObject(); // Start processing the JSON object
while (jsonReader.hasNext()) { // Loop through all keys
    String key = jsonReader.nextName(); // Fetch the next key
    if (key.equals("organization_url")) { // Check if desired key
        // Fetch the value as a String
        String value = jsonReader.nextString();
        
        // Do something with the value
        // ...
                
        break; // Break out of the loop
    } else {
        jsonReader.skipValue(); // Skip values of other keys
    }
}

Code ở trên xử lý phản hồi JSON như là một stream của các token. Vì vậy, nó chiếm rất ít bộ nhớ. Tuy nhiên, bởi vì nó phải xử lý mỗi lần một token duy nhất, nên nó có thể bị chậm khi xử lý các phản hồi lớn.

Sau khi bạn đã trích xuất tất cả các thông tin cần thiết, bạn phải luôn luôn gọi phương thức close() của đối tượng JsonReader để nó giải phóng mọi tài nguyên mà nó nắm giữ.

jsonReader.close();

Bạn cũng phải đóng kết nối bằng cách gọi phương thức disconnect() của đối tượng HttpURLConnection.

myConnection.disconnect();

7. Sử dụng các phương thức HTTP khác nhau

Giao diện REST dựa trên HTTP sử dụng các phương thức HTTP để xác định kiểu hoạt động cần được thực hiện trên một nguồn tài nguyên. Trong các bước trước đó, chúng ta sử dụng phương thức GET của HTTP để thực hiện một thao tác đọc. Bởi vì lớp HttpURLConnection sử dụng phương thức GET theo mặc định, nên chúng ta không cần phải chỉ định một cách rõ ràng.

Để thay đổi phương thức HTTP của đối tượng HttpURLConnection, bạn phải sử dụng phương thức setRequestMethod() của nó. Ví dụ như đoạn code sau đây sẽ mở một kết nối đến một endpoint thuộc về httpbin.org và thiết lập phương thức HTTP thành POST:

URL httpbinEndpoint = new URL("https://httpbin.org/post");
HttpsURLConnection myConnection
        = (HttpsURLConnection) httpbinEndpoint.openConnection();

myConnection.setRequestMethod("POST");

Như bạn đã biết, yêu cầu POST được sử dụng để gửi dữ liệu đến máy chủ. Bằng cách ghi vào Output Stream của kết nối, bạn có thể dễ dàng thêm bất kỳ dữ liệu nào vào body của yêu cầu POST. Tuy nhiên, trước khi bạn làm vậy, bạn phải chắc chắn rằng bạn gọi phương thức setDoOutput() của đối tượng HttpURLConnection và truyền true vào nó.

Đoạn code sau đây cho bạn thấy cách để gửi một cặp khóa-giá trị đơn giản lên máy chủ:

// Create the data
String myData = "message=Hello";

// Enable writing
myConnection.setDoOutput(true);

// Write the data
myConnection.getOutputStream().write(myData.getBytes());

8. Lưu các phản hồi vào bộ nhớ đệm

Luôn là một ý tưởng tốt khi lưu vào bộ nhớ bộ nhớ đệm các phản hồi HTTP. Bằng cách đó, bạn có thể không những giảm mức tiêu thụ băng thông của ứng dụng, mà còn làm cho nó đáp ứng tốt hơn. Từ API Level 13 trở đi, Android SDK cung cấp một lớp gọi là HttpResponseCache, cho phép bạn dễ dàng lưu bộ nhớ đệm mà không cần thực hiện bất kỳ thay đổi nào đối với logic của mạng.

Để cài đặt một bộ nhớ đệm cho ứng dụng của bạn, bạn phải gọi phương thức install() của lớp HttpResponseCache. Phương thức cần một đường dẫn tuyệt đối xác định nơi mà bộ nhớ đệm phải được cài đặt, và một con số chỉ định kích thước bộ nhớ đệm. Bạn có thể sử dụng phương thức getCacheDir() nếu bạn không muốn chỉ định đường dẫn tuyệt đối theo cách thủ công.

Đoạn code sau sẽ cài đặt một bộ nhớ đệm có kích thước là 100.000 byte:

HttpResponseCache myCache = HttpResponseCache.install(
                                getCacheDir(), 100000L);

Khi bộ nhớ đệm đã được cài đặt, thì lớp HttpURLConnection sẽ bắt đầu sử dụng nó một cách tự động. Để kiểm tra xem bộ nhớ đệm của bạn có hoạt động hay không, bạn có thể sử dụng phương thức getHitCount(), phương thức này trả về số lượng các phản hồi HTTP mà được phục vụ từ bộ nhớ đệm.

if (myCache.getHitCount() > 0) {
    // The cache is working
}

Tổng kết

Có hàng ngàn REST API để bạn có thể tự do sử dụng trong các ứng dụng Android. Bằng cách sử dụng chúng, bạn có thể làm cho ứng dụng của bạn thêm nhiều thông tin phong phú, thú vị và giàu tính năng. Trong hướng dẫn này, bạn đã học được cách sử dụng lớp HttpURLConnection để tiêu thụ những REST API như thế. Bạn cũng học được cách để tạo ra một bộ nhớ đệm cho các phản hồi HTTP để giữ cho ứng dụng của bạn sử dụng băng thông thấp.

Nếu bạn nghĩ rằng sử dụng HttpURLConnection là khó, thì bạn nên thử một thư viện bên thứ ba, chẳng hạn như Volley. Thư viện như thế này sử dụng lớp HttpURLConnection bên trong, nhưng cung cấp rất nhiều phương thức thuận tiện cho phép bạn làm cho code của bạn dễ đọc và súc tích hơn.

Để tìm hiểu thêm về kết nối mạng trên nền tảng Android, bạn có thể tham khảo tài liệu hướng dẫn Network Operations của Android.