Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android---XUtils框架之HttpUtils源碼分析

android---XUtils框架之HttpUtils源碼分析

編輯:關於Android編程

之前我們對Volley框架源碼進行了分析,知道了他適用於頻繁的網絡請求,但是不太適合post較大數據以及文件的上傳操作,在項目中為了彌補Volley的這個缺陷,使用了XUtils框架的HttpUtils實現了文件上傳的操作,當然文件上傳的方法類似於照片上傳,有時間的話單獨寫一篇博客介紹,這篇博客我們從源碼角度來分析HttpUtils的實現原理,希望對這幾天的學習做個總結:

先來回顧下HttpUtils的使用步驟:

(1)創建HttpUtils對象:HttpUtils httpUtils = new HttpUtils();

(2)調用HttpUtils的send方法進行上傳操作;

(3)或者調用download方法來進行下載任務,雖然download方法有多個重載方法並且需要傳入不同個參數,但是最後他們都會歸結到同一個download方法上面;

就是這麼簡單,那麼具體在這兩步中做了些什麼事呢?我們就該慢慢看看啦!

先來從HttpUtils httpUtils = new HttpUtils()開始

HttpUtils有四個構造函數,但是最終他們都會調用public HttpUtils(int connTimeout, String userAgent)這個構造函數,第一個參數是連接超時時間,默認的超時時間是15s,第二個參數是用戶代理,其實也就是浏覽器訪問頭啦:

 

    public HttpUtils(int connTimeout, String userAgent) {
        HttpParams params = new BasicHttpParams();

        ConnManagerParams.setTimeout(params, connTimeout);
        HttpConnectionParams.setSoTimeout(params, connTimeout);
        HttpConnectionParams.setConnectionTimeout(params, connTimeout);

        if (!TextUtils.isEmpty(userAgent)) {
            HttpProtocolParams.setUserAgent(params, userAgent);
        }

        ConnManagerParams.setMaxConnectionsPerRoute(params, new ConnPerRouteBean(10));
        ConnManagerParams.setMaxTotalConnections(params, 10);

        HttpConnectionParams.setTcpNoDelay(params, true);
        HttpConnectionParams.setSocketBufferSize(params, 1024 * 8);
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);

        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schemeRegistry.register(new Scheme("https", DefaultSSLSocketFactory.getSocketFactory(), 443));

        httpClient = new DefaultHttpClient(new ThreadSafeClientConnManager(params, schemeRegistry), params);

        httpClient.setHttpRequestRetryHandler(new RetryHandler(DEFAULT_RETRY_TIMES));

        httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
            @Override
            public void process(org.apache.http.HttpRequest httpRequest, HttpContext httpContext) throws org.apache.http.HttpException, IOException {
                if (!httpRequest.containsHeader(HEADER_ACCEPT_ENCODING)) {
                    httpRequest.addHeader(HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
                }
            }
        });

        httpClient.addResponseInterceptor(new HttpResponseInterceptor() {
            @Override
            public void process(HttpResponse response, HttpContext httpContext) throws org.apache.http.HttpException, IOException {
                final HttpEntity entity = response.getEntity();
                if (entity == null) {
                    return;
                }
                final Header encoding = entity.getContentEncoding();
                if (encoding != null) {
                    for (HeaderElement element : encoding.getElements()) {
                        if (element.getName().equalsIgnoreCase("gzip")) {
                            response.setEntity(new GZipDecompressingEntity(response.getEntity()));
                            return;
                        }
                    }
                }
            }
        });
    }

構造函數首先在2到17行進行請求參數的設置,其中ConnManagerParams是一個final類型的類,他是HTTP協議參數的集合,主要用於客戶端連接管理,第19到21行進行的是協議模式的設置,另外同樣也可以使用他來進行協議屬性的設置,比如設置默認端口號等等,第23行創建了HttpClient對象,注意他是使用ThreadSafeClientConnManager來進行連接管理的,也就是他是線程安全的,第25行設置他的請求重試次數,這裡是默認值5次,27行添加請求攔截器,36行添加響應攔截器;

接著我們通過send來實現上傳操作了,先來看看send方法:

 public  HttpHandler send(HttpRequest.HttpMethod method, String url,
                                   RequestCallBack callBack) {
        return send(method, url, null, callBack);
    }

    public  HttpHandler send(HttpRequest.HttpMethod method, String url, RequestParams params,
                                   RequestCallBack callBack) {
        if (url == null) throw new IllegalArgumentException("url may not be null");

        HttpRequest request = new HttpRequest(method, url);
        return sendRequest(request, params, callBack);
    }
