美文网首页
由OSS文件上传“借题发挥”

由OSS文件上传“借题发挥”

作者: 苹果tree | 来源:发表于2018-10-11 15:05 被阅读94次

    前言

    功能实现思优化,程序员的奋斗路漫漫。
    最近项目接入了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接口,支持重试,但是如何设置等待时间,对因网络情况,文件过大或服务器原因等造成的失败如何区别处理,分配不同的优先级?多想一步总会多条思路。

    相关文章

      网友评论

          本文标题:由OSS文件上传“借题发挥”

          本文链接:https://www.haomeiwen.com/subject/xudyaftx.html