feat:完成礼物资源预缓存功能,修改礼物播放下载方案

This commit is contained in:
max
2024-05-16 16:58:11 +08:00
parent 58f1002b4e
commit 89b381bfbc
20 changed files with 3067 additions and 252 deletions

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}
}

View 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
}

View File

@@ -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)
}
}

View File

@@ -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"

View 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) ?: "")
}
}

View File

@@ -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>>
}
}

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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){}
}

View File

@@ -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)
}
}
}
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -0,0 +1,4 @@
package com.chwl.library.download
open class FileDownloadListener : DownloadListener

View File

@@ -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()
}
}

View File

@@ -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)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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 路径
@@ -30,4 +518,4 @@ object PathUtils {
}
return null
}
}
}