最近要完成一个批量上传本地图片的功能
1.上传单张的话,只要用简单的Retrofit上传,即可,但是产品要求做到批量上传功能,特地看了一下QQ空间的上传图片功能,并且分析了一下;
2.QQ空间上传的时候,有一个进度条在页面展示,点击进去是一个多线程上传过程,这就要数据库保存上传的图片数据,并且多线程上传,在压缩的时候,感觉容易出现OOM;
3.而且要保持数据在后台上传,就必须要有一个Service在后台跑,而且还要回调给某些界面显示,可用广播或者是RxBus等数据总线完成;
完成上面的分析,直接上代码吧
4.最开始是数据库表的设计,是用GreenDao,实体类如下
@Entity
public class UploadImageEntity implements android.os.Parcelable {
@Id
private Long id;
@Property(nameInDb = "DIR_ID")
private String dirId; //目录id
@Property(nameInDb = "TASK_ID")
private String taskId;
@Property(nameInDb = "CUST_ID")
private String custId; //用户id
@Property(nameInDb = "SAVE_BIZ_TYPE")
private boolean saveBizType;//是否关联业务
@Property(nameInDb = "STATE")
private int state; //0排队中,1上传成功,2上传中,3上传失败
@Property(nameInDb = "PATH")
private String path; //图片路径
@Property(nameInDb = "IMAGE_NAME")
private String imageName;
@Property(nameInDb = "PROGRESS")
private long progress;
@Property(nameInDb = "DIR_NAME")
private String dirName;
@Property(nameInDb = "IMAGE_SIZE")
private long imageSize;
@Property(nameInDb = "UPLOAD_ID")
private String uploadId;
@Property(nameInDb = "SELECT_DIR_ID")
private String selectDirId;
@Property(nameInDb = "CUST_NAME")
private String custName; //对应客户的名字
@Property(nameInDb = "CUST_TYPE")
private String custType; //客户类型
@Property(nameInDb = "USER_ID")
private String userId; //登录用户的id
@Property(nameInDb = "UPLOAD_COUNT")
private String uploadCount; //图片上传成功的个数
@Property(nameInDb = "IMAGE_COUNT")
private String imageCount; //该用户总共上传的个数
@Property(nameInDb = "FAILURE_COUNT")
private String failCount; //图片上传失败的个数
对应的DBHelper
public class DBHelper {
private static final String DB_NAME = "upload_task.db";//数据库名称
private static DBHelper instance;
private static DBManager<UploadTaskEntity, Long> uploadTask;
private static DBManager<UploadImageEntity, Long> uploadImage;
private DaoMaster.DevOpenHelper mHelper;
private DaoMaster mDaoMaster;
private DaoSession mDaoSession;
private DBHelper() {
}
public static DBHelper getInstance() {
if (instance == null) {
synchronized (DBHelper.class) {
if (instance == null) {
instance = new DBHelper();
}
}
}
return instance;
}
public void init(Context context) {
mHelper = new DaoMaster.DevOpenHelper(context, DB_NAME, null);
mDaoMaster = new DaoMaster(mHelper.getWritableDatabase());
mDaoSession = mDaoMaster.newSession();
QueryBuilder.LOG_SQL = true;
QueryBuilder.LOG_VALUES = true;
}
public void init(Context context, String dbName) {
mHelper = new DaoMaster.DevOpenHelper(context, dbName, null);
mDaoMaster = new DaoMaster(mHelper.getWritableDatabase());
mDaoSession = mDaoMaster.newSession();
}
public DBManager<UploadImageEntity, Long> uploadImage() {
if (uploadImage == null) {
uploadImage = new DBManager<UploadImageEntity, Long>(){
@Override
public AbstractDao<UploadImageEntity, Long> getAbstractDao() {
return mDaoSession.getUploadImageEntityDao();
}
};
}
return uploadImage;
}
public void deleteImageUploadByUploadId(String uploadId){
uploadImage()
.queryBuilder().where(Properties.UploadId.eq(uploadId))
.buildDelete().executeDeleteWithoutDetachingEntities();
}
public void updateImageUploadState(String uploadId,int state){
StringUtils.checkUserId();
UploadImageEntity entity = uploadImage().queryBuilder()
.where(Properties.UploadId.eq(uploadId)
,Properties.UserId.eq(Net.USER_ID)).build().unique();
if (entity != null) {
entity.setState(state);
uploadImage().update(entity);
}
}
关于greenDao的使用,请自行查找
关键的看对应的service和task任务
public class UploadService extends Service {
public static final String ACTION_START = "ACTION_START"; //开始
public static final String ACTION_CONTINUE = "ACTION_CONTINUE"; //继续
public static final String ACTION_STOP = "ACTION_STOP";
public static final String ACTION_FAILURE = "ACTION_FAILURE";
public static final String ACTION_UPDATE = "ACTION_UPDATE";
public static final String ACTION_FINISH = "ACTION_FINISH";
public static final String ACTION_DELETE = "ACTION_DELETE";
public static final int DOWN_INIT = 0;
private UploadImageTaskEntity imageTaskEntity;
private String actionType;
// 下载任务集合
private static HashMap<String,UploadTask> mTasks = new HashMap<>();
public Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DOWN_INIT:
//启动下载任务
UploadTask mTask = new UploadTask(UploadService.this, imageTaskEntity);
if(actionType.equals(ACTION_START)) {
mTask.upload();
}else if(actionType.equals(ACTION_CONTINUE)) {
mTask.continueDownload(uploadImageEntity);
}
// 把下载任务添加到集合
mTasks.put(imageTaskEntity.getTaskId(), mTask);
break;
default:
break;
}
}
};
private UploadImageEntity uploadImageEntity;
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(intent == null) {
return super.onStartCommand(intent, flags, startId);
}
//开始下载传过来的参
imageTaskEntity =intent.getParcelableExtra("uploadImageEntity");
actionType = intent.getAction();
if(ACTION_START.equals(actionType)){
//启动线程下载
mHandler.obtainMessage(DOWN_INIT, null).sendToTarget();
}else if(ACTION_CONTINUE.equals(actionType)){
uploadImageEntity = imageTaskEntity.getImageList().get(0);
mHandler.obtainMessage(DOWN_INIT, null).sendToTarget();
}else if(ACTION_STOP.equals(actionType)){
//停止
stopUpload();
}else if(ACTION_DELETE.equals(actionType)){
//停止
deleteUpload();
}
return super.onStartCommand(intent, flags, startId);
}
private void deleteUpload() {
UploadTask uploadTask = mTasks.get(imageTaskEntity.getTaskId());
String uploadId = imageTaskEntity.getImageList().get(0).getUploadId();
if(uploadTask != null) {
uploadTask.deleteUploadById(uploadId);
}else {
DBHelper.getInstance().deleteImageUploadByUploadId(uploadId);
Intent intent = new Intent(UploadService.ACTION_DELETE);
UploadService.this.sendBroadcast(intent);
}
}
public void stopUpload(){
// 暂停下载
UploadTask uploadTask = mTasks.get(imageTaskEntity.getTaskId());
if(uploadTask != null) {
uploadTask.isPause = true;
}
mTasks.remove(imageTaskEntity.getTaskId());
}
@Override
public void onDestroy() {
super.onDestroy();
destroy();
}
public static void destroy(){
LogUtils.e("destroy-------");
Iterator iter = mTasks.entrySet().iterator();
while (iter.hasNext()){
Map.Entry entry = (Map.Entry) iter.next();
String key = (String) entry.getKey();
UploadTask uploadTask = (UploadTask) entry.getValue();
if(uploadTask != null){
uploadTask.isPause = true;
uploadTask.onPause();
}
mTasks.remove(key);
}
}
}
以及Task
public class UploadTask {
private Context mContext;
private ArrayList<UploadImageEntity> mList;
private UploadImageTaskEntity taskEntity;
public boolean isPause = false;
private UploadImageEntity entity;
private File file;
private File orginaFile;
private String deleteId;
private static ExecutorService executorService = Executors.newSingleThreadExecutor();
private String uploadUrl = new String(AppConfig.Net.HOST
+AppConfig.Net.PREFIX+"gamma/cust/info/res/attach/upload");
public UploadTask(Context mContext,UploadImageTaskEntity imageTaskEntity) {
this.mContext = mContext;
this.taskEntity = imageTaskEntity;
this.mList = taskEntity.getImageList();
}
//开始上传
public void upload(){
Intent intent = new Intent(UploadService.ACTION_START);
mContext.sendBroadcast(intent);
// 启动初始化线程 获取文件大小
for(int i = 0 ; i < mList.size() ;i++) {
final UploadImageEntity imageEntity = mList.get(i);
executorService.submit(new Runnable() {
@Override
public void run() {
startUpLoad(imageEntity); //开始上传
LogUtils.e("startUpLoad");
}
});
ProgressManager.getInstance().addRequestListener(uploadUrl, getUploadListener());
}
}
@NonNull
private ProgressListener getUploadListener() {
return new ProgressListener() {
@Override
public void onProgress(ProgressInfo progressInfo) {
int progress = progressInfo.getPercent();
if(null == entity){
return;
}
entity.setProgress(progress);
entity.setState(2);
RxBus.get().post(RxBusKey.UPLOAD_STATUS,entity);
}
@Override
public void onError(long id, Exception e) {
LogUtils.w("onError"+e.getLocalizedMessage());
}
};
}
/** 单个图片上传*/
private void startUpLoad(final UploadImageEntity imageEntity){
this.entity = imageEntity;
if (!StringUtils.isEmpty(deleteId) && imageEntity.getUploadId().equals(deleteId)){
LogUtils.w("删除下载"+imageEntity.getCustName()+"*****imageName="+imageEntity.getImageName());
deleteUploadById(deleteId);
}else {
Observable.create(new ObservableOnSubscribe<List<File>>() {
@Override
public void subscribe(@NonNull final ObservableEmitter<List<File>> e) throws Exception {
try {
orginaFile = new File(imageEntity.getPath());
int maxWidth = BitmapUtil.getImageMaxWidth(imageEntity.getPath());
int ignoreBy = StringUtils.getScale(maxWidth);
LogUtils.w("ignoreBy="+ignoreBy);
LogUtils.w("orginaFile.path==" + orginaFile.getPath() + "size=" + StringUtils.bytes2MB(
orginaFile.length()));
if(ignoreBy == 100){
file = StringUtils.getSmallFile(orginaFile);
}else{
List<File> list = Luban.with(MyApplication.getContext())
.ignoreBy(ignoreBy)
.load(orginaFile).get();
file = list.get(0);
}
LogUtils.w("path==" + file.getPath() + "size=" + StringUtils.bytes2MB(file.length()));
try {
BitmapUtil.saveExif(imageEntity.getPath(), file.getPath());
} catch (Exception e1) {
e1.printStackTrace();
}
List<File> files = new ArrayList<>();
files.add(file);
e.onNext(files);
e.onComplete();
} catch (Exception ex) {
e.onError(ex);
}
}
}).flatMap(new Function<List<File>, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(@NonNull List<File> files) throws Exception {
ArrayList<File> arrayList = new ArrayList<>();
arrayList.addAll(files);
return UserApi.uploadAttchImage(imageEntity.getCustId(), imageEntity.getDirId(),imageEntity.isSaveBizType(), arrayList);
}
}).subscribe(
new SuccessSubscriber<String>(MyApplication.getInstance().getRxErrorHandler(), "3") {
@Override
protected void onSuccess(String baseResponse) {
LogUtils.e("onSuccess");
DBHelper.getInstance().deleteImageUploadByUploadId(imageEntity.getUploadId());
if (entity != null) {
entity.setState(1);
}
RxBus.get().post(RxBusKey.UPLOAD_STATUS, entity);
sendUpdateBroadcast();
file = null;
orginaFile = null;
}
@Override
public void onFailure(String msg) {
LogUtils.e("onFailure");
if (entity != null) {
entity.setState(3);
LogUtils.e("onFailure" + entity.getDirName());
}
sendUpdateBroadcast();
RxBus.get().post(RxBusKey.UPLOAD_STATUS, entity);
file = null;
orginaFile = null;
DBHelper.getInstance().updateImageUploadState(imageEntity.getUploadId(), 3);
}
});
}
}
// //下载一条后执行的任务
private void sendUpdateBroadcast() {
//删除线程中的任务
List<UploadImageEntity> uploadImageEntities = DBHelper.getInstance().uploadImage().loadAll();
int size = taskEntity.getImageList().size() - uploadImageEntities.size();
Intent intent = new Intent(UploadService.ACTION_UPDATE);
intent.putExtra("taskId", taskEntity.getTaskId());
intent.putExtra("size", size);
mContext.sendBroadcast(intent);
//如果数据库剩余下载数为0 则下载完成
if(size == 0) {
setThreadsFinished();
}
}
/**
* 全部线程下载完成执行的方法
*/
private synchronized void setThreadsFinished() {
//更新下载数据
DBHelper.getInstance().updateImageUploadState(entity.getUploadId(),1);
// 发送广播通知下载任务结束
Intent intent = new Intent(UploadService.ACTION_FINISH);
intent.putExtra("taskId", taskEntity.getTaskId());
mContext.sendBroadcast(intent);
}
/**
* 重新下载
* @param uploadImageEntity
*/
public void continueDownload(final UploadImageEntity uploadImageEntity) {
UploadTask.executorService.submit(new Runnable() {
@Override
public void run() {
//开始下载
startUpLoad(uploadImageEntity);
}
});
ProgressManager.getInstance().addRequestListener(uploadUrl, getUploadListener());
}
public void onPause(){
if(null != entity){
//当前图片上传失败
DBHelper.getInstance().updateImageUploadState(entity.getUploadId(),3);
}
Intent intent = new Intent(mContext,UploadService.class);
intent.setAction(UploadService.ACTION_STOP); //停止下载
mContext.startService(intent);
uploadUrl = null;
}
public void deleteUploadById(String uploadId) {
this.deleteId = uploadId;
DBHelper.getInstance().deleteImageUploadByUploadId(uploadId);
Intent intent = new Intent(UploadService.ACTION_DELETE);
mContext.sendBroadcast(intent);
}
}
对应的监听使用的是ProgressManager
难点总结:
1.熟悉GreenDao,自己设计数据库表;
2.okhttp上传进度的监听;
3.task任务的安排,图片压缩,oom的控制,最后我只使用了单线程还上传图片,多线程能力有限还没实现,多线程容易出现oom,目前还没解决
4.已经后台上传的状态控制,数据库操作等;
看后续能不能完成多线程上传