可以看到不管是調用哪個send方法,最終都會調用sendRequest(request, params, callBack)這個方法,其中request對象封裝了我們的請求方法類型以及請求url,來看看sendRequest方法啦:
private  HttpHandler sendRequest(HttpRequest request, RequestParams params, RequestCallBack callBack) {

        HttpHandler handler = new HttpHandler(httpClient, httpContext, responseTextCharset, callBack);

        handler.setExpiry(currentRequestExpiry);
        handler.setHttpRedirectHandler(httpRedirectHandler);
        request.setRequestParams(params, handler);

        Priority priority = null;
        if (params != null) {
            priority = params.getPriority();
        }
        if (priority == null) {
            priority = Priority.DEFAULT;
        }
        handler.executeOnExecutor(EXECUTOR, priority, request);
        return handler;
    }

第3行利用我們之前在HttpUtils構造函數中創建的httpClient,以及httpContext,responseTextCharset, callBack來創建一個HttpHandler對象,其中httpContext用於保存請求的上下文信息,這個值得設置函數是:

public HttpUtils configCookieStore(CookieStore cookieStore) {
        httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
        return this;
    }
可以看出這裡進行了Cookie屬性的設置,當然,我們可以不去設置他,不設置的話就只是一個沒有值得上下文對象啦,這一點在HttpUtils的全局變量定義中能充分顯示出來:
private final HttpContext httpContext = new BasicHttpContext();
responseTextCharset指的是返回的編碼類型,可以通過
public HttpUtils configResponseTextCharset(String charSet) {
        if (!TextUtils.isEmpty(charSet)) {
            this.responseTextCharset = charSet;
        }
        return this;
    }

來進行設置,默認的值是UTF-8類型的啦:

 

private String responseTextCharset = HTTP.UTF_8;
callBack就是我們調用send方法傳入的RequestCallBack對象了;

接著sendRequest的第5行設置了請求有效期,第6行設置了重定向請求,這個值默認情況下是null,當然你可以通過以下方法來進行設置:

 public HttpUtils configHttpRedirectHandler(HttpRedirectHandler httpRedirectHandler) {
        this.httpRedirectHandler = httpRedirectHandler;
        return this;
    }
第7行將剛剛創建的HttpHandler對象作為參數設置到了request請求中,第9到15行是用來設置當前所執行線程的優先級的,因為HttpUtils是采用線程池的方式執行請求的,如果請求參數中有設置線程優先級的話,則執行11行,如果請求參數沒有設置優先級的話,則設置優先級為默認值,也就是默認情況下是按順序執行請求的,其中Priority是枚舉類型的,最關鍵的就是第16行的代碼了,executeOnExecutor這個方法我們通常會在AsyncTask源碼中遇到,因為當你通過execute方法執行AsyncTask任務的時候,實際上調用的方法是executeOnExecutor,那麼我們就可以猜測HttpHandler是一個和AsyncTask有關的類了,來看看源碼是什麼樣子的啦:
public class HttpHandler extends PriorityAsyncTask

很明顯了吧,他實現了PriorityAsyncTask抽象類,證明了我們的猜測,但是這還不夠,我們會發現HttpHandler中並沒有第16行的executeOnExecutor這個方法,顯然需要到他的父類PriorityAsyncTask中去查看該方法,這個類中存在兩個executeOnExecutor方法

 

  public final PriorityAsyncTask executeOnExecutor(Executor exec,
                                                                               Params... params) {
        return executeOnExecutor(exec, Priority.DEFAULT, params);
    }
  public final PriorityAsyncTask executeOnExecutor(Executor exec,
                                                                               Priority priority,
                                                                               Params... params) {
	}
可以看到第一個的最終執行方法還是第二個,只不過將線程的優先級設置為默認值罷了,另外這裡有一點可以看出Priority其實是和Params沒什麼關系啦,要是有的話就不可能出現兩個executeOnExecutor方法啦,我們只需要看第二個方法的實現就可以了:

 

 

public final PriorityAsyncTask executeOnExecutor(Executor exec,
                                                                               Priority priority,
                                                                               Params... params) {
        if (mExecuteInvoked) {
            throw new IllegalStateException("Cannot execute task:"
                    + " the task is already executed.");
        }

        mExecuteInvoked = true;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(new PriorityRunnable(priority, mFuture));

        return this;
    }
