由OSS文件上傳“借題發揮”

前言

功能實現思優化,程序員的奮鬥路漫漫。
最近項目接入了oss文件上傳功能,乍一聽,這容易啊,文檔不已經寫的明明白白了。好嘛,文檔給的通用方案好使是好使,但具體問題具體分析永遠是王道。下面我就談談具體做的優化點,還請多多指教。


思路

沿襲一貫的自問自答式思維,捋一捋這次優化的思路。
實現一個基本的上傳功能需要哪些步驟?
構造上傳請求->設置回調->調用上傳接口。有方案就有疑問。上傳線程如何被創建?又將在何時被銷燬呢?這涉及到app的內存佔用,不得不多想一步,於是打開debugger,翻開源碼。
調試可見,觸發上傳操作後多了1,2,3,4,5個線程



順藤摸瓜,果然在源碼裏找到了ExecutorService,創建的線程數正是5個。

public static final int DEFAULT_BASE_THREAD_POOL_SIZE = 5;
private static ExecutorService executorService =
        Executors.newFixedThreadPool(OSSConstants.DEFAULT_BASE_THREAD_POOL_SIZE, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "oss-android-api-thread");
            }
        });

查看調用

@Override
public OSSAsyncTask<PutObjectResult> asyncPutObject(
        PutObjectRequest request, final OSSCompletedCallback<PutObjectRequest, PutObjectResult> completedCallback) {
    return internalRequestOperation.putObject(request, completedCallback);
}

 public OSSAsyncTask<PutObjectResult> putObject(
            PutObjectRequest request, final OSSCompletedCallback<PutObjectRequest, PutObjectResult> completedCallback) {
    ··· ···
return OSSAsyncTask.wrapRequestTask(executorService.submit(callable), executionContext);
}

which means,oss內部維護了一個線程池,每次觸發上傳將從中獲得相應線程執行任務。
既然這樣,那不如··· ···創建一個遠端Service,處於新的進程,用於上傳的線程全都依附於這個新的進程,將上傳任務真正隔離,既減少了對主進程的影響,邏輯上也更清晰。

<!-- 遠端服務 -->
<service
    android:name="com.dasheng.b2s.service.RemoteService"
    android:process=":remoteservice" >
</service>

於是新建:遠端任務管理類RemoteOssTaskManager和遠程服務類RemoteService,在onStartCommand中對不同的指令做出響應,如下

switch (msg_type) {
    case REMOTE_OSS_UPLOAD_YY:
    case REMOTE_OSS_UPLOAD_AC:
        // 開啓上傳
        ··· ···
        break;
    case REMOTE_CHECK_STOP:
        // 遍歷任務列表,全部執行完畢則停止服務
        ··· ···
        break;
}

注意在任務全部上傳完畢後停止服務,以降低進程優先級。


實現

大致結構清楚了,聚焦實現,繼續思考。
構造上傳請求->設置回調->調用上傳接口。這個流程中,上傳任務都是併發麼?如此,失敗的任務如何記錄?如何定義重試規則?等等
爲了解決這些問題,我們來到之所以成爲“借題發揮”的重點。
所謂“發揮”,即如下幾點;
1.定義RemoteTaskItem對象(注意同管理類解耦),包含如下屬性

String fileLocalPath;//文件本地存儲路徑
String ossSavedPath;// oss上的存儲地址
PutObjectRequest request;
int retryCount = 0;// 重試次數
long nextUploadTime = 0;// 下次可執行時間 默認0
long lastUploadTime = 0;// 上次執行時間

2.對上傳任務的操作都通過消息發送給handler進行處理,保證單線程和任務串行。處理的消息類型如下:

private static final int MSG_TASK_SUCCESSED = 1;
private static final int MSG_TASK_FAILED = 2;
private static final int MSG_TASK_ADD = 3;
private static final int MSG_TASK_CHECK = 4;

3.輪詢任務列表,遍歷列表檢查任務狀態(下次可執行時間)
   增加無網延遲檢查
   遍歷任務列表,找到最近時間可執行的任務,等待或直接執行
   任務執行中延遲10s檢查當前任務執行狀態,超過30min沒執行完任務掛起
流程如下

private long uploadAndCheck() {
        if (!hasTask()) {   
            // 沒有待執行任務 退出輪詢
            ··· ···
            return 0;
        }
        if (網絡狀況異常) { 
            return 30000; // 無網狀態重試時間30s
        }
        // 當前任務超過最長等待時間 
        long currTime = System.currentTimeMillis();
        if (mCurrTaskItem != null) {
            if (mCurrTaskItem.lastUploadTime + MAX_EXECUTING_TIME < currTime) {
                // 任務掛起或移除
                ··· ···
            } else {
                // 任務執行中
                return 10000; // 每10s檢查當前任務執行狀態
            }
        }
        // 遍歷檢查任務隊列
        long nextUploadTime = Long.MAX_VALUE;
        for (RemoteTaskItem item : mRemoteTasks) {
            if (item.nextUploadTime <= currTime) {// 任務初次執行或已爲可執行狀態
                // 執行該任務並返回
                ··· ···
                return 10000; // 任務執行中,每10s檢查當前任務執行狀態
            } else if (nextUploadTime > item.nextUploadTime) {
                nextUploadTime = item.nextUploadTime;
            }
        }
        return nextUploadTime - currTime;
    }

下面重點看一看handler回調方法,主要流程都蘊含其中:

@Override
    public boolean handleMessage(Message msg) {
        Object obj = msg.obj;
        switch (msg.what) {
            case MSG_TASK_ADD:
                // 新任務加入上傳隊列
                ··· ···
                break;
            case MSG_TASK_CHECK:
                // 輪詢
                long ms = uploadAndCheck();
                if (ms > 0) {
                    sendUploadAndCheckAction(ms);
                }
                break;
            case MSG_TASK_SUCCESSED:
                    // 上傳成功 當前任務置空並從列表中移除
                    ··· ···
                break;
            case MSG_TASK_FAILED:
                // 上傳失敗 當前執行任務置空若超過最大重試次數或錯誤不可修復移除任務
                ··· ···
                break;
        }
        return true;
    }

最後

我們寫一個功能時除了邏輯層面,必須有業務層面的考量。就oss上傳功能而言,oss自身提供了retry接口,支持重試,但是如何設置等待時間,對因網絡情況,文件過大或服務器原因等造成的失敗如何區別處理,分配不同的優先級?多想一步總會多條思路。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章