使用OkHttp發送網絡請求并將結果更新至UI的幾種方式

發送網絡請求時我們寫大部分安卓項目時無法避免的一環,使用OkHttp庫可以很好的幫助我們封裝網絡請求的底層處理細節,更專注的完成實際業務需求。但是安卓不允許我們直接在UI線程運行網絡請求,因為網絡請求可能會阻塞UI的響應,因此我們只能開辟新的線程來處理網絡請求。那么怎么在網絡請求完成后在子線程中更新UI呢?下面我們就來討論一下幾種簡單的實現方式。

1. 使用AsyncTask + OkHttp同步請求

AsyncTask類是用來在子線程中異步處理一些耗時操作的一個工具類,我們先繼承AsyncTask類編寫一個處理自己業務需求的子類,然后在其中編寫邏輯代碼,最后在UI線程中啟動AsyncTask即可,如下代碼:

private class UserLoginTask extends AsyncTask<String, Void, LoginResult> {

        /**
         * 登錄參數
         * @param params params[0] ==> username,
         *               params[1] ==> password
         * @return
         */
        @Override
        protected LoginResult doInBackground(String... params) {
            String username = params[0];
            String password = params[1];
            NetworkHelper networkHelper = NetworkHelper.getInstance();
            try {
                User user = networkHelper.login(username, password);
                //login failed
                if (user == null) {
                    Log.i(TAG, "Username and password do not match");
                    result.setStatus(ResultStatus.AUTH_ERROR);
                } else {
                    Log.i(TAG, "Login success");
                    result.setStatus(ResultStatus.SUCCESS);
                    result.setUser(user);
                }
            } catch (IOException e) {
                Log.e(TAG, "Login failed due to network error", e);
                result.setStatus(ResultStatus.NETWORK_ERROR);
            }
            return result;
        }

        @Override
        protected void onPostExecute(LoginResult loginResult) {
            mProgressDialog.hide();

            if (loginResult.getStatus() == ResultStatus.SUCCESS) {
                // do login success task, update UI
                ...
            } else if (loginResult.getStatus() == ResultStatus.NETWORK_ERROR){
                Toast.makeText(getApplication(), R.string.error_network_fail, Toast.LENGTH_SHORT)
                        .show();
            } else if (loginResult.getStatus() == ResultStatus.AUTH_ERROR) {
                Toast.makeText(getApplication(), R.string.error_incorrect_password, Toast.LENGTH_SHORT)
                        .show();
            }
        }
    }

以上代碼中UserLoginTask是LoginActivity的一個子類,其中最重要的有兩個方法:doInBackground(String... params)onPostExecute(LoginResult loginResult),前者的返回值會傳入后者的方法參數中。前者在子線程中執行,因此不可以在doInBackground中更新UI,否則會拋出異常,而后者是在主線程中執行的,所以相關UI操作可以在這里進行。
然后在Activity中為LoginButton設置監聽,在點擊時創建并啟動LoginTask:

mLoginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                login();
            }
        });
...
private void login() {
        String username = mUsernameEditText.getText().toString();
        String password = mPasswordEditText.getText().toString();
        if (username.isEmpty() || password.isEmpty()) {
            Toast.makeText(getApplication(), R.string.error_empty_username_or_password, Toast.LENGTH_SHORT)
                    .show();
            return;
        }
        //如果登錄任務還未完成,防止創建重復的登錄任務
        if (mLoginTask != null) {
            return;
        }

        mProgressDialog.show();

        mLoginTask = new UserLoginTask();
        mLoginTask.execute(username, password);
}

其中最后一行代碼mLoginTask.execute(username, password)就是在子線程中執行LoginTask中方法。
以上僅為創建子線程任務的部分,而其中networkHelper.login(username, password)調用中才是真正執行OkHttp請求的地方,因為AsyncTask已經是子線程了,所以在發送OkHttp請求時就不需要使用異步請求,發送同步請求就可以了,下面給出其簡單代碼(以post請求為例):

