由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接口,支持重试,但是如何设置等待时间,对因网络情况,文件过大或服务器原因等造成的失败如何区别处理,分配不同的优先级?多想一步总会多条思路。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,331评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,372评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,755评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,528评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,526评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,166评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,768评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,664评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,205评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,290评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,435评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,126评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,804评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,276评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,393评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,818评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,442评论 2 359

推荐阅读更多精彩内容