該方法首先判斷當前執行的任務是否已經正在執行,如果是的話,執行第5行拋出異常表示當前任務正在執行,如果當前任務沒有正在執行的話,執行第6行設置當前任務正在執行,接著執行onPreExecute方法,該方法是PriorityAsyncTask抽象類中的抽象方法,因此需要在繼承PriorityAsyncTask的類中實現,具體可以在這個方法中寫一些上傳下載前的初始化工作,比如提示用戶等等,其實從這開始就已經屬於AsyncTask的源碼范疇了,如果你對AsyncTask源碼不是太了解的話,建議看看我的另一篇博客:android-----AsyncTask源碼分析,第13行將請求參數設置到WorkerRunnable對象中,接著執行14行的execute方法,將PriorityRunnable類型的任務添加到線程池中,而該任務的執行方法也就是PriorityRunnable對象的run方法了,查看PriorityRunnable的run方法:

 

 

 @Override
    public void run() {
        this.obj.run();
    }
而這裡的obj指的就是PriorityRunnable構造函數的第二個參數,從源碼中也能找到答案:

 

 

 public PriorityRunnable(Priority priority, Runnable obj) {
        super(priority, obj);
    
那麼上面的run方法執行的就是mFuture的run方法了,我們先來看看mFuture的定義:

 

 

mFuture = new FutureTask(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    LogUtils.w(e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occured while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
可見他是以WorkerRunnable對象為參數的FutureTask對象,裡面的done方法我們一會介紹,執行他的run方法其實就是執行FutureTask的run方法啦,源碼如下:

 

 

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
第7行的callable對象就是前面的WorkerRunnable對象mWorker了,這點看過AsyncTask源碼的應該都知道吧,主要的一句代碼是第12行的c.call()方法,這個方法將會執行mWorker的call方法,我們來看看mWorker的定義:

 

 

 public PriorityAsyncTask() {
        mWorker = new WorkerRunnable() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }
        };
那麼在第8行的call方法裡面你看到了熟悉的doInBackground方法,這個方法在PriorityAsyncTask裡面同樣也是抽象方法,那麼他的實現就該在他的子類HttpHandler中了:

 

 

 @Override
    protected Void doInBackground(Object... params) {
        if (this.state == State.CANCELLED || params == null || params.length == 0) return null;

        if (params.length > 3) {
            fileSavePath = String.valueOf(params[1]);
            isDownloadingFile = fileSavePath != null;
            autoResume = (Boolean) params[2];
            autoRename = (Boolean) params[3];
        }

        try {
            if (this.state == State.CANCELLED) return null;
            // init request & requestUrl
            request = (HttpRequestBase) params[0];
            requestUrl = request.getURI().toString();
            if (callback != null) {
                callback.setRequestUrl(requestUrl);
            }

            this.publishProgress(UPDATE_START);

            lastUpdateTime = SystemClock.uptimeMillis();

            ResponseInfo responseInfo = sendRequest(request);
            if (responseInfo != null) {
                this.publishProgress(UPDATE_SUCCESS, responseInfo);
                return null;
            }
        } catch (HttpException e) {
            this.publishProgress(UPDATE_FAILURE, e, e.getMessage());
        }

        return null;
    }

首先如果當前線程被暫停或者請求參數為null或者沒有設置請求參數的話,直接return,第5行判斷如果請求參數的個數大於三的話(這種情況是我們在調用download方法進行文件下載的時候會進入到if語句塊中的,源碼在分析完send方法之後分析),第15行獲取到request請求,並且在16行通過request請求獲取到請求url,如果我們在創建HttpHandler的時候傳入了RequestCallBack對象的話,則在18行將請求url設置到RequestCallBack對象上面,第21行調用publishProgress方法傳入UPDATE_START參數,這個方法會調用onProgressUpdate方法,那麼他到底為什麼調用publishProgress的時候會調用onProgressUpdate方法呢?查看第21行看到他調用的是this的publishProgress方法,可以發現HttpHandler中並沒有publishProgress方法方法,那麼就需要到他的父類PriorityAsyncTask查看是否存在了,很慶幸我們找到了定義為protected的方法了,也就是說這個方法是允許子類訪問的,來看看他的定義:

 

protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult
(this, values)).sendToTarget(); } }

 

