feat:完成礼物资源预缓存功能,修改礼物播放下载方案
This commit is contained in:
@@ -23,7 +23,9 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.chwl.app.base.GlobalViewModelOwner;
|
||||
import com.chwl.app.star.StarFragment;
|
||||
import com.chwl.app.support.PreloadResourceViewModel;
|
||||
import com.chwl.app.ui.login.LoginPasswordActivity;
|
||||
import com.chwl.core.home.bean.MainTabInfo;
|
||||
import com.chwl.core.settings.SettingsModel;
|
||||
@@ -255,7 +257,6 @@ public class MainActivity extends BaseMvpActivity<IMainView, MainPresenter>
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void otherModelInit() {
|
||||
PwdCodeMgr.get().onCreateInit();
|
||||
//初始化线程池
|
||||
@@ -263,6 +264,14 @@ public class MainActivity extends BaseMvpActivity<IMainView, MainPresenter>
|
||||
IMBroadcastManager.get().onCreate();
|
||||
ImageLoadUtilsV2.init(context);
|
||||
SettingsModel.get().checkSysAccount();
|
||||
initPreloadResource();
|
||||
}
|
||||
|
||||
private void initPreloadResource(){
|
||||
PreloadResourceViewModel viewModel = new ViewModelProvider(
|
||||
GlobalViewModelOwner.Companion.getInstance()
|
||||
).get(PreloadResourceViewModel.class);
|
||||
viewModel.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -24,6 +24,7 @@ import com.chwl.library.language.LanguageHelper;
|
||||
import com.coorchice.library.utils.LogUtils;
|
||||
import com.example.lib_utils.ServiceTime;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import com.liulishuo.filedownloader.FileDownloader;
|
||||
import com.netease.nim.uikit.api.NimUIKit;
|
||||
import com.netease.nim.uikit.common.util.log.LogUtil;
|
||||
import com.netease.nimlib.sdk.NIMClient;
|
||||
@@ -457,6 +458,7 @@ public class App extends BaseApp {
|
||||
.build();
|
||||
Realm.setDefaultConfiguration(config);
|
||||
|
||||
FileDownloader.setup(BasicConfig.INSTANCE.getAppContext());
|
||||
LogUtil.i(TAG, channel);
|
||||
}
|
||||
|
||||
|
@@ -1,30 +1,35 @@
|
||||
package com.chwl.app.avroom.widget;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.chwl.core.helper.PathHelper;
|
||||
import com.chwl.library.download.DownloadException;
|
||||
import com.chwl.library.download.DownloadManager;
|
||||
import com.chwl.library.download.DownloadRequest;
|
||||
import com.chwl.library.download.DownloadTask;
|
||||
import com.chwl.library.download.FileDownloadListener;
|
||||
import com.example.lib_utils.log.LogUtil;
|
||||
import com.netease.nim.uikit.common.util.string.StringUtil;
|
||||
import com.opensource.svgaplayer.SVGACallback;
|
||||
import com.opensource.svgaplayer.SVGADrawable;
|
||||
import com.opensource.svgaplayer.SVGAImageView;
|
||||
import com.opensource.svgaplayer.SVGAParser;
|
||||
import com.opensource.svgaplayer.SVGAVideoEntity;
|
||||
import com.tencent.qgame.animplayer.AnimConfig;
|
||||
import com.tencent.qgame.animplayer.AnimView;
|
||||
import com.chwl.app.R;
|
||||
import com.chwl.app.common.widget.CircleImageView;
|
||||
import com.chwl.app.ui.utils.ImageLoadKt;
|
||||
import com.chwl.core.gift.GiftModel;
|
||||
import com.chwl.core.gift.bean.GiftEffectInfo;
|
||||
import com.chwl.core.gift.bean.GiftInfo;
|
||||
@@ -33,39 +38,33 @@ import com.chwl.core.initial.bean.InitInfo;
|
||||
import com.chwl.core.manager.AvRoomDataManager;
|
||||
import com.chwl.core.manager.IMNetEaseManager;
|
||||
import com.chwl.core.manager.RoomEvent;
|
||||
import com.chwl.library.utils.ResolutionUtils;
|
||||
import com.tencent.qgame.animplayer.inter.IAnimListener;
|
||||
import com.tencent.qgame.animplayer.util.ScaleType;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @author chenran
|
||||
* @date 2017/10/8
|
||||
*/
|
||||
|
||||
public class GiftEffectView extends RelativeLayout implements SVGACallback {
|
||||
private RelativeLayout container;
|
||||
public class GiftEffectView extends RelativeLayout {
|
||||
private SVGAImageView svgaImageView;
|
||||
private AnimView vapAnimView;
|
||||
private View svgaBg;
|
||||
private ImageView giftLightBg;
|
||||
private CircleImageView giftImg;
|
||||
private ImageView imgBg;
|
||||
private CircleImageView benefactorAvatar;
|
||||
private CircleImageView receiverAvatar;
|
||||
private TextView benefactorNick;
|
||||
private TextView receiverNick;
|
||||
private TextView giftNumber;
|
||||
private TextView giftName;
|
||||
private GiftEffectListener giftEffectListener;
|
||||
private EffectHandler effectHandler;
|
||||
private boolean isAnim;
|
||||
private boolean isPlayAnim;
|
||||
private boolean isHideCarEffect;
|
||||
|
||||
private String DOWNLOAD_TAG = "gift_effect_download";
|
||||
|
||||
private SVGAParser.ParseCompletion parseCompletion;
|
||||
|
||||
public GiftEffectView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
@@ -96,22 +95,85 @@ public class GiftEffectView extends RelativeLayout implements SVGACallback {
|
||||
private void init() {
|
||||
LayoutInflater.from(getContext()).inflate(R.layout.layout_gift_effect, this, true);
|
||||
effectHandler = new EffectHandler(this);
|
||||
container = findViewById(R.id.container);
|
||||
imgBg = findViewById(R.id.img_bg);
|
||||
giftLightBg = findViewById(R.id.gift_light_bg);
|
||||
giftImg = findViewById(R.id.gift_img);
|
||||
benefactorAvatar = findViewById(R.id.benefactor_avatar);
|
||||
receiverAvatar = findViewById(R.id.receiver_avatar);
|
||||
benefactorNick = findViewById(R.id.benefactor_nick);
|
||||
receiverNick = findViewById(R.id.receiver_nick);
|
||||
giftNumber = findViewById(R.id.gift_number);
|
||||
giftName = findViewById(R.id.gift_name);
|
||||
svgaImageView = findViewById(R.id.svga_imageview);
|
||||
svgaImageView.setCallback(this);
|
||||
svgaImageView.setClearsAfterStop(true);
|
||||
svgaImageView.setLoops(1);
|
||||
svgaBg = findViewById(R.id.svga_imageview_bg);
|
||||
vapAnimView = findViewById(R.id.vap_anim_view);
|
||||
parseCompletion = new SVGAParser.ParseCompletion() {
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
log("onError");
|
||||
effectHandler.sendEmptyMessage(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@NonNull SVGAVideoEntity svgaVideoEntity) {
|
||||
log("onComplete");
|
||||
SVGADrawable drawable = new SVGADrawable(svgaVideoEntity);
|
||||
svgaImageView.setImageDrawable(drawable);
|
||||
svgaImageView.startAnimation();
|
||||
svgaBg.setVisibility(VISIBLE);
|
||||
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(svgaBg, "alpha", 0.0F, 2.0F).setDuration(1250);
|
||||
objectAnimator1.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
objectAnimator1.start();
|
||||
}
|
||||
};
|
||||
svgaImageView.setCallback(new SVGACallback() {
|
||||
@Override
|
||||
public void onPause() {
|
||||
log("onPause");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinished() {
|
||||
log("onFinished");
|
||||
effectHandler.sendEmptyMessage(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRepeat() {
|
||||
log("onRepeat");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStep(int i, double v) {
|
||||
|
||||
}
|
||||
});
|
||||
vapAnimView.setScaleType(ScaleType.CENTER_CROP);
|
||||
vapAnimView.setAnimListener(new IAnimListener() {
|
||||
@Override
|
||||
public boolean onVideoConfigReady(@NonNull AnimConfig animConfig) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoStart() {
|
||||
log("onVideoStart");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoRender(int i, @androidx.annotation.Nullable AnimConfig animConfig) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoComplete() {
|
||||
log("onVideoComplete");
|
||||
effectHandler.sendEmptyMessage(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoDestroy() {
|
||||
log("onVideoDestroy");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed(int i, @androidx.annotation.Nullable String s) {
|
||||
log("onFailed i:" + i + " s:" + s);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void startGiftEffect(GiftEffectInfo giftEffectInfo) {
|
||||
@@ -121,107 +183,98 @@ public class GiftEffectView extends RelativeLayout implements SVGACallback {
|
||||
giftInfo = giftEffectInfo.getGift();
|
||||
}
|
||||
if (giftInfo != null) {
|
||||
isPlayAnim = false;
|
||||
container.setVisibility(INVISIBLE);
|
||||
effectHandler.sendEmptyMessageDelayed(0, 4000);
|
||||
if (giftInfo.getOtherViewType() == 1 && !TextUtils.isEmpty(giftInfo.getViewUrl())) {
|
||||
drawVAPEffect(giftInfo.getViewUrl());
|
||||
} else if (giftInfo.isHasVggPic() && !StringUtil.isEmpty(giftInfo.getVggUrl())) {
|
||||
try {
|
||||
drawSvgaEffect(giftInfo.getVggUrl());
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
InitInfo initInfo = InitialModel.get().getCacheInitInfo();
|
||||
if (!isHideCarEffect && initInfo != null && giftInfo.getGoldPrice() >= initInfo.getHideCarEffectGiftPrice()) {
|
||||
isHideCarEffect = true;
|
||||
IMNetEaseManager.get().noticeRoomEvent(null, RoomEvent.ROOM_CAR_EFFECT_HIDE);
|
||||
}
|
||||
if (!AvRoomDataManager.get().mIsNeedGiftEffect || AvRoomDataManager.get().isSelfGamePlaying()) {
|
||||
effectHandler.sendEmptyMessage(0);
|
||||
return;
|
||||
}
|
||||
if (giftInfo.getOtherViewType() == 1 && !TextUtils.isEmpty(giftInfo.getViewUrl())) {
|
||||
drawVAPEffect(giftInfo.getViewUrl());
|
||||
} else if (giftInfo.isHasVggPic() && !StringUtil.isEmpty(giftInfo.getVggUrl())) {
|
||||
drawSvgaEffect(giftInfo.getVggUrl());
|
||||
} else {
|
||||
effectHandler.sendEmptyMessage(0);
|
||||
}
|
||||
} else {
|
||||
effectHandler.sendEmptyMessage(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawSvgaEffect(String url) throws MalformedURLException {
|
||||
if (!AvRoomDataManager.get().mIsNeedGiftEffect ||
|
||||
AvRoomDataManager.get().isSelfGamePlaying()) {
|
||||
return;
|
||||
private void drawSvgaEffect(String url) {
|
||||
log("drawSvgaEffect url:" + url);
|
||||
String filePath = PathHelper.INSTANCE.generateResourcesFilePath(url);
|
||||
DownloadRequest request = DownloadRequest.Companion.build(url, filePath, DOWNLOAD_TAG, null, 60000L);
|
||||
DownloadManager.INSTANCE.download(request, new FileDownloadListener() {
|
||||
@Override
|
||||
public void onDownloadCompleted(@NonNull DownloadTask task) {
|
||||
String path = task.getRequest().getPath();
|
||||
log("drawSvgaEffect onDownloadCompleted url:" + url + " path:" + path);
|
||||
drawSvgaEffectFile(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadError(@NonNull DownloadException exception) {
|
||||
log("drawSvgaEffect onDownloadError url:" + url);
|
||||
effectHandler.sendEmptyMessage(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void drawSvgaEffectFile(String path) {
|
||||
try {
|
||||
log("drawSvgaEffectFile path:" + path);
|
||||
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(path));
|
||||
SVGAParser.Companion.shareParser().decodeFromInputStream(inputStream, path, parseCompletion, true, null, null);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
effectHandler.sendEmptyMessage(0);
|
||||
}
|
||||
SVGAParser.Companion.shareParser().decodeFromURL(new URL(url), new SVGAParser.ParseCompletion() {
|
||||
@Override
|
||||
public void onComplete(@Nullable SVGAVideoEntity videoItem) {
|
||||
SVGADrawable drawable = new SVGADrawable(videoItem);
|
||||
svgaImageView.setImageDrawable(drawable);
|
||||
svgaImageView.startAnimation();
|
||||
svgaBg.setVisibility(VISIBLE);
|
||||
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(svgaBg, "alpha", 0.0F, 2.0F).setDuration(1250);
|
||||
objectAnimator1.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
objectAnimator1.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
|
||||
}
|
||||
}, null);
|
||||
|
||||
}
|
||||
|
||||
private void drawVAPEffect(String url) {
|
||||
if (!AvRoomDataManager.get().mIsNeedGiftEffect || AvRoomDataManager.get().isSelfGamePlaying()) {
|
||||
return;
|
||||
}
|
||||
ImageLoadKt.loadAnim(vapAnimView, url);
|
||||
log("drawVAPEffect url:" + url);
|
||||
String filePath = PathHelper.INSTANCE.generateResourcesFilePath(url);
|
||||
DownloadRequest request = DownloadRequest.Companion.build(url, filePath, DOWNLOAD_TAG, null, 60000L);
|
||||
DownloadManager.INSTANCE.download(request, new FileDownloadListener() {
|
||||
@Override
|
||||
public void onDownloadCompleted(@NonNull DownloadTask task) {
|
||||
String path = task.getRequest().getPath();
|
||||
log("drawVAPEffect onDownloadCompleted url:" + url + " path:" + path);
|
||||
vapAnimView.startPlay(new File(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadError(@NonNull DownloadException exception) {
|
||||
log("drawVAPEffect onDownloadError url:" + url);
|
||||
exception.printStackTrace();
|
||||
effectHandler.sendEmptyMessageDelayed(0, 4000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void deleteAnim() {
|
||||
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(container, "translationX", container.getX(), ResolutionUtils.getScreenWidth(getContext())).setDuration(1250);
|
||||
objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
objectAnimator.start();
|
||||
|
||||
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(container, "alpha", 1.0F, 0.0F).setDuration(1250);
|
||||
objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
objectAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
if (isHideCarEffect) {
|
||||
isHideCarEffect = false;
|
||||
IMNetEaseManager.get().noticeRoomEvent(null, RoomEvent.ROOM_CAR_EFFECT_SHOW);
|
||||
}
|
||||
if (giftEffectListener != null) {
|
||||
isAnim = false;
|
||||
isPlayAnim = false;
|
||||
giftEffectListener.onGiftEffectEnd();
|
||||
}
|
||||
}
|
||||
});
|
||||
objectAnimator1.start();
|
||||
if (isHideCarEffect) {
|
||||
isHideCarEffect = false;
|
||||
IMNetEaseManager.get().noticeRoomEvent(null, RoomEvent.ROOM_CAR_EFFECT_SHOW);
|
||||
}
|
||||
svgaBg.setVisibility(GONE);
|
||||
isAnim = false;
|
||||
isPlayAnim = false;
|
||||
if (giftEffectListener != null) {
|
||||
giftEffectListener.onGiftEffectEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
log("release");
|
||||
DownloadManager.INSTANCE.stopTag(DOWNLOAD_TAG);
|
||||
effectHandler.removeMessages(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinished() {
|
||||
svgaBg.setVisibility(GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRepeat() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStep(int i, double v) {
|
||||
|
||||
}
|
||||
|
||||
public interface GiftEffectListener {
|
||||
void onGiftEffectEnd();
|
||||
}
|
||||
@@ -246,4 +299,8 @@ public class GiftEffectView extends RelativeLayout implements SVGACallback {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void log(String message) {
|
||||
LogUtil.INSTANCE.d("GiftEffectView", message, false);
|
||||
}
|
||||
}
|
||||
|
22
app/src/main/java/com/chwl/app/base/GlobalViewModelOwner.kt
Normal file
22
app/src/main/java/com/chwl/app/base/GlobalViewModelOwner.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.chwl.app.base
|
||||
|
||||
|
||||
import androidx.lifecycle.ViewModelStore
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import com.example.lib_utils.ICleared
|
||||
|
||||
class GlobalViewModelOwner : ViewModelStoreOwner, ICleared {
|
||||
|
||||
companion object {
|
||||
private val viewModelStore by lazy { ViewModelStore() }
|
||||
val instance by lazy { GlobalViewModelOwner() }
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
viewModelStore.clear()
|
||||
}
|
||||
|
||||
override val viewModelStore: ViewModelStore
|
||||
get() = GlobalViewModelOwner.viewModelStore
|
||||
}
|
@@ -0,0 +1,153 @@
|
||||
package com.chwl.app.support
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.chwl.app.base.BaseViewModel
|
||||
import com.chwl.core.helper.PathHelper
|
||||
import com.chwl.core.home.model.HomeModel
|
||||
import com.chwl.library.common.util.SPUtils
|
||||
import com.chwl.library.download.DownloadException
|
||||
import com.chwl.library.download.DownloadListener
|
||||
import com.chwl.library.download.DownloadManager
|
||||
import com.chwl.library.download.DownloadRequest
|
||||
import com.chwl.library.download.DownloadTask
|
||||
import com.chwl.library.utils.NetworkUtils
|
||||
import com.example.lib_utils.AppUtils
|
||||
import com.example.lib_utils.FileUtils2
|
||||
import com.example.lib_utils.log.ILog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PreloadResourceViewModel : BaseViewModel(), DownloadListener, ILog {
|
||||
|
||||
private val DOWNLOAD_TAG = "RESOURCE_DOWNLOAD_TAG"
|
||||
|
||||
private var preloadResourceList: MutableList<String>? = null
|
||||
|
||||
private var isStarted = false
|
||||
|
||||
fun start() {
|
||||
if (isStarted) {
|
||||
return
|
||||
}
|
||||
getPreloadResourceList {
|
||||
isStarted = true
|
||||
nextTask(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPreloadResourceList(block: suspend CoroutineScope.() -> Unit) {
|
||||
logI("getPreloadResourceList()")
|
||||
safeLaunch(false, onError = {
|
||||
logI("getPreloadResourceList() onError:${it.message}")
|
||||
it.printStackTrace()
|
||||
}) {
|
||||
val set = getDownloadRecord()
|
||||
logI("getPreloadResourceList() set:${set.size}")
|
||||
preloadResourceList = HomeModel.getEffectResourceList()?.filter {
|
||||
if (it.isEmpty()) {
|
||||
return@filter false
|
||||
}
|
||||
if (set.contains(it)) {
|
||||
val path = PathHelper.generateResourcesFilePath(it)
|
||||
if (FileUtils2.isFileExists(path)) {
|
||||
return@filter false
|
||||
}
|
||||
}
|
||||
return@filter true
|
||||
}?.toMutableList()
|
||||
logI("getPreloadResourceList() it:${preloadResourceList?.size}")
|
||||
block.invoke(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun nextTask(lastTaskUrl: String? = null) {
|
||||
logI("nextTask() lastTaskUrl:${lastTaskUrl} preloadResourceList:${preloadResourceList?.size}")
|
||||
if (lastTaskUrl != null) {
|
||||
preloadResourceList?.removeAll {
|
||||
it == lastTaskUrl
|
||||
}
|
||||
}
|
||||
if (!isWifiNet()) {
|
||||
logI("nextTask() isWifiNet==false")
|
||||
delayLoopTask()
|
||||
return
|
||||
}
|
||||
logI("nextTask() preloadResourceList:${preloadResourceList?.size}")
|
||||
preloadResourceList?.firstOrNull()?.let {
|
||||
downloadTask(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadTask(url: String) {
|
||||
val path = PathHelper.generateResourcesFilePath(url)
|
||||
logI("downloadTask() url:${url} path:${path}")
|
||||
val request = DownloadRequest.build(
|
||||
url = url,
|
||||
path = path,
|
||||
tag = DOWNLOAD_TAG,
|
||||
header = null,
|
||||
timeout = null
|
||||
)
|
||||
DownloadManager.download(request, this)
|
||||
}
|
||||
|
||||
override fun onDownloadError(exception: DownloadException) {
|
||||
super.onDownloadError(exception)
|
||||
val url = exception.task()?.getRequest()?.getUrl()
|
||||
logI(
|
||||
"onDownloadError() url:${url} message:${exception.message}"
|
||||
)
|
||||
exception.printStackTrace()
|
||||
nextTask(url)
|
||||
}
|
||||
|
||||
override fun onDownloadCompleted(task: DownloadTask) {
|
||||
super.onDownloadCompleted(task)
|
||||
val url = task.getRequest().getUrl()
|
||||
logI(
|
||||
"onDownloadCompleted() url:${url} path:${
|
||||
task.getRequest().getPath()
|
||||
}"
|
||||
)
|
||||
makeDownloadRecord(url)
|
||||
nextTask(url)
|
||||
}
|
||||
|
||||
private fun isWifiNet(): Boolean {
|
||||
return getNetType() == NetworkUtils.NET_WIFI
|
||||
}
|
||||
|
||||
private fun isNetAvailable(): Boolean {
|
||||
return NetworkUtils.isNetworkStrictlyAvailable(AppUtils.getApp())
|
||||
}
|
||||
|
||||
private fun getNetType(): Int {
|
||||
return NetworkUtils.getNetworkType(AppUtils.getApp())
|
||||
}
|
||||
|
||||
private fun delayLoopTask() {
|
||||
logI(
|
||||
"delayLoopTask()"
|
||||
)
|
||||
viewModelScope.launch {
|
||||
delay(10000)
|
||||
nextTask(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDownloadRecord(): MutableSet<String> {
|
||||
return SPUtils.getStringSet("RESOURCE_DOWNLOAD_COMPLETE", setOf()).toMutableSet()
|
||||
}
|
||||
|
||||
private fun makeDownloadRecord(url: String) {
|
||||
val set = getDownloadRecord()
|
||||
set.add(url)
|
||||
SPUtils.putStringSet("RESOURCE_DOWNLOAD_COMPLETE", set)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
DownloadManager.stopTag(DOWNLOAD_TAG)
|
||||
}
|
||||
}
|
@@ -5,143 +5,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="375dp"
|
||||
android:layout_height="164dp"
|
||||
android:layout_marginTop="50dp"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_bg"
|
||||
android:layout_width="376dp"
|
||||
android:layout_height="164dp"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/icon_gift_effect_bg_1" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="123dp"
|
||||
android:src="@drawable/icon_light_bg"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/benefactor_container"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="50dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.chwl.app.common.widget.CircleImageView
|
||||
android:id="@+id/benefactor_avatar"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/benefactor_nick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="2dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/sp_10" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/give_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="17dp"
|
||||
android:layout_toEndOf="@id/benefactor_container"
|
||||
android:text="@string/layout_layout_gift_effect_01"
|
||||
android:textColor="#FEFEFE"
|
||||
android:textSize="@dimen/sp_12" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/receiver_container"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="17dp"
|
||||
android:layout_toEndOf="@id/give_text"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.chwl.app.common.widget.CircleImageView
|
||||
android:id="@+id/receiver_avatar"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/receiver_nick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="2dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/sp_10" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/gift_light_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toEndOf="@id/receiver_container">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/gift_light_bg"
|
||||
android:layout_width="97.5dp"
|
||||
android:layout_height="97.5dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/icon_light_rotate" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerInParent="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.chwl.app.common.widget.CircleImageView
|
||||
android:id="@+id/gift_img"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/gift_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="2dp"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/sp_10" />
|
||||
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/gift_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:textColor="#FFF600"
|
||||
android:textSize="26sp"
|
||||
android:textStyle="bold" />
|
||||
</RelativeLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/svga_imageview_bg"
|
||||
android:layout_width="match_parent"
|
||||
|
147
core/src/main/java/com/chwl/core/helper/PathHelper.kt
Normal file
147
core/src/main/java/com/chwl/core/helper/PathHelper.kt
Normal file
@@ -0,0 +1,147 @@
|
||||
package com.chwl.core.helper
|
||||
|
||||
|
||||
import com.chwl.library.utils.codec.MD5Utils
|
||||
import com.example.lib_utils.AppUtils
|
||||
import com.example.lib_utils.PathUtils
|
||||
|
||||
/**
|
||||
* Created by Max on 2022/3/14 18:14
|
||||
* Desc:路径助手
|
||||
*/
|
||||
object PathHelper {
|
||||
|
||||
/**
|
||||
* 获取应用外部Cache目录路径(优先外置存储,其次内置存储)
|
||||
*/
|
||||
fun getExternalCachePath(): String {
|
||||
val path = PathUtils.getExternalAppCachePath(AppUtils.getApp())
|
||||
if (!path.isNullOrEmpty()) {
|
||||
// /storage/emulated/0/Android/data/package/cache
|
||||
return path
|
||||
}
|
||||
// /data/data/package/cache
|
||||
return PathUtils.getInternalAppCachePath(AppUtils.getApp())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用外部Files目录路径(优先外置存储,其次内置存储)
|
||||
*/
|
||||
fun getExternalFilesPath(): String {
|
||||
val path = PathUtils.getExternalAppFilesPath(AppUtils.getApp())
|
||||
if (!path.isNullOrEmpty()) {
|
||||
// /storage/emulated/0/Android/data/package/files
|
||||
return path
|
||||
}
|
||||
// /data/data/package/files
|
||||
return PathUtils.getInternalAppFilesPath(AppUtils.getApp())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用内置Cache目录路径
|
||||
*/
|
||||
fun getInternalCachePath(): String {
|
||||
// /data/data/package/cache
|
||||
return PathUtils.getInternalAppCachePath(AppUtils.getApp())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用内置Files目录路径
|
||||
*/
|
||||
fun getInternalFilesPath(): String {
|
||||
// /data/data/package/files
|
||||
return PathUtils.getInternalAppFilesPath(AppUtils.getApp())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用外部临时Cache目录路径
|
||||
*/
|
||||
fun getExternalTempCachePath(): String {
|
||||
// /storage/emulated/0/Android/data/package/cache/temp
|
||||
// or
|
||||
// /data/data/package/cache/temp
|
||||
return PathUtils.plusPathNotNull(getExternalCachePath(), "temp")
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用内置临时Cache目录路径
|
||||
*/
|
||||
fun getInternalTempCachePath(): String {
|
||||
// /data/data/package/cache/temp
|
||||
return PathUtils.plusPathNotNull(getInternalCachePath(), "temp")
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用外部下载目录路径
|
||||
*/
|
||||
fun getExternalDownloadPath(): String {
|
||||
val path = PathUtils.getExternalAppDownloadPath(AppUtils.getApp())
|
||||
if (!path.isNullOrEmpty()) {
|
||||
// /storage/emulated/0/Android/data/package/files/Download
|
||||
return path
|
||||
}
|
||||
// /data/data/package/files/download
|
||||
return PathUtils.plusPathNotNull(getInternalFilesPath(), "download")
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用外部下载缓存目录路径
|
||||
*/
|
||||
fun getExternalDownloadCachePath(): String {
|
||||
// /storage/emulated/0/Android/data/package/cache/download
|
||||
return PathUtils.plusPathNotNull(getExternalCachePath(), "download")
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用内部下载目录路径
|
||||
*/
|
||||
fun getInternalDownloadPath(): String {
|
||||
// /data/data/package/files/download
|
||||
return PathUtils.plusPathNotNull(getInternalFilesPath(), "download")
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成外部下载文件缓存文件路径
|
||||
* @param url 资源地址
|
||||
*/
|
||||
fun generateExternalDownloadCacheFilePath(url: String): String {
|
||||
// /storage/emulated/0/Android/data/package/cache/download/**.**
|
||||
return PathUtils.plusPathNotNull(getExternalDownloadCachePath(), generateNameByUrl(url))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下载文件路径(文件路径)
|
||||
* @param url 资源地址
|
||||
*/
|
||||
fun generateExternalDownloadFilePath(url: String): String {
|
||||
// /storage/emulated/0/Android/data/package/files/Download/**.**
|
||||
return PathUtils.plusPathNotNull(getExternalDownloadPath(), generateNameByUrl(url))
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成外部临时缓存文件路径
|
||||
* @param url 资源地址
|
||||
*/
|
||||
fun generateExternalTempCacheFilePath(url: String): String {
|
||||
// /storage/emulated/0/Android/data/package/cache/temp/**.**
|
||||
return PathUtils.plusPathNotNull(getExternalTempCachePath(), generateNameByUrl(url))
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成礼物文件存储文件路径 PS:项目中所有的MP4、SVGA都在此维护
|
||||
* @param url 礼物地址
|
||||
*/
|
||||
fun generateResourcesFilePath(url: String): String {
|
||||
val dir = PathUtils.plusPathNotNull(getInternalFilesPath(), "resources6")
|
||||
return PathUtils.plusPathNotNull(dir, generateNameByUrl(url))
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Url生成名称
|
||||
* @param url url
|
||||
*/
|
||||
fun generateNameByUrl(url: String): String {
|
||||
// md5路径+后缀
|
||||
return MD5Utils.getMD5String(url) + (PathUtils.getSuffixType(url) ?: "")
|
||||
}
|
||||
}
|
@@ -210,6 +210,11 @@ object HomeModel : BaseModel() {
|
||||
api.getGiveDetail(uid, type, page, pageSize)
|
||||
}
|
||||
|
||||
suspend fun getEffectResourceList(): MutableList<String>? =
|
||||
launchRequest {
|
||||
api.getEffectResourceList()
|
||||
}
|
||||
|
||||
private interface Api {
|
||||
|
||||
/**
|
||||
@@ -461,6 +466,14 @@ object HomeModel : BaseModel() {
|
||||
*/
|
||||
@GET("/home/pickResource")
|
||||
suspend fun getResourceJumpInfo(@Query("id") id: Int): ServiceResult<HomeRoomInfo>
|
||||
|
||||
/**
|
||||
* 资源列表(预加载)
|
||||
*
|
||||
* @return -
|
||||
*/
|
||||
@GET("resource/effect")
|
||||
suspend fun getEffectResourceList(): ServiceResult<MutableList<String>>
|
||||
}
|
||||
|
||||
}
|
@@ -119,6 +119,7 @@ dependencies {
|
||||
api project(':libs:lib_encipher')
|
||||
|
||||
api 'com.qcloud.cos:cos-android:5.9.25'
|
||||
api 'com.liulishuo.filedownloader:library:1.7.7'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@@ -0,0 +1,26 @@
|
||||
package com.chwl.library.download
|
||||
|
||||
|
||||
class DownloadException : Exception {
|
||||
|
||||
private var task: DownloadTask? = null
|
||||
|
||||
private var request: DownloadRequest? = null
|
||||
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
constructor(task: DownloadTask, throwable: Throwable) : super(
|
||||
throwable
|
||||
) {
|
||||
this.task = task
|
||||
this.request = task.getRequest()
|
||||
}
|
||||
|
||||
constructor(request: DownloadRequest, throwable: Throwable) : super(
|
||||
throwable
|
||||
) {
|
||||
this.request = request
|
||||
}
|
||||
|
||||
fun task() = task
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package com.chwl.library.download
|
||||
|
||||
|
||||
interface DownloadListener {
|
||||
|
||||
/**
|
||||
* 下载完成
|
||||
*/
|
||||
fun onDownloadCompleted(task: DownloadTask) {}
|
||||
|
||||
/**
|
||||
* 下载进度
|
||||
* @param soFarBytes 已下载
|
||||
* @param totalBytes 总
|
||||
*/
|
||||
fun onDownloadProgress(task: DownloadTask, soFarBytes: Int, totalBytes: Int) {}
|
||||
|
||||
/**
|
||||
* 下载异常
|
||||
*/
|
||||
fun onDownloadError(exception: DownloadException){}
|
||||
}
|
@@ -0,0 +1,231 @@
|
||||
package com.chwl.library.download
|
||||
|
||||
import com.example.lib_utils.log.ILog
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
|
||||
object DownloadManager : ILog {
|
||||
|
||||
/**
|
||||
* 任务列表 <URL,任务>
|
||||
*/
|
||||
private val tasks: ConcurrentHashMap<String, DownloadTask> by lazy {
|
||||
ConcurrentHashMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签列表 <TAG,URL>
|
||||
*/
|
||||
private val tags: ConcurrentHashMap<String, HashSet<String>> by lazy {
|
||||
ConcurrentHashMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止所有任务
|
||||
*/
|
||||
fun stopAll() {
|
||||
logD("stopAll()")
|
||||
tasks.keys.map {
|
||||
it
|
||||
}.forEach {
|
||||
stop(it)
|
||||
}
|
||||
tasks.clear()
|
||||
tags.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止某一个URL下载任务(有多组下载该URL,都会被停止)
|
||||
*/
|
||||
fun stop(url: String) {
|
||||
logD("stop() url:${url}")
|
||||
getTask(url)?.stop()
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止某一组内的某个URL下载任务(其他组正在下载该URL的任务不受影响)
|
||||
*/
|
||||
fun stop(url: String, tag: String) {
|
||||
logD("stop() url:$url ,tag:$tag")
|
||||
getTask(url)?.removeListener(tag)
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止某一标签的所有任务
|
||||
*/
|
||||
fun stopTag(tag: String) {
|
||||
logD("stopTag()")
|
||||
if (tags.containsKey(tag)) {
|
||||
tags.remove(tag)?.let {
|
||||
it.forEach { url ->
|
||||
getTask(url)?.removeListener(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止某个下载请求
|
||||
*/
|
||||
fun stop(request: DownloadRequest) {
|
||||
stop(request.getUrl(), request.getTag())
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件 (简单下载,也可通过自己构造DownloadRequest下载)
|
||||
* @param url 下载地址
|
||||
* @param tag 标签
|
||||
* @param listener 监听器
|
||||
*/
|
||||
fun download(url: String, path: String, listener: DownloadListener, tag: String? = null) {
|
||||
val request = DownloadRequest.build(url, path, tag, null)
|
||||
download(request, listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param request 下载请求体
|
||||
* @param listener 监听器
|
||||
*/
|
||||
fun download(request: DownloadRequest, listener: DownloadListener) {
|
||||
download(request, request.getTag(), listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param request 下载请求体
|
||||
* @param listenerTag 监听器标签(有重复任务时,内部会复用任务追加监听器,监听器以标签区分。)
|
||||
* @param listener 监听器
|
||||
*/
|
||||
fun download(request: DownloadRequest, listenerTag: String, listener: DownloadListener) {
|
||||
logD("download() url:${request.getUrl()} ,path:${request.getPath()} ,listenerTag:$listenerTag")
|
||||
// 记录标签与地址关系
|
||||
addTag(request.getTag(), request.getUrl())
|
||||
synchronized(this) {
|
||||
var task = getTask(request)
|
||||
// 判断是否有相同目标的任务
|
||||
if (task != null) {
|
||||
logD("download() 有重复任务->关联复用")
|
||||
task.addListener(listenerTag, listener)
|
||||
task.start()
|
||||
} else {
|
||||
logD("download() 创建新任务")
|
||||
task = request.createTask().apply {
|
||||
addListener(listenerTag, listener)
|
||||
}
|
||||
addTask(task)
|
||||
task.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加监听
|
||||
* @param url 下载地址
|
||||
* @param listener 监听器
|
||||
*/
|
||||
fun addListener(url: String, listener: DownloadListener) {
|
||||
getTask(url)?.addListener(listener = listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除监听
|
||||
* @param url 下载地址
|
||||
* @param listener 监听器
|
||||
*/
|
||||
fun removeListener(url: String, listener: DownloadListener) {
|
||||
getTask(url)?.removeListener(listener = listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务(具有相同目标的任务)
|
||||
* @param request
|
||||
*/
|
||||
fun getTask(request: DownloadRequest): DownloadTask? {
|
||||
tasks.values.forEach {
|
||||
if (it.getRequest().equalsTarget(request)) {
|
||||
return it
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务
|
||||
*/
|
||||
private fun addTask(task: DownloadTask) {
|
||||
logD("addTask()")
|
||||
tasks[task.getRequest().getUrl()] = task
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除任务
|
||||
*/
|
||||
fun removeTask(task: DownloadTask) {
|
||||
removeTask(task.getRequest().getUrl())
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除任务
|
||||
*/
|
||||
@Synchronized
|
||||
private fun removeTask(url: String) {
|
||||
logD("removeTask() url:$url")
|
||||
tasks.remove(url)?.apply {
|
||||
// 移除该URL与标签关系
|
||||
tags.iterator().let {
|
||||
while (it.hasNext()) {
|
||||
it.next().let { entry ->
|
||||
entry.value.remove(url)
|
||||
if (entry.value.isEmpty()) {
|
||||
it.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务-根据下载地址
|
||||
* @param url 下载地址
|
||||
*/
|
||||
fun getTask(url: String): DownloadTask? {
|
||||
if (hasTask(url)) {
|
||||
return tasks[url]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在相同任务
|
||||
* @param url 下载地址
|
||||
*/
|
||||
private fun hasTask(url: String): Boolean {
|
||||
return tasks.containsKey(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在相同任务
|
||||
*/
|
||||
private fun hasTask(request: DownloadRequest): Boolean {
|
||||
return getTask(request) != null
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加标签记录(建立关系)
|
||||
*/
|
||||
private fun addTag(tag: String, url: String) {
|
||||
logD("addTag() tag:$tag ,url:$url")
|
||||
if (tag.isEmpty() || url.isEmpty()) {
|
||||
return
|
||||
}
|
||||
synchronized(this) {
|
||||
tags[tag]?.add(url) ?: let {
|
||||
tags[tag] = HashSet<String>().apply {
|
||||
add(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
package com.chwl.library.download
|
||||
|
||||
interface DownloadRequest {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* 构建请求
|
||||
* @param url 下载地址
|
||||
* @param path 存储路径(完整)
|
||||
* @param tag 标记
|
||||
* @param header 请求头
|
||||
* @param timeout 下载超时时间
|
||||
*/
|
||||
fun build(
|
||||
url: String,
|
||||
path: String,
|
||||
tag: String? = null,
|
||||
header: Map<String, String>? = null,
|
||||
timeout: Long? = null,
|
||||
): DownloadRequest {
|
||||
return FileDownloadRequest(url, path, tag, header, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载地址
|
||||
*/
|
||||
fun getUrl(): String
|
||||
|
||||
/**
|
||||
* 本地文件路径
|
||||
*/
|
||||
fun getPath(): String
|
||||
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
fun getTag(): String
|
||||
|
||||
/**
|
||||
* 请求头
|
||||
*/
|
||||
fun getHeader(): Map<String, String>?
|
||||
|
||||
/**
|
||||
* 获取下载任务
|
||||
*/
|
||||
fun createTask(): DownloadTask
|
||||
|
||||
/**
|
||||
* 获取下载超时时间(毫秒)
|
||||
*/
|
||||
fun getTimeout(): Long?
|
||||
|
||||
/**
|
||||
* 是否是相同的目标
|
||||
*/
|
||||
fun equalsTarget(request: DownloadRequest): Boolean
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package com.chwl.library.download
|
||||
|
||||
|
||||
|
||||
object DownloadState {
|
||||
|
||||
/**
|
||||
* 空闲
|
||||
*/
|
||||
const val IDLE: Int = -1
|
||||
|
||||
/**
|
||||
* 错误
|
||||
*/
|
||||
const val ERROR: Int = 0
|
||||
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
const val SUCCESS: Int = 1
|
||||
|
||||
/**
|
||||
* 已启动
|
||||
*/
|
||||
const val STARTED: Int = 2
|
||||
|
||||
/**
|
||||
* 进行中
|
||||
*/
|
||||
const val PROGRESS: Int = 3
|
||||
|
||||
/**
|
||||
* 暂停
|
||||
*/
|
||||
const val PAUSED: Int = 4
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package com.chwl.library.download
|
||||
|
||||
import com.example.lib_utils.ICleared
|
||||
|
||||
|
||||
interface DownloadTask : ICleared {
|
||||
|
||||
/**
|
||||
* 请求体
|
||||
*/
|
||||
fun getRequest(): DownloadRequest
|
||||
|
||||
/**
|
||||
* 获取当前状态
|
||||
*/
|
||||
fun getState(): Int
|
||||
|
||||
/**
|
||||
* 任务ID(唯一标识)
|
||||
*/
|
||||
fun getId(): String
|
||||
|
||||
/**
|
||||
* 文件路径(只有成功时才能拿到有效的文件路径)
|
||||
*/
|
||||
fun getFilePath(): String?
|
||||
|
||||
/**
|
||||
* 开始/恢复
|
||||
*/
|
||||
fun start()
|
||||
|
||||
/**
|
||||
* 停止/暂停
|
||||
*/
|
||||
fun stop()
|
||||
|
||||
/**
|
||||
* 是否暂停状态
|
||||
*/
|
||||
fun isPause(): Boolean {
|
||||
return getState() == DownloadState.PAUSED
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加回调监听
|
||||
* @param tag 标签 (重复标签时,监听器会发生覆盖操作)
|
||||
* @param listener 监听器
|
||||
* @return 是否添加成功
|
||||
*/
|
||||
fun addListener(tag: String? = null, listener: DownloadListener)
|
||||
|
||||
/**
|
||||
* 移除回调监听
|
||||
* @param tag 标签
|
||||
*/
|
||||
fun removeListener(tag: String)
|
||||
|
||||
/**
|
||||
* 移除回调监听
|
||||
* @param listener 监听器
|
||||
*/
|
||||
fun removeListener(listener: DownloadListener)
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
package com.chwl.library.download
|
||||
|
||||
|
||||
open class FileDownloadListener : DownloadListener
|
@@ -0,0 +1,47 @@
|
||||
package com.chwl.library.download
|
||||
|
||||
|
||||
|
||||
|
||||
open class FileDownloadRequest(
|
||||
// 下载地址
|
||||
private val url: String,
|
||||
// 保存路径(完整)
|
||||
private val path: String,
|
||||
// 别名
|
||||
private val tag: String? = null,
|
||||
// 请求头
|
||||
private val header: Map<String, String>? = null,
|
||||
// 下载超时时间
|
||||
private val timeout: Long? = null,
|
||||
) : DownloadRequest {
|
||||
|
||||
override fun getUrl(): String {
|
||||
return url
|
||||
}
|
||||
|
||||
override fun getPath(): String {
|
||||
return path
|
||||
}
|
||||
|
||||
override fun getTag(): String {
|
||||
return tag ?: "DEFAULT_TAG"
|
||||
}
|
||||
|
||||
override fun getHeader(): Map<String, String>? {
|
||||
return header
|
||||
}
|
||||
|
||||
override fun createTask(): DownloadTask {
|
||||
return FileDownloadTask(this)
|
||||
}
|
||||
|
||||
override fun getTimeout(): Long? {
|
||||
return timeout
|
||||
}
|
||||
|
||||
override fun equalsTarget(request: DownloadRequest): Boolean {
|
||||
// 暂时只判断目标地址是否一致
|
||||
return request.getUrl() == getUrl()
|
||||
}
|
||||
}
|
@@ -0,0 +1,276 @@
|
||||
package com.chwl.library.download
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.example.lib_utils.FileUtils2
|
||||
import com.example.lib_utils.log.ILog
|
||||
import com.liulishuo.filedownloader.BaseDownloadTask
|
||||
import com.liulishuo.filedownloader.FileDownloadListener
|
||||
import com.liulishuo.filedownloader.FileDownloader
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
|
||||
|
||||
open class FileDownloadTask(
|
||||
private val request: DownloadRequest,
|
||||
) : DownloadTask, ILog {
|
||||
|
||||
// 真正执行下载工作的任务(第三方组件SDK)
|
||||
private var task: BaseDownloadTask? = null
|
||||
|
||||
// 工作的任务监听器
|
||||
private var taskListener: ListenerBridge? = null
|
||||
|
||||
// 监听器列表 <标签,监听器>
|
||||
private val listeners: ConcurrentHashMap<String, DownloadListener> by lazy {
|
||||
ConcurrentHashMap()
|
||||
}
|
||||
|
||||
// 任务ID
|
||||
private val taskId: String by lazy {
|
||||
getRequest().getUrl() + getRequest().getPath()
|
||||
}
|
||||
|
||||
// 状态
|
||||
@Volatile
|
||||
private var state: Int = DownloadState.IDLE
|
||||
|
||||
private var handler: Handler? = null
|
||||
|
||||
// 是否释放了(临时记录,只是为了协助排除问题)
|
||||
private var isCleared: Boolean? = null
|
||||
|
||||
override fun getRequest(): DownloadRequest {
|
||||
return request
|
||||
}
|
||||
|
||||
override fun getState(): Int {
|
||||
return state
|
||||
}
|
||||
|
||||
override fun getId(): String {
|
||||
return taskId
|
||||
}
|
||||
|
||||
override fun getFilePath(): String {
|
||||
return getWorkTask().targetFilePath
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
logI(
|
||||
"start()" +
|
||||
"\nurl:${getRequest().getUrl()}" +
|
||||
"\npath:${getRequest().getPath()}" +
|
||||
"\ntimeout:${getRequest().getTimeout()}" +
|
||||
"\nstate:${getState()}" +
|
||||
"\nworkTask-status:${getWorkTask().status}", filePrinter = true
|
||||
)
|
||||
if (FileUtils2.isFileExists(getRequest().getPath())) {
|
||||
logD("该目标文件已存在本地")
|
||||
downloadSuccess()
|
||||
return
|
||||
}
|
||||
if (isCleared == true) {
|
||||
logE("当前任务已释放 url:${request.getUrl()}", filePrinter = true)
|
||||
return
|
||||
}
|
||||
if (getState() == DownloadState.IDLE || getState() == DownloadState.ERROR) {
|
||||
state = DownloadState.STARTED
|
||||
startTimeout()
|
||||
|
||||
// -------start
|
||||
// TODO 线上部分异常:调用start时,内部抛正在进行的异常(This task is dirty to restart, If you want to reuse this task, please invoke #reuse method manually and retry to restart again)
|
||||
// TODO 临时预先判断记录下
|
||||
if (getWorkTask().isUsing) {
|
||||
logE("下载正在进行,无需重复启动,url:${request.getUrl()}", filePrinter = true)
|
||||
return
|
||||
}
|
||||
// -------end
|
||||
|
||||
try {
|
||||
getWorkTask().start()
|
||||
} catch (e: Exception) {
|
||||
downloadError(e)
|
||||
}
|
||||
} else {
|
||||
logI("start() 已经启动")
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
logI("stop()", filePrinter = true)
|
||||
if (getState() != DownloadState.PAUSED && getState() != DownloadState.IDLE) {
|
||||
getWorkTask().pause()
|
||||
}
|
||||
onCleared()
|
||||
}
|
||||
|
||||
override fun addListener(tag: String?, listener: DownloadListener) {
|
||||
listeners[tag ?: getRequest().getTag()] = listener
|
||||
}
|
||||
|
||||
override fun removeListener(tag: String) {
|
||||
listeners.remove(tag)
|
||||
if (listeners.isEmpty()) {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeListener(listener: DownloadListener) {
|
||||
listeners.forEach {
|
||||
if (it == listener) {
|
||||
listeners.remove(it.key)
|
||||
if (listeners.isEmpty()) {
|
||||
stop()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* (真正)工作的任务
|
||||
*/
|
||||
protected open fun getWorkTask(): BaseDownloadTask {
|
||||
if (task == null) {
|
||||
task = createTask()
|
||||
}
|
||||
return task!!
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建任务
|
||||
*/
|
||||
protected open fun createTask(): BaseDownloadTask {
|
||||
taskListener = ListenerBridge()
|
||||
return FileDownloader.getImpl().create(getRequest().getUrl()).apply {
|
||||
setPath(getRequest().getPath(), false)
|
||||
this.listener = taskListener
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放工作任务相关资源
|
||||
*/
|
||||
private fun releaseWorkTask() {
|
||||
task?.listener = null
|
||||
taskListener = null
|
||||
task = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放
|
||||
*/
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
logD("onCleared() url:${getRequest().getUrl()}", filePrinter = true)
|
||||
this.isCleared = true
|
||||
// 从任务管理中移除自己
|
||||
DownloadManager.removeTask(this)
|
||||
handler?.removeCallbacksAndMessages(null)
|
||||
handler = null
|
||||
// 释放工作任务
|
||||
releaseWorkTask()
|
||||
// 移除所有监听
|
||||
listeners.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载成功处理
|
||||
*/
|
||||
private fun downloadSuccess() {
|
||||
logI("downloadSuccess() url:${getRequest().getUrl()}", filePrinter = true)
|
||||
state = DownloadState.SUCCESS
|
||||
listeners.values.forEach {
|
||||
it.onDownloadCompleted(this)
|
||||
}
|
||||
onCleared()
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载失败
|
||||
*/
|
||||
private fun downloadError(error: Throwable) {
|
||||
logE("downloadError() url:${getRequest().getUrl()}", error, filePrinter = true)
|
||||
state = DownloadState.ERROR
|
||||
listeners.values.forEach {
|
||||
it.onDownloadError(DownloadException(this@FileDownloadTask, error))
|
||||
}
|
||||
onCleared()
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始超时检测
|
||||
*/
|
||||
private fun startTimeout() {
|
||||
val timeout = request.getTimeout()
|
||||
if (timeout == null || timeout < 500) {
|
||||
// 时间太小就过滤掉
|
||||
return
|
||||
}
|
||||
logD("startTimeout() timeout:$timeout")
|
||||
if (handler == null) {
|
||||
handler = Handler(Looper.getMainLooper())
|
||||
} else {
|
||||
handler?.removeCallbacksAndMessages(null)
|
||||
}
|
||||
handler?.postDelayed({
|
||||
logD("startTimeout() postDelayed state:$state")
|
||||
if (state != DownloadState.IDLE && state != DownloadState.SUCCESS && state != DownloadState.ERROR) {
|
||||
logI(
|
||||
"startTimeout() postDelayed state:$state url:${getRequest().getUrl()}",
|
||||
filePrinter = true
|
||||
)
|
||||
if (getState() != DownloadState.PAUSED) {
|
||||
getWorkTask().pause()
|
||||
}
|
||||
downloadError(TimeoutException())
|
||||
}
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听器转发处理
|
||||
*/
|
||||
private inner class ListenerBridge : FileDownloadListener() {
|
||||
override fun pending(task: BaseDownloadTask, soFarBytes: Int, totalBytes: Int) {
|
||||
state = DownloadState.STARTED
|
||||
logI(
|
||||
"pending() url:${getRequest().getUrl()} soFarBytes:$soFarBytes,totalBytes:$totalBytes",
|
||||
filePrinter = true
|
||||
)
|
||||
}
|
||||
|
||||
override fun progress(task: BaseDownloadTask, soFarBytes: Int, totalBytes: Int) {
|
||||
state = DownloadState.PROGRESS
|
||||
// logD("progress() soFarBytes:$soFarBytes,totalBytes:$totalBytes")
|
||||
listeners.values.forEach {
|
||||
it.onDownloadProgress(this@FileDownloadTask, soFarBytes, totalBytes)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成时
|
||||
*/
|
||||
override fun completed(task: BaseDownloadTask) {
|
||||
downloadSuccess()
|
||||
}
|
||||
|
||||
override fun paused(task: BaseDownloadTask, soFarBytes: Int, totalBytes: Int) {
|
||||
logI("paused() url:${getRequest().getUrl()}", filePrinter = true)
|
||||
state = DownloadState.PAUSED
|
||||
}
|
||||
|
||||
override fun error(task: BaseDownloadTask, e: Throwable) {
|
||||
downloadError(e)
|
||||
}
|
||||
|
||||
/**
|
||||
* 在下载队列中(正在等待/正在下载)已经存在相同下载连接与相同存储路径的任务
|
||||
*/
|
||||
override fun warn(task: BaseDownloadTask) {
|
||||
logI("warn() 已经存在相同下载连接与相同存储路径的任务 url:${getRequest().getUrl()}", filePrinter = true)
|
||||
}
|
||||
}
|
||||
}
|
1294
libs/lib_utils/src/main/java/com/example/lib_utils/FileUtils2.java
Normal file
1294
libs/lib_utils/src/main/java/com/example/lib_utils/FileUtils2.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,495 @@
|
||||
package com.example.lib_utils
|
||||
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* getRootPath : 获取根路径
|
||||
* getDataPath : 获取数据路径
|
||||
* getDownloadCachePath : 获取下载缓存路径
|
||||
* getInternalAppDataPath : 获取内存应用数据路径
|
||||
* getInternalAppCodeCacheDir : 获取内存应用代码缓存路径
|
||||
* getInternalAppCachePath : 获取内存应用缓存路径
|
||||
* getInternalAppDbsPath : 获取内存应用数据库路径
|
||||
* getInternalAppDbPath : 获取内存应用数据库路径
|
||||
* getInternalAppFilesPath : 获取内存应用文件路径
|
||||
* getInternalAppSpPath : 获取内存应用 SP 路径
|
||||
* getInternalAppNoBackupFilesPath: 获取内存应用未备份文件路径
|
||||
* getExternalStoragePath : 获取外存路径
|
||||
* getExternalMusicPath : 获取外存音乐路径
|
||||
* getExternalPodcastsPath : 获取外存播客路径
|
||||
* getExternalRingtonesPath : 获取外存铃声路径
|
||||
* getExternalAlarmsPath : 获取外存闹铃路径
|
||||
* getExternalNotificationsPath : 获取外存通知路径
|
||||
* getExternalPicturesPath : 获取外存图片路径
|
||||
* getExternalMoviesPath : 获取外存影片路径
|
||||
* getExternalDownloadsPath : 获取外存下载路径
|
||||
* getExternalDcimPath : 获取外存数码相机图片路径
|
||||
* getExternalDocumentsPath : 获取外存文档路径
|
||||
* getExternalAppDataPath : 获取外存应用数据路径
|
||||
* getExternalAppCachePath : 获取外存应用缓存路径
|
||||
* getExternalAppFilesPath : 获取外存应用文件路径
|
||||
* getExternalAppMusicPath : 获取外存应用音乐路径
|
||||
* getExternalAppPodcastsPath : 获取外存应用播客路径
|
||||
* getExternalAppRingtonesPath : 获取外存应用铃声路径
|
||||
* getExternalAppAlarmsPath : 获取外存应用闹铃路径
|
||||
* getExternalAppNotificationsPath: 获取外存应用通知路径
|
||||
* getExternalAppPicturesPath : 获取外存应用图片路径
|
||||
* getExternalAppMoviesPath : 获取外存应用影片路径
|
||||
* getExternalAppDownloadPath : 获取外存应用下载路径
|
||||
* getExternalAppDcimPath : 获取外存应用数码相机图片路径
|
||||
* getExternalAppDocumentsPath : 获取外存应用文档路径
|
||||
* getExternalAppObbPath : 获取外存应用 OBB 路径
|
||||
* 路径 工具类 By https://github.com/Blankj/AndroidUtilCode -> PathUtils.java
|
||||
* Created by Max on 2018/12/12.
|
||||
*/
|
||||
object PathUtils {
|
||||
|
||||
|
||||
/**
|
||||
* Return the path of /system.
|
||||
*
|
||||
* @return the path of /system
|
||||
*/
|
||||
val rootPath: String
|
||||
get() = Environment.getRootDirectory().absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /data.
|
||||
*
|
||||
* @return the path of /data
|
||||
*/
|
||||
val dataPath: String
|
||||
get() = Environment.getDataDirectory().absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /cache.
|
||||
*
|
||||
* @return the path of /cache
|
||||
*/
|
||||
val downloadCachePath: String
|
||||
get() = Environment.getDownloadCacheDirectory().absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /data/data/package.
|
||||
*
|
||||
* @return the path of /data/data/package
|
||||
*/
|
||||
fun getInternalAppDataPath(application: Application): String {
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
application.applicationInfo.dataDir
|
||||
} else application.dataDir.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /data/data/package/code_cache.
|
||||
*
|
||||
* @return the path of /data/data/package/code_cache
|
||||
*/
|
||||
fun getInternalAppCodeCacheDir(application: Application): String {
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
application.applicationInfo.dataDir + "/code_cache"
|
||||
} else application.codeCacheDir.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /data/data/package/cache.
|
||||
*
|
||||
* @return the path of /data/data/package/cache
|
||||
*/
|
||||
fun getInternalAppCachePath(application: Application): String {
|
||||
return application.cacheDir.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /data/data/package/databases.
|
||||
*
|
||||
* @return the path of /data/data/package/databases
|
||||
*/
|
||||
fun getInternalAppDbsPath(application: Application): String {
|
||||
return application.applicationInfo.dataDir + "/databases"
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /data/data/package/databases/name.
|
||||
*
|
||||
* @param name The name of database.
|
||||
* @return the path of /data/data/package/databases/name
|
||||
*/
|
||||
fun getInternalAppDbPath(application: Application, name: String?): String {
|
||||
return application.getDatabasePath(name).absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /data/data/package/files.
|
||||
*
|
||||
* @return the path of /data/data/package/files
|
||||
*/
|
||||
fun getInternalAppFilesPath(application: Application): String {
|
||||
return application.filesDir.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /data/data/package/shared_prefs.
|
||||
*
|
||||
* @return the path of /data/data/package/shared_prefs
|
||||
*/
|
||||
fun getInternalAppSpPath(application: Application): String {
|
||||
return application.applicationInfo.dataDir + "shared_prefs"
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /data/data/package/no_backup.
|
||||
*
|
||||
* @return the path of /data/data/package/no_backup
|
||||
*/
|
||||
fun getInternalAppNoBackupFilesPath(application: Application): String {
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
application.applicationInfo.dataDir + "no_backup"
|
||||
} else application.noBackupFilesDir.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0.
|
||||
*
|
||||
* @return the path of /storage/emulated/0
|
||||
*/
|
||||
val externalStoragePath: String?
|
||||
get() = if (isExternalStorageDisable) null else Environment.getExternalStorageDirectory().absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Music.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Music
|
||||
*/
|
||||
val externalMusicPath: String?
|
||||
get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_MUSIC
|
||||
).absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Podcasts.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Podcasts
|
||||
*/
|
||||
val externalPodcastsPath: String?
|
||||
get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_PODCASTS
|
||||
).absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Ringtones.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Ringtones
|
||||
*/
|
||||
val externalRingtonesPath: String?
|
||||
get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_RINGTONES
|
||||
).absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Alarms.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Alarms
|
||||
*/
|
||||
val externalAlarmsPath: String?
|
||||
get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_ALARMS
|
||||
).absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Notifications.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Notifications
|
||||
*/
|
||||
val externalNotificationsPath: String?
|
||||
get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_NOTIFICATIONS
|
||||
).absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Pictures.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Pictures
|
||||
*/
|
||||
val externalPicturesPath: String?
|
||||
get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_PICTURES
|
||||
).absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Movies.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Movies
|
||||
*/
|
||||
val externalMoviesPath: String?
|
||||
get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_MOVIES
|
||||
).absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Download.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Download
|
||||
*/
|
||||
val externalDownloadsPath: String?
|
||||
get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOWNLOADS
|
||||
).absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/DCIM.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/DCIM
|
||||
*/
|
||||
val externalDcimPath: String?
|
||||
get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DCIM
|
||||
).absolutePath
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Documents.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Documents
|
||||
*/
|
||||
val externalDocumentsPath: String?
|
||||
get() {
|
||||
if (isExternalStorageDisable) return null
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
Environment.getExternalStorageDirectory().absolutePath + "/Documents"
|
||||
} else Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOCUMENTS
|
||||
).absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/data/package
|
||||
*/
|
||||
fun getExternalAppDataPath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.externalCacheDir?.parentFile?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package/cache.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/data/package/cache
|
||||
*/
|
||||
fun getExternalAppCachePath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.externalCacheDir?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package/files.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/data/package/files
|
||||
*/
|
||||
fun getExternalAppFilesPath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.getExternalFilesDir(null)?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package/files/Music.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/data/package/files/Music
|
||||
*/
|
||||
fun getExternalAppMusicPath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.getExternalFilesDir(
|
||||
Environment.DIRECTORY_MUSIC
|
||||
)?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package/files/Podcasts.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/data/package/files/Podcasts
|
||||
*/
|
||||
fun getExternalAppPodcastsPath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.getExternalFilesDir(
|
||||
Environment.DIRECTORY_PODCASTS
|
||||
)?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package/files/Ringtones.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/data/package/files/Ringtones
|
||||
*/
|
||||
fun getExternalAppRingtonesPath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.getExternalFilesDir(
|
||||
Environment.DIRECTORY_RINGTONES
|
||||
)?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package/files/Alarms.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/data/package/files/Alarms
|
||||
*/
|
||||
fun getExternalAppAlarmsPath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.getExternalFilesDir(
|
||||
Environment.DIRECTORY_ALARMS
|
||||
)?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package/files/Notifications.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/data/package/files/Notifications
|
||||
*/
|
||||
fun getExternalAppNotificationsPath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.getExternalFilesDir(
|
||||
Environment.DIRECTORY_NOTIFICATIONS
|
||||
)?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package/files/Pictures.
|
||||
*
|
||||
* @return path of /storage/emulated/0/Android/data/package/files/Pictures
|
||||
*/
|
||||
fun getExternalAppPicturesPath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.getExternalFilesDir(
|
||||
Environment.DIRECTORY_PICTURES
|
||||
)?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package/files/Movies.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/data/package/files/Movies
|
||||
*/
|
||||
fun getExternalAppMoviesPath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.getExternalFilesDir(
|
||||
Environment.DIRECTORY_MOVIES
|
||||
)?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package/files/Download.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/data/package/files/Download
|
||||
*/
|
||||
fun getExternalAppDownloadPath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.getExternalFilesDir(
|
||||
Environment.DIRECTORY_DOWNLOADS
|
||||
)?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package/files/DCIM.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/data/package/files/DCIM
|
||||
*/
|
||||
fun getExternalAppDcimPath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.getExternalFilesDir(
|
||||
Environment.DIRECTORY_DCIM
|
||||
)?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/data/package/files/Documents.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/data/package/files/Documents
|
||||
*/
|
||||
fun getExternalAppDocumentsPath(application: Application): String? {
|
||||
if (isExternalStorageDisable) return null
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
application.getExternalFilesDir(null)?.absolutePath + "/Documents"
|
||||
} else application.getExternalFilesDir(
|
||||
Environment.DIRECTORY_DOCUMENTS
|
||||
)?.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path of /storage/emulated/0/Android/obb/package.
|
||||
*
|
||||
* @return the path of /storage/emulated/0/Android/obb/package
|
||||
*/
|
||||
fun getExternalAppObbPath(application: Application): String? {
|
||||
return if (isExternalStorageDisable) null else application.obbDir.absolutePath
|
||||
}
|
||||
|
||||
private val isExternalStorageDisable: Boolean
|
||||
private get() = Environment.MEDIA_MOUNTED != Environment.getExternalStorageState()
|
||||
|
||||
/**
|
||||
* 判断sub是否在parent之下的文件或子文件夹<br></br>
|
||||
*
|
||||
* @param parent
|
||||
* @param sub
|
||||
* @return
|
||||
*/
|
||||
fun isSub(parent: File, sub: File): Boolean {
|
||||
return try {
|
||||
sub.absolutePath.startsWith(parent.absolutePath)
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子绝对路径与父绝对路径的相对路径
|
||||
*
|
||||
* @param parentPath
|
||||
* @param subPath
|
||||
* @return
|
||||
*/
|
||||
fun getRelativePath(parentPath: String?, subPath: String?): String? {
|
||||
return try {
|
||||
if (parentPath == null || subPath == null) {
|
||||
return null
|
||||
}
|
||||
if (subPath.startsWith(parentPath)) {
|
||||
subPath.substring(parentPath.length)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接两个路径
|
||||
*
|
||||
* @param pathA 路径A
|
||||
* @param pathB 路径B
|
||||
* @return 拼接后的路径
|
||||
*/
|
||||
fun plusPath(pathA: String?, pathB: String?): String? {
|
||||
if (pathA == null) {
|
||||
return pathB
|
||||
}
|
||||
if (pathB == null) {
|
||||
return pathA
|
||||
}
|
||||
return plusPathNotNull(pathA, pathB)
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接两个路径
|
||||
*
|
||||
* @param pathA 路径A
|
||||
* @param pathB 路径B
|
||||
* @return 拼接后的路径
|
||||
*/
|
||||
fun plusPathNotNull(pathA: String, pathB: String): String {
|
||||
val pathAEndSeparator = pathA.endsWith(File.separator)
|
||||
val pathBStartSeparator = pathB.startsWith(File.separator)
|
||||
return if (pathAEndSeparator && pathBStartSeparator) {
|
||||
pathA + pathB.substring(1)
|
||||
} else if (pathAEndSeparator || pathBStartSeparator) {
|
||||
pathA + pathB
|
||||
} else {
|
||||
pathA + File.separator + pathB
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取后缀名称
|
||||
* @param path 路径
|
||||
|
Reference in New Issue
Block a user