public User login(String username, String password) throws IOException {
        OkHttpClient client = new OkHttpClient();
        String requestBody = "{\"username\": \"" + username + "\", \"password\": \"" + password + "\"}";
        String res;
        RequestBody body = RequestBody.create(JSON, requestBody);
        Request request = new Request.Builder()
                .url(baseUrl + path)  //你的請求URL
                .post(body)
                .build();
        Response response = client.newCall(request).execute();
        if (response.isSuccessful()) {
            res = response.body().string();
        } else {
            throw new IOException("Unexpected code " + response);
        }
        //用戶名或密碼錯誤,返回空字符串
        if (res == null || res.trim().isEmpty()) {
            return null;
        }
        return gson.fromJson(res, User.class);
    }

OkHttp的使用非常簡單,創建client,創建request,然后調用client.newCall(request).execute()就可以得到response,然后對其進行處理即可,詳情可參考官方文檔,這里不再贅述。

2. 使用OkHttp的異步請求

異步OkHttp請求可以是我們不需要再編寫自己的AsyncTask了,OkHttp會自動在子線程中執行網絡請求,并在請求成功或失敗后回來調用相應的回調方法,如下:

public void asyncGetStudent(final Activity activity, int groupId, final NetworkCallback<User> callback) {
        String authToken = getAuthToken(activity);
        String path = "/group/" + groupId + "/students";
        Request request = buildGetRequest(path, authToken);
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                callback.onGetFail(e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (!response.isSuccessful()) {
                    onFailure(call, new IOException("Unexpected Code: " + response));
                } else {
                    String responseJson = response.body().string();
                    User[] students = gson.fromJson(responseJson, User[].class);
                    callback.onGetSuccess(students);
                }
            }
        });
    }

異步請求與同步請求的主要區別在于調用clinet.newCall(request).enqueue(Callback)而非execute()方法,然后在onFailureonResponse中處理結果。
為了集中處理網絡請求,我們依然將所有網絡請求的代碼放在了NetworkHelper類中,然后在Activity中調用其中的方法,以上代碼中的NetworkCallback<T>回調接口如下:

public interface NetworkCallback<T> {
    void onGetSuccess(T[] resultList);
    void onGetFail(Exception ex);
}

該回調接口在網絡請求完成后調用,其實現定義在調用網絡請求的Activity或Fragment中:

protected void onCreate(Bundle savedInstance) {
    NetworkHelper.getInstance().asyncGetStudent(this, groupId, new NetworkCallback<User>() {
                @Override
                public void onGetSuccess(User[] resultList) {
                    mStudents = resultList;
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            updateUI();
                        }
                    });
                }

                @Override
                public void onGetFail(Exception ex) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(StudentListActivity.this, R.string.error_network_fail, Toast.LENGTH_SHORT)
                                    .show();
                        }
                    });
                }
}

在上面的代碼片段中,我們在拿到結果resultList后,沒有直接使用它來更新UI,而是調用了一個runOnUIThread(Runnable runable)方法,這是為什么呢?
因為回調方法的執行依然是在子線程中的,所以回調方法中依然不能更新UI!,這里我們使用一個簡單方便的調用runOnUiThread來更新UI,該方法接受一個Runnable對象,只要在Runnable中更新UI就可以了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,455評論 25 708
  • Android開發者:你真的會用AsyncTask嗎? 導讀.1 在Android應用開發中,我們需要時刻注意保證...
    cxm11閱讀 2,730評論 0 29
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • AFHTTPRequestOperationManager 網絡傳輸協議UDP、TCP、Http、Socket、X...
    Carden閱讀 4,394評論 0 12
  • 親愛的兒子,媽媽下班還沒有到家,你就開始做作業了,數學有道題目有點疑問,你就在群里問了數學老師,后來媽媽在群里看到...
    杰仔媽閱讀 208評論 0 1