發現他實際上是通過InternalHandler類型的handler發送了一條標志為MESSAGE_POST_PROGRESS的消息,InternalHandler是PriorityAsyncTask的內部類,那麼這條消息的具體實現就是在InternalHandler的handleMessage方法裡面了:

 

    @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
找到標志為MESSAGE_POST_PROGRESS的case子句,發現他會執行PriorityAsyncTask的onProgressUpdate方法:

 

 

 @SuppressWarnings({"UnusedDeclaration"})
    protected void onProgressUpdate(Progress... values) {
    }

 

從onProgressUpdate的定義可以看出,這個方法是需要子類實現的,也就是說實際上調用的是HttpHandler的onProgressUpdate方法啦;

在看onProgressUpdate方法之前呢,我們需要看看通過HttpHandler傳入的RequestCallBack到底是什麼樣子的,查看源碼可以發現他是一個抽象類,其中抽象方法如下:

    public abstract void onSuccess(ResponseInfo responseInfo);

    public abstract void onFailure(HttpException error, String msg);
除此之外還有三個方法:
    public void onStart() {
    

    public void onCancelled() {
    }

    public void onLoading(long total, long current, boolean isUploading) {
    }

我們可以在繼承RequestCallBack抽象類的時候進行重寫這三個方法,回到doInBackground方法裡面,在第21行調用publishProgress方法傳入UPDATE_START參數後轉而執行的方法是onProgressUpdate,我們看看他的源碼:

 

protected void onProgressUpdate(Object... values) {
        if (this.state == State.CANCELLED || values == null || values.length == 0 || callback == null) return;
        switch ((Integer) values[0]) {
            case UPDATE_START:
                this.state = State.STARTED;
                callback.onStart();
                break;
            case UPDATE_LOADING:
                if (values.length != 3) return;
                this.state = State.LOADING;
                callback.onLoading(
                        Long.valueOf(String.valueOf(values[1])),
                        Long.valueOf(String.valueOf(values[2])),
                        isUploading);
                break;
            case UPDATE_FAILURE:
                if (values.length != 3) return;
                this.state = State.FAILURE;
                callback.onFailure((HttpException) values[1], (String) values[2]);
                break;
            case UPDATE_SUCCESS:
                if (values.length != 2) return;
                this.state = State.SUCCESS;
                callback.onSuccess((ResponseInfo) values[1]);
                break;
            default:
                break;
        }
    }
可以發現他是一個受保護的方法,也就是子類對這個方法是不可以進行重寫操作的,找到UPDATE_START的case子句,可以發現他會首先將標志位修改為start,隨後執行RequestCallBack對象的onStart方法,這個方法裡面我們可以實現在開始上傳之前的一些初始化操作,比如提示用戶將要上傳文件之類的信息;

接著第25行調用sendRequest方法來執行request請求返回ResponseInfo對象,來看看sendRequest方法:

 private ResponseInfo sendRequest(HttpRequestBase request) throws HttpException {

        HttpRequestRetryHandler retryHandler = client.getHttpRequestRetryHandler();
        while (true) {

            if (autoResume && isDownloadingFile) {
                File downloadFile = new File(fileSavePath);
                long fileLen = 0;
                if (downloadFile.isFile() && downloadFile.exists()) {
                    fileLen = downloadFile.length();
                }
                if (fileLen > 0) {
                    request.setHeader("RANGE", "bytes=" + fileLen + "-");
                }
            }

            boolean retry = true;
            IOException exception = null;
            try {
                requestMethod = request.getMethod();
                if (HttpUtils.sHttpCache.isEnabled(requestMethod)) {
                    String result = HttpUtils.sHttpCache.get(requestUrl);
                    if (result != null) {
                        return new ResponseInfo(null, (T) result, true);
                    }
                }

                ResponseInfo responseInfo = null;
                if (!isCancelled()) {
                    HttpResponse response = client.execute(request, context);
                    responseInfo = handleResponse(response);
                }
                return responseInfo;
            } catch (UnknownHostException e) {
                exception = e;
                retry = retryHandler.retryRequest(exception, ++retriedCount, context);
            } catch (IOException e) {
                exception = e;
                retry = retryHandler.retryRequest(exception, ++retriedCount, context);
            } catch (NullPointerException e) {
                exception = new IOException(e.getMessage());
                exception.initCause(e);
                retry = retryHandler.retryRequest(exception, ++retriedCount, context);
            } catch (HttpException e) {
                throw e;
            } catch (Throwable e) {
                exception = new IOException(e.getMessage());
                exception.initCause(e);
                retry = retryHandler.retryRequest(exception, ++retriedCount, context);
            }
            if (!retry) {
                throw new HttpException(exception);
            }
        }
    }

第3行首先獲取retry的設置,裡面可能包括一些如果連接發生錯誤需要重新開啟連接的連接次數限制等等屬性,可以看到這個方法裡面是一個while死循環,也就是說只有裡面的return語句以及連接次數超出retry中的設置值才會結束當前方法,第6到15行是download方法用來判斷是否需要斷點下載的部分,稍後我們再分析,第21行判斷是否使用了緩存,如果使用了的話則從httpCache獲取對應於我們請求的內容,並且當前內容獲取成功的話通過24行的ResponseInfo構造方法創建ResponseInfo的實例對象返回;如果沒有啟用緩存或者從緩存中獲取到的內容為空的話,則執行第29行判斷當前請求是否被暫停的判斷語句,如果沒有的話,第30行執行http請求,從網絡中獲取到對應請求的內容,31行調用handleResponse方法封裝一個ResponseInfo類型的對象出來,想必handleResponse裡面肯定有存緩存的操作啦,我們可以驗證一下:

 

 @SuppressWarnings("unchecked")
    private ResponseInfo handleResponse(HttpResponse response) throws HttpException, IOException {
        if (response == null) {
            throw new HttpException("response is null");
        }
        if (isCancelled()) return null;

        StatusLine status = response.getStatusLine();
        int statusCode = status.getStatusCode();
        if (statusCode < 300) {
            Object result = null;
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                isUploading = false;
                if (isDownloadingFile) {
                    autoResume = autoResume && OtherUtils.isSupportRange(response);
                    String responseFileName = autoRename ? OtherUtils.getFileNameFromHttpResponse(response) : null;
                    result = mFileDownloadHandler.handleEntity(entity, this, fileSavePath, autoResume, responseFileName);
                } else {
                    result = mStringDownloadHandler.handleEntity(entity, this, charset);
                    if (HttpUtils.sHttpCache.isEnabled(requestMethod)) {
                        HttpUtils.sHttpCache.put(requestUrl, (String) result, expiry);
                    }
                }
            }
            return new ResponseInfo(response, (T) result, false);
        } else if (statusCode == 301 || statusCode == 302) {
            if (httpRedirectHandler == null) {
                httpRedirectHandler = new DefaultHttpRedirectHandler();
            }
            HttpRequestBase request = httpRedirectHandler.getDirectRequest(response);
            if (request != null) {
                return this.sendRequest(request);
            }
        } else if (statusCode == 416) {
            throw new HttpException(statusCode, "maybe the file has downloaded completely");
        } else {
            throw new HttpException(statusCode, status.getReasonPhrase());
        }
        return null;
    }
