修复在activity 的onStop中取消注册导致的内存泄露问题

pull/330/head
laoyuyu 7 years ago
parent cf65959bf9
commit 9cad84127d
  1. 57
      Aria/src/main/java/com/arialyy/aria/core/AriaManager.java
  2. 8
      Aria/src/main/java/com/arialyy/aria/core/WidgetLiftManager.java
  3. 52
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java
  4. 63
      Aria/src/main/java/com/arialyy/aria/core/inf/AbsReceiver.java
  5. 28
      Aria/src/main/java/com/arialyy/aria/core/inf/IReceiver.java
  6. 33
      Aria/src/main/java/com/arialyy/aria/core/inf/ReceiverType.java
  7. 51
      Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java
  8. 6
      Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java
  9. 148
      Aria/src/main/java/com/arialyy/aria/util/FileUtil.java
  10. 2
      app/src/main/java/com/arialyy/simple/download/SingleTaskActivity.java
  11. 2
      build.gradle

@ -25,6 +25,7 @@ import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.widget.PopupWindow;
import com.arialyy.aria.core.command.ICmd;
import com.arialyy.aria.core.common.QueueMod;
@ -293,39 +294,10 @@ import org.xml.sax.SAXException;
return receiver;
}
/**
* 不允许在"onDestroy""finish""onStop"这三个方法中添加注册器
*/
private AbsReceiver checkTarget(String key, AbsReceiver receiver, Object obj,
boolean needRmReceiver) {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
int i = 0;
for (StackTraceElement e : stack) {
String name = e.getClassName();
if (!name.equals(AriaManager.class.getName())) {
i++;
} else {
break;
}
}
i += 4;
String methodName = stack[i].getMethodName();
boolean isDestroyed =
methodName.equals("onDestroy") || methodName.equals("finish") || methodName.equals(
"onStop");
if (isDestroyed) {
ALog.e(TAG,
"请不要在Activity或Fragment的onDestroy、finish、onStop等方法中注册Aria,Aria的unRegister会在Activity页面销毁时自动执行");
}
if (obj instanceof Activity && isDestroyed) {
return receiver;
} else if (obj instanceof Fragment && isDestroyed) {
return receiver;
}
receiver.targetName = obj.getClass().getName();
receiver.obj = obj;
AbsReceiver.OBJ_MAP.put(receiver.getKey(), obj);
receiver.needRmListener = needRmReceiver;
mReceivers.put(key, receiver);
return receiver;
@ -430,29 +402,16 @@ import org.xml.sax.SAXException;
/**
* 移除指定对象的receiver
*/
public void removeReceiver(Object obj) {
if (obj == null) return;
String clsName = obj.getClass().getName();
for (Iterator<Map.Entry<String, AbsReceiver>> iter = mReceivers.entrySet().iterator();
iter.hasNext(); ) {
Map.Entry<String, AbsReceiver> entry = iter.next();
String key = entry.getKey();
if (key.contains(clsName)) {
iter.remove();
public void removeReceiver(String targetName) {
if (TextUtils.isEmpty(targetName)) {
ALog.e(TAG, "target name null");
return;
}
}
}
/**
* Aria注册对象被销毁时调用
*/
void destroySchedulerListener(Object obj) {
String clsName = obj.getClass().getName();
for (Iterator<Map.Entry<String, AbsReceiver>> iter = mReceivers.entrySet().iterator();
iter.hasNext(); ) {
Map.Entry<String, AbsReceiver> entry = iter.next();
String key = entry.getKey();
if (key.contains(clsName)) {
if (key.contains(targetName)) {
AbsReceiver receiver = mReceivers.get(key);
if (receiver != null) {
receiver.unRegisterListener();
@ -493,7 +452,7 @@ import org.xml.sax.SAXException;
}
@Override public void onActivityDestroyed(Activity activity) {
destroySchedulerListener(activity);
removeReceiver(activity.getClass().getName());
// TODO: 2018/4/11 维护一个activity堆栈,应用被kill,activity会回调onDestroy方法,需要考虑server后台情况
}
}

@ -80,7 +80,7 @@ final class WidgetLiftManager {
private PopupWindow.OnDismissListener createPopupWindowListener(final PopupWindow popupWindow) {
return new PopupWindow.OnDismissListener() {
@Override public void onDismiss() {
AriaManager.getInstance(AriaManager.APP).destroySchedulerListener(popupWindow);
AriaManager.getInstance(AriaManager.APP).removeReceiver(popupWindow.getClass().getName());
}
};
}
@ -120,7 +120,8 @@ final class WidgetLiftManager {
return new Dialog.OnCancelListener() {
@Override public void onCancel(DialogInterface dialog) {
AriaManager.getInstance(AriaManager.APP).destroySchedulerListener(dialog);
AriaManager.getInstance(AriaManager.APP)
.destroySchedulerListener(dialog.getClass().getName());
}
};
}
@ -132,7 +133,8 @@ final class WidgetLiftManager {
return new Dialog.OnDismissListener() {
@Override public void onDismiss(DialogInterface dialog) {
AriaManager.getInstance(AriaManager.APP).destroySchedulerListener(dialog);
AriaManager.getInstance(AriaManager.APP)
.destroySchedulerListener(dialog.getClass().getName());
}
};
}

@ -18,6 +18,7 @@ package com.arialyy.aria.core.download;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.command.ICmd;
import com.arialyy.aria.core.command.normal.CancelAllCmd;
@ -27,6 +28,7 @@ import com.arialyy.aria.core.download.wrapper.DGEWrapper;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.inf.AbsReceiver;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.ReceiverType;
import com.arialyy.aria.core.manager.TEManager;
import com.arialyy.aria.core.scheduler.DownloadGroupSchedulers;
import com.arialyy.aria.core.scheduler.DownloadSchedulers;
@ -229,15 +231,23 @@ public class DownloadReceiver extends AbsReceiver {
* 将当前类注册到Aria
*/
public void register() {
String className = obj.getClass().getName();
if (TextUtils.isEmpty(targetName)) {
ALog.e(TAG, "download register target null");
return;
}
Object obj = OBJ_MAP.get(getKey());
if (obj == null) {
ALog.e(TAG, String.format("【%s】观察者为空", targetName));
return;
}
Set<String> dCounter = ProxyHelper.getInstance().downloadCounter;
Set<String> dgCounter = ProxyHelper.getInstance().downloadGroupCounter;
Set<String> dgsCounter = ProxyHelper.getInstance().downloadGroupSubCounter;
if (dCounter != null && dCounter.contains(className)) {
if (dCounter != null && dCounter.contains(targetName)) {
DownloadSchedulers.getInstance().register(obj);
}
if ((dgCounter != null && dgCounter.contains(className)) || (dgsCounter != null
&& dgsCounter.contains(className))) {
if ((dgCounter != null && dgCounter.contains(targetName)) || (dgsCounter != null
&& dgsCounter.contains(targetName))) {
DownloadGroupSchedulers.getInstance().register(obj);
}
}
@ -251,25 +261,37 @@ public class DownloadReceiver extends AbsReceiver {
if (needRmListener) {
unRegisterListener();
}
AriaManager.getInstance(AriaManager.APP).removeReceiver(obj);
AriaManager.getInstance(AriaManager.APP).removeReceiver(targetName);
}
@Override protected String getType() {
return ReceiverType.DOWNLOAD;
}
@Override public void unRegisterListener() {
String className = obj.getClass().getName();
if (TextUtils.isEmpty(targetName)) {
ALog.e(TAG, "download unRegisterListener target null");
return;
}
Object obj = OBJ_MAP.get(getKey());
if (obj == null) {
ALog.e(TAG, String.format("【%s】观察者为空", targetName));
return;
}
Set<String> dCounter = ProxyHelper.getInstance().downloadCounter;
Set<String> dgCounter = ProxyHelper.getInstance().downloadGroupCounter;
Set<String> dgsCounter = ProxyHelper.getInstance().downloadGroupSubCounter;
if (dCounter != null && dCounter.contains(className)) {
if (dCounter != null && dCounter.contains(targetName)) {
DownloadSchedulers.getInstance().unRegister(obj);
}
if (dgCounter != null && dgCounter.contains(className) || (dgsCounter != null
&& dgsCounter.contains(className))) {
if (dgCounter != null && dgCounter.contains(targetName) || (dgsCounter != null
&& dgsCounter.contains(targetName))) {
DownloadGroupSchedulers.getInstance().unRegister(obj);
}
}
@Override public void destroy() {
targetName = null;
removeObj();
}
/**
@ -338,8 +360,10 @@ public class DownloadReceiver extends AbsReceiver {
/**
* 下载任务是否存在
*
* @return {@code true}存在{@code false} 不存在
*/
@Override public boolean taskExists(String downloadUrl) {
public boolean taskExists(String downloadUrl) {
return DownloadEntity.findFirst(DownloadEntity.class, "url=?", downloadUrl) != null;
}
@ -362,7 +386,7 @@ public class DownloadReceiver extends AbsReceiver {
* 获取未完成的普通任务列表{@link #getAllNotCompletTask()}
* 获取已经完成的普通任务列表{@link #getAllCompleteTask()}
*/
@Override public List<DownloadEntity> getTaskList() {
public List<DownloadEntity> getTaskList() {
return DownloadEntity.findDatas(DownloadEntity.class, "isGroupChild=? and downloadPath!=''",
"false");
}
@ -419,7 +443,7 @@ public class DownloadReceiver extends AbsReceiver {
/**
* 停止所有正在下载的任务并清空等待队列
*/
@Override public void stopAllTask() {
public void stopAllTask() {
AriaManager.getInstance(AriaManager.APP)
.setCmd(NormalCmdFactory.getInstance()
.createCmd(targetName, new DownloadTaskEntity(), NormalCmdFactory.TASK_STOP_ALL,
@ -446,7 +470,7 @@ public class DownloadReceiver extends AbsReceiver {
* @param removeFile {@code true} 删除已经下载完成的任务不仅删除下载记录还会删除已经下载完成的文件{@code false}
* 如果文件已经下载完成只删除下载记录
*/
@Override public void removeAllTask(boolean removeFile) {
public void removeAllTask(boolean removeFile) {
final AriaManager ariaManager = AriaManager.getInstance(AriaManager.APP);
CancelAllCmd cancelCmd =
(CancelAllCmd) CommonUtil.createNormalCmd(targetName, new DownloadTaskEntity(),

@ -16,19 +16,76 @@
package com.arialyy.aria.core.inf;
import com.arialyy.aria.core.queue.DownloadGroupTaskQueue;
import com.arialyy.aria.core.queue.DownloadTaskQueue;
import com.arialyy.aria.core.queue.UploadTaskQueue;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by AriaL on 2017/6/27.
* 接收器
*/
public abstract class AbsReceiver implements IReceiver {
/**
* 观察者对象map
* key {@link #getKey(String, IReceiver)}指定
*/
public static final Map<String, Object> OBJ_MAP = new ConcurrentHashMap<>();
public abstract class AbsReceiver<ENTITY extends AbsEntity> implements IReceiver<ENTITY> {
public String targetName;
public Object obj;
/**
* 当dialogdialogFragmentpopupwindow已经被设置了关闭监听时需要手动移除receiver
*/
public boolean needRmListener = false;
public void unRegisterListener(){
/**
* 创建观察者对象map的key生成规则
* {@link #targetName}_{@code download}{@code upload}_{@link #hashCode()}
*
* @param type {@link ReceiverType}
* @param receiver 当前接收器
* @return 返回key
*/
public static String getKey(@ReceiverType String type, IReceiver receiver) {
return String.format("%s_%s_%s", receiver.getTargetName(), type, receiver.hashCode());
}
@Override public String getTargetName() {
return targetName;
}
/**
* 获取当前Receiver的key
*/
@Override public String getKey() {
return getKey(getType(), this);
}
/**
* 移除观察者对象
*/
protected void removeObj() {
for (Iterator<Map.Entry<String, Object>> iter = OBJ_MAP.entrySet().iterator();
iter.hasNext(); ) {
Map.Entry<String, Object> entry = iter.next();
String key = entry.getKey();
if (key.equals(getKey())) {
iter.remove();
}
}
}
/**
* 设置类型
*
* @return {@link ReceiverType}
*/
protected abstract @ReceiverType String getType();
/**
* 移除{@link DownloadTaskQueue}{@link DownloadGroupTaskQueue}{@link UploadTaskQueue}中注册的观察者
*/
public abstract void unRegisterListener();
}

@ -15,42 +15,32 @@
*/
package com.arialyy.aria.core.inf;
import java.util.List;
/**
* Created by lyy on 2017/2/6.
*/
public interface IReceiver<ENTITY extends IEntity> {
public interface IReceiver {
/**
* Receiver 销毁
*/
void destroy();
/**
* 移除观察者
*/
void unRegister();
/**
* 停止所有任务
* 注册
*/
void stopAllTask();
void register();
/**
* 删除所有任务
* 移除观察者
*/
void removeAllTask(boolean removeFile);
void unRegister();
/**
* 任务是否存在
*
* @param key 下载时为下载路径上传时为文件路径
* 观察者对象的类完整名称
*/
boolean taskExists(String key);
String getTargetName();
/**
* 获取任务列表
* 获取当前Receiver的key
*/
List<ENTITY> getTaskList();
String getKey();
}

@ -0,0 +1,33 @@
/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.core.inf;
import android.support.annotation.StringDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* {@link AbsReceiver}类型
*/
@StringDef({
ReceiverType.DOWNLOAD,
ReceiverType.UPLOAD,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ReceiverType {
String DOWNLOAD = "download";
String UPLOAD = "upload";
}

@ -23,8 +23,10 @@ import com.arialyy.aria.core.command.ICmd;
import com.arialyy.aria.core.command.normal.NormalCmdFactory;
import com.arialyy.aria.core.common.ProxyHelper;
import com.arialyy.aria.core.inf.AbsReceiver;
import com.arialyy.aria.core.inf.ReceiverType;
import com.arialyy.aria.core.scheduler.UploadSchedulers;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.util.CommonUtil;
import java.util.List;
@ -34,7 +36,7 @@ import java.util.Set;
* Created by lyy on 2017/2/6.
* 上传功能接收器
*/
public class UploadReceiver extends AbsReceiver<UploadEntity> {
public class UploadReceiver extends AbsReceiver {
private static final String TAG = "UploadReceiver";
/**
@ -71,9 +73,11 @@ public class UploadReceiver extends AbsReceiver<UploadEntity> {
}
/**
* 下载任务是否存在
* 上传任务是否存在
*
* @return {@code true}存在{@code false} 不存在
*/
@Override public boolean taskExists(String filePath) {
public boolean taskExists(String filePath) {
return DbEntity.findFirst(UploadEntity.class, "filePath=?", filePath) != null;
}
@ -82,7 +86,7 @@ public class UploadReceiver extends AbsReceiver<UploadEntity> {
* 获取未完成的普通任务列表{@link #getAllNotCompletTask()}
* 获取已经完成的普通任务列表{@link #getAllCompleteTask()}
*/
@Override public List<UploadEntity> getTaskList() {
public List<UploadEntity> getTaskList() {
return DbEntity.findAllData(UploadEntity.class);
}
@ -102,7 +106,10 @@ public class UploadReceiver extends AbsReceiver<UploadEntity> {
"isGroupChild=? and isComplete=?", "false", "true");
}
@Override public void stopAllTask() {
/**
* 停止所有正在下载的任务并清空等待队列
*/
public void stopAllTask() {
AriaManager.getInstance(AriaManager.APP)
.setCmd(NormalCmdFactory.getInstance()
.createCmd(targetName, new UploadTaskEntity(), NormalCmdFactory.TASK_STOP_ALL,
@ -116,7 +123,7 @@ public class UploadReceiver extends AbsReceiver<UploadEntity> {
* @param removeFile {@code true} 删除已经上传完成的任务不仅删除上传记录还会删除已经上传完成的文件{@code false}
* 如果文件已经上传完成只删除上传记录
*/
@Override public void removeAllTask(boolean removeFile) {
public void removeAllTask(boolean removeFile) {
final AriaManager am = AriaManager.getInstance(AriaManager.APP);
am.setCmd(CommonUtil.createNormalCmd(targetName, new UploadTaskEntity(),
@ -129,16 +136,24 @@ public class UploadReceiver extends AbsReceiver<UploadEntity> {
}
@Override public void destroy() {
targetName = null;
removeObj();
}
/**
* 将当前类注册到Aria
*/
public void register() {
String className = obj.getClass().getName();
if (TextUtils.isEmpty(targetName)) {
ALog.e(TAG, "upload register target null");
return;
}
Object obj = OBJ_MAP.get(getKey());
if (obj == null) {
ALog.e(TAG, String.format("【%s】观察者为空", targetName));
return;
}
Set<String> cCounter = ProxyHelper.getInstance().uploadCounter;
if (cCounter != null && cCounter.contains(className)) {
if (cCounter != null && cCounter.contains(targetName)) {
UploadSchedulers.getInstance().register(obj);
}
}
@ -151,13 +166,25 @@ public class UploadReceiver extends AbsReceiver<UploadEntity> {
if (needRmListener) {
unRegisterListener();
}
AriaManager.getInstance(AriaManager.APP).removeReceiver(obj);
AriaManager.getInstance(AriaManager.APP).removeReceiver(targetName);
}
@Override protected String getType() {
return ReceiverType.UPLOAD;
}
@Override public void unRegisterListener() {
String className = obj.getClass().getName();
if (TextUtils.isEmpty(targetName)) {
ALog.e(TAG, "upload unRegisterListener target null");
return;
}
Object obj = OBJ_MAP.get(getKey());
if (obj == null) {
ALog.e(TAG, String.format("【%s】观察者为空", targetName));
return;
}
Set<String> dCounter = ProxyHelper.getInstance().uploadCounter;
if (dCounter != null && dCounter.contains(className)) {
if (dCounter != null && dCounter.contains(targetName)) {
UploadSchedulers.getInstance().unRegister(obj);
}
}

@ -23,7 +23,6 @@ import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.FtpUrlEntity;
import com.arialyy.aria.core.command.ICmd;
@ -106,6 +105,11 @@ public class CommonUtil {
public static List<String> getPkgClassName(String path, String filterClass) {
List<String> list = new ArrayList<>();
try {
File file = new File(path);
if (!file.exists()) {
ALog.w(TAG, String.format("路径【%s】下的Dex文件不存在", path));
return list;
}
DexFile df = new DexFile(path);//通过DexFile查找当前的APK中可执行文件
Enumeration<String> enumeration = df.entries();//获取df中的元素 这里包含了所有可执行的类名 该类名包含了包名+类名的方式

@ -0,0 +1,148 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
/**
* 文件操作工具类
*/
public class FileUtil {
private static final String TAG = "FileUtil";
/**
* 合并文件
*
* @param targetPath 目标文件
* @param subPaths 碎片文件路径
*/
public static void mergeFile(String targetPath, List<String> subPaths) {
File file = new File(targetPath);
FileOutputStream fos = null;
FileChannel foc = null;
try {
if (!file.exists()) {
file.createNewFile();
fos = new FileOutputStream(targetPath);
foc = fos.getChannel();
List<FileInputStream> streams = new LinkedList<>();
for (String subPath : subPaths) {
File f = new File(subPath);
if (!f.exists()) {
ALog.d(TAG, String.format("合并文件失败,文件【%s】不存在", subPath));
for (FileInputStream fis : streams) {
fis.close();
}
streams.clear();
return;
}
streams.add(new FileInputStream(subPath));
}
Enumeration<FileInputStream> en = Collections.enumeration(streams);
SequenceInputStream sis = new SequenceInputStream(en);
ReadableByteChannel fic = Channels.newChannel(sis);
ByteBuffer bf = ByteBuffer.allocate(8196);
while (fic.read(bf) != -1) {
bf.flip();
foc.write(bf);
bf.compact();
}
fic.close();
sis.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (foc != null) {
foc.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 分割文件
*
* @param filePath 被分割的文件路径
* @param num 分割的块数
*/
public static void splitFile(String filePath, int num) {
try {
final File file = new File(filePath);
long size = file.length();
FileInputStream fis = new FileInputStream(file);
FileChannel fic = fis.getChannel();
long j = 0;
long block = size / num;
for (int i = 0; i < num; i++) {
if (i == num - 1) {
block = size - block * (num - 1);
}
String subPath = file.getPath() + "." + i + ".part";
ALog.d(TAG, String.format("block = %s", block));
File subFile = new File(subPath);
if (!subFile.exists()) {
subFile.createNewFile();
}
FileOutputStream fos = new FileOutputStream(subFile);
FileChannel sfoc = fos.getChannel();
ByteBuffer bf = ByteBuffer.allocate(8196);
int len;
//fis.skip(block * i);
while ((len = fic.read(bf)) != -1) {
bf.flip();
sfoc.write(bf);
bf.compact();
j += len;
if (j >= block * (i + 1)) {
break;
}
}
ALog.d(TAG, String.format("len = %s", subFile.length()));
fos.close();
sfoc.close();
}
fis.close();
fic.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

@ -254,6 +254,6 @@ public class SingleTaskActivity extends BaseActivity<ActivitySingleBinding> {
@Override protected void onStop() {
super.onStop();
//Aria.download(this).unRegister();
Aria.download(this).unRegister();
}
}

@ -39,7 +39,7 @@ task clean(type: Delete) {
ext {
userOrg = 'arialyy'
groupId = 'com.arialyy.aria'
publishVersion = '3.4.2_dev6'
publishVersion = '3.4.3_dev1'
// publishVersion = '1.0.3' //FTP插件
repoName='maven'
desc = 'android 下载框架'

Loading…
Cancel
Save