第6行如果當前請求被暫停的話,直接返回null,第9行獲取請求的返回狀態碼,如果狀態碼小於300的話表示請求成功類型,獲取到返回結果的內容實體HttpEntity,第15行到18行同樣也是屬於download下載文件部分,稍後我們分析,我們需要重點看看第20行了,因為正是這行才能夠允許我們在上傳的過程中實時查看上傳進度進而顯示上傳進度條來提示用戶,mFileDownloadHandler是StringDownloadHandler類型的對象,我們看看StringDownloadHandler裡面的handleEntity方法:

 

 

 public String handleEntity(HttpEntity entity, RequestCallBackHandler callBackHandler, String charset) throws IOException {
        if (entity == null) return null;

        long current = 0;
        long total = entity.getContentLength();

        if (callBackHandler != null && !callBackHandler.updateProgress(total, current, true)) {
            return null;
        }

        InputStream inputStream = null;
        StringBuilder sb = new StringBuilder();
        try {
            inputStream = entity.getContent();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, charset));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line).append('\n');
                current += OtherUtils.sizeOfString(line, charset);
                if (callBackHandler != null) {
                    if (!callBackHandler.updateProgress(total, current, false)) {
                        break;
                    }
                }
            }
            if (callBackHandler != null) {
                callBackHandler.updateProgress(total, current, true);
            }
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
        return sb.toString().trim();
    }
其實思路也是很簡單啦,首先14行獲取到返回的輸入流,接著17行通過循環來操作輸入流,只不過在每次循環過程中記錄下已經上傳了的字節數,接著調用RequestCallBackHandler的updateProgress方法,注意這裡的RequestCallBackHandler是一個接口,這個方法的第三個參數表示的是是否馬上更新UI,因為下載結束的時候就是我們需要更新UI的時候,所以第27行的第3個參數是true;那麼updateProgress方法具體會執行什麼呢?這個需要我們分析下類結構了,因為HttpHandler是實現RequestCallBackHandler接口的,所以實際上是執行HttpHandler裡面的updateProgress方法的,來看看這個方法裡面做了些什麼吧:

 

 

 @Override
    public boolean updateProgress(long total, long current, boolean forceUpdateUI) {
        if (callback != null && this.state != State.CANCELLED) {
            if (forceUpdateUI) {
                this.publishProgress(UPDATE_LOADING, total, current);
            } else {
                long currTime = SystemClock.uptimeMillis();
                if (currTime - lastUpdateTime >= callback.getRate()) {
                    lastUpdateTime = currTime;
                    this.publishProgress(UPDATE_LOADING, total, current);
                }
            }
        }
        return this.state != State.CANCELLED;
    }
從關鍵字override就可以看出他是實現接口中的方法的,這個方法會根據forceUpdateUI的值來決定執行if語句塊還是else語句塊,但是他們最後都會執行publishProgress,並且傳入的標志參數都是UPDATE_LOADING,很顯然接下來執行的就是onProgressUpdate方法了,查看case語句標志是UPDATE_LOADING的部分:

 

 

 case UPDATE_LOADING:
                if (values.length != 3) return;
                this.state = State.LOADING;
                callback.onLoading(
                        Long.valueOf(String.valueOf(values[1])),
                        Long.valueOf(String.valueOf(values[2])),
                        isUploading);
                break;
可以看出它會執行我們RequestCallBack的onLoading方法,這個方法呢也就是我們平時實現RequestCallBack接口時需要實現的方法了;

繼續回到handleResponse方法中,在20行我們獲取到result值之後,21行判斷是否開啟了緩存功能,如果開啟的話,則將當前值添加到緩存中,並且在第26行返回一個封裝好的ResponseInfo對象;

如果返回狀態碼是301或者302的話,表示需要進行重定向操作了;

返回狀態碼是416的話表示文件早已下載結束了;

這樣handleResponse方法分析結束,接著我們的sendRequest方法分析,如果我們在請求的過程中出現異常的話,就會執行34---50行部分,可以看到這部分都有一個retryRequest方法,目的就是用來進行請求失敗之後重新請求的,至於最多請求多少次呢,sendRequest方法的第3行的HttpRequestRetryHandler的對象中就有啦,這樣我們的整個請求就結束啦,請求結束之後我們有時候就需要來通知用戶上傳或者下載成功了,那麼這裡的調用時機是在什麼時候呢?不用說,當然是回到doInBackground方法中了,第25行通過sendRequest方法獲取到請求結果之後,26行判斷該請求結果是否為空,不為空的話調用publishProgress方法,傳入的標志信息是UPDATE_SUCCESS,此時就會執行onProgressUpdate的case值是UPDATE_SUCCESS的語句塊,進而執行RequestCallBack的onSuccess方法,在這個方法中我們就可以進行提示用戶的操作了;如果說在doInBackground方法中13--29行部分出現異常的話,那麼他會執行標志信息是UPDATE_FAILURE的onProgressUpdate case語句塊,進而調用RequestCallBack的onFailure方法,這個方法是用戶自己實現的,在此方法中可以提示用戶下載失敗的信息;

好了,在上面我們並沒有分析mFuture的done方法,現在是時候分析一下了,為了方便查看,我們再次貼出mFuture的定義:

 

 mFuture = new FutureTask(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    LogUtils.w(e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occured while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
done方法的執行時機是當前線程正常執行結束或者執行過程中發生異常現象,done方法的第一句是postResultIfNotInvoked方法,該方法的定義是:

 

 

   private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }
也就是說如果當前線程沒有被觸發的話,他會調用postResult方法:

 

 

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult(this, result));
        message.sendToTarget();
        return result;
    }
很明顯,在postResult方法裡面,我們通過InternalHandler類型的handler發送了一條標志為MESSAGE_POST_RESULT的消息,InternalHandler是PriorityAsyncTask的內部私有類,具體的處理該消息的操作應該是出現在InternalHandler的handleMessage裡面:

 

 

 @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
查看MESSAGE_POST_RESULT的case子句,它會執行PriorityAsyncTask的finish方法,這個方法的定義如下:

 

 

  private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
    }
可以看出如果當前線程被暫停的話執行onCancelled方法,這個方法在PriorityAsyncTask中並沒有實現,也就是說實際上調用的是繼承自PriorityAsyncTask抽象類的HttpHandler裡的onCancelled方法,如果當前線程沒有被暫停的話則執行onPostExecute方法,同樣這個方法PriorityAsyncTask也沒實現,實際上執行的是HttpHandler裡的;

 

至此通過Httputils實現上傳操作的源碼分析完畢了,當然這個只是android客戶端在上傳操作過程中的一些操作,我們還需要相應的服務器端代碼來配合實現真正的上傳操作,上面分析的過程中我們都避開了download文件下載部分的代碼,其實下載部分的代碼分析流程和上傳過程是非常類似的,只不過在下載的過程中我們需要處理一些斷點下載、重命名下載的問題,下面簡單分析一下:

眾所周知,使用HttpUtils實現下載操作只需要調用HttpUtils對象的download方法就可以了,這個方法有多個重載類型實現,但是最終他們都會執行到下面這個方法:

 

  public HttpHandler download(HttpRequest.HttpMethod method, String url, String target,
                                      RequestParams params, boolean autoResume, boolean autoRename, RequestCallBack callback) {

        if (url == null) throw new IllegalArgumentException("url may not be null");
        if (target == null) throw new IllegalArgumentException("target may not be null");

        HttpRequest request = new HttpRequest(method, url);

        HttpHandler handler = new HttpHandler(httpClient, httpContext, responseTextCharset, callback);

        handler.setExpiry(currentRequestExpiry);
        handler.setHttpRedirectHandler(httpRedirectHandler);
        request.setRequestParams(params, handler);

        handler.executeOnExecutor(EXECUTOR, request, target, autoResume, autoRename);
        return handler;
    }

可以發現和send方法的最大差別在第15行部分,這裡在調用executeOnExecutor方法時傳入的參數要多於send方法,這裡面的target指的是下載文件的存儲路徑,autoResume指的是是否允許在下載暫停恢復之後從斷點處下載,autoRename指的是當下載完成之後是否需要根據返回的頭部信息來重命名下載的文件,這兩個值默認情況下都是false,接著真正的執行者就是PriorityAsyncTask裡面的

executeOnExecutor方法了,通過mFuture的run方法調用mWorker的call方法,而call方法中存在doInBackground方法,在PriorityAsyncTask中他又是沒有實現的方法,因而它會執行HttpHandler的doInBackground方法,重點就來了,正是這個方法裡面有一些處理斷點下載以及重命名下載方面的內容,doInBackground中會執行sendRequest方法,sendRequest方法的前幾行有如下代碼:

 

if (autoResume && isDownloadingFile) {
                File downloadFile = new File(fileSavePath);
                long fileLen = 0;
                if (downloadFile.isFile() && downloadFile.exists()) {
                    fileLen = downloadFile.length();
                }
                if (fileLen > 0) {
                    request.setHeader("RANGE", "bytes=" + fileLen + "-");
                }
            }
可以看到首先會判斷是否允許斷點下載以及是否正在下載文件isDownloadingFile的默認值是false,if條件成立的話,則執行if語句塊,如果當前要下載的文件已經存在的話,則獲取已經下載的文件大小,並且設置request請求頭的RANGE字段為當前文件的大小,也就是說當前request請求並不需要從頭開始下載文件了,這點達到了斷點下載的目的,至於重命名下載文件的實現呢,具體實現是在HttpHandler的handleResponse方法裡面的,這個方法裡面有下面一句代碼:
 result = mFileDownloadHandler.handleEntity(entity, this, fileSavePath, autoResume, responseFileName);
那麼真正的實現就該在FileDownloadHandler裡的handleEntity方法裡面啦,這個方法主要就是一些http進行請求下載文件的操作,想了解的可以自行看看啦;

 

至此,這幾天的學習總結完畢,希望能夠幫助到大家,有什麼錯誤的地方望指正,贈人玫瑰,手留余香!!!!!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved