diff --git a/app/src/main/java/com/chwl/app/MainActivity.java b/app/src/main/java/com/chwl/app/MainActivity.java index 441bcb622..8a02e05f6 100644 --- a/app/src/main/java/com/chwl/app/MainActivity.java +++ b/app/src/main/java/com/chwl/app/MainActivity.java @@ -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 } }); } - private void otherModelInit() { PwdCodeMgr.get().onCreateInit(); //初始化线程池 @@ -263,6 +264,14 @@ public class MainActivity extends BaseMvpActivity 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 diff --git a/app/src/main/java/com/chwl/app/application/App.java b/app/src/main/java/com/chwl/app/application/App.java index f77de32c6..3947885eb 100644 --- a/app/src/main/java/com/chwl/app/application/App.java +++ b/app/src/main/java/com/chwl/app/application/App.java @@ -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); } diff --git a/app/src/main/java/com/chwl/app/avroom/widget/GiftEffectView.java b/app/src/main/java/com/chwl/app/avroom/widget/GiftEffectView.java index 9b5e39fd0..0d3a1b0f2 100644 --- a/app/src/main/java/com/chwl/app/avroom/widget/GiftEffectView.java +++ b/app/src/main/java/com/chwl/app/avroom/widget/GiftEffectView.java @@ -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); + } } diff --git a/app/src/main/java/com/chwl/app/base/GlobalViewModelOwner.kt b/app/src/main/java/com/chwl/app/base/GlobalViewModelOwner.kt new file mode 100644 index 000000000..ae54ab0b5 --- /dev/null +++ b/app/src/main/java/com/chwl/app/base/GlobalViewModelOwner.kt @@ -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 +} diff --git a/app/src/main/java/com/chwl/app/support/PreloadResourceViewModel.kt b/app/src/main/java/com/chwl/app/support/PreloadResourceViewModel.kt new file mode 100644 index 000000000..7a0eb49c5 --- /dev/null +++ b/app/src/main/java/com/chwl/app/support/PreloadResourceViewModel.kt @@ -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? = 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 { + 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) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/layout_gift_effect.xml b/app/src/main/res/layout/layout_gift_effect.xml index 1cfeca9f0..494253842 100644 --- a/app/src/main/res/layout/layout_gift_effect.xml +++ b/app/src/main/res/layout/layout_gift_effect.xml @@ -5,143 +5,6 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ? = + launchRequest { + api.getEffectResourceList() + } + private interface Api { /** @@ -461,6 +466,14 @@ object HomeModel : BaseModel() { */ @GET("/home/pickResource") suspend fun getResourceJumpInfo(@Query("id") id: Int): ServiceResult + + /** + * 资源列表(预加载) + * + * @return - + */ + @GET("resource/effect") + suspend fun getEffectResourceList(): ServiceResult> } } \ No newline at end of file diff --git a/library/build.gradle b/library/build.gradle index cd59bfc23..32aada015 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -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() diff --git a/library/src/main/java/com/chwl/library/download/DownloadException.kt b/library/src/main/java/com/chwl/library/download/DownloadException.kt new file mode 100644 index 000000000..a284f83b2 --- /dev/null +++ b/library/src/main/java/com/chwl/library/download/DownloadException.kt @@ -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 +} \ No newline at end of file diff --git a/library/src/main/java/com/chwl/library/download/DownloadListener.kt b/library/src/main/java/com/chwl/library/download/DownloadListener.kt new file mode 100644 index 000000000..aed75d39b --- /dev/null +++ b/library/src/main/java/com/chwl/library/download/DownloadListener.kt @@ -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){} +} \ No newline at end of file diff --git a/library/src/main/java/com/chwl/library/download/DownloadManager.kt b/library/src/main/java/com/chwl/library/download/DownloadManager.kt new file mode 100644 index 000000000..6bff08f30 --- /dev/null +++ b/library/src/main/java/com/chwl/library/download/DownloadManager.kt @@ -0,0 +1,231 @@ +package com.chwl.library.download + +import com.example.lib_utils.log.ILog +import java.util.concurrent.ConcurrentHashMap + + +object DownloadManager : ILog { + + /** + * 任务列表 + */ + private val tasks: ConcurrentHashMap by lazy { + ConcurrentHashMap() + } + + /** + * 标签列表 + */ + private val tags: ConcurrentHashMap> 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().apply { + add(url) + } + } + } + } +} \ No newline at end of file diff --git a/library/src/main/java/com/chwl/library/download/DownloadRequest.kt b/library/src/main/java/com/chwl/library/download/DownloadRequest.kt new file mode 100644 index 000000000..b222b6ffa --- /dev/null +++ b/library/src/main/java/com/chwl/library/download/DownloadRequest.kt @@ -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? = null, + timeout: Long? = null, + ): DownloadRequest { + return FileDownloadRequest(url, path, tag, header, timeout) + } + } + + /** + * 下载地址 + */ + fun getUrl(): String + + /** + * 本地文件路径 + */ + fun getPath(): String + + /** + * 标签 + */ + fun getTag(): String + + /** + * 请求头 + */ + fun getHeader(): Map? + + /** + * 获取下载任务 + */ + fun createTask(): DownloadTask + + /** + * 获取下载超时时间(毫秒) + */ + fun getTimeout(): Long? + + /** + * 是否是相同的目标 + */ + fun equalsTarget(request: DownloadRequest): Boolean +} \ No newline at end of file diff --git a/library/src/main/java/com/chwl/library/download/DownloadState.kt b/library/src/main/java/com/chwl/library/download/DownloadState.kt new file mode 100644 index 000000000..3b08790d0 --- /dev/null +++ b/library/src/main/java/com/chwl/library/download/DownloadState.kt @@ -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 +} \ No newline at end of file diff --git a/library/src/main/java/com/chwl/library/download/DownloadTask.kt b/library/src/main/java/com/chwl/library/download/DownloadTask.kt new file mode 100644 index 000000000..e0fd66bdf --- /dev/null +++ b/library/src/main/java/com/chwl/library/download/DownloadTask.kt @@ -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) +} \ No newline at end of file diff --git a/library/src/main/java/com/chwl/library/download/FileDownloadListener.kt b/library/src/main/java/com/chwl/library/download/FileDownloadListener.kt new file mode 100644 index 000000000..3abad6352 --- /dev/null +++ b/library/src/main/java/com/chwl/library/download/FileDownloadListener.kt @@ -0,0 +1,4 @@ +package com.chwl.library.download + + +open class FileDownloadListener : DownloadListener \ No newline at end of file diff --git a/library/src/main/java/com/chwl/library/download/FileDownloadRequest.kt b/library/src/main/java/com/chwl/library/download/FileDownloadRequest.kt new file mode 100644 index 000000000..835a56718 --- /dev/null +++ b/library/src/main/java/com/chwl/library/download/FileDownloadRequest.kt @@ -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? = 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? { + 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() + } +} \ No newline at end of file diff --git a/library/src/main/java/com/chwl/library/download/FileDownloadTask.kt b/library/src/main/java/com/chwl/library/download/FileDownloadTask.kt new file mode 100644 index 000000000..9739afb55 --- /dev/null +++ b/library/src/main/java/com/chwl/library/download/FileDownloadTask.kt @@ -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 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) + } + } +} \ No newline at end of file diff --git a/libs/lib_utils/src/main/java/com/example/lib_utils/FileUtils2.java b/libs/lib_utils/src/main/java/com/example/lib_utils/FileUtils2.java new file mode 100644 index 000000000..e73206320 --- /dev/null +++ b/libs/lib_utils/src/main/java/com/example/lib_utils/FileUtils2.java @@ -0,0 +1,1294 @@ +package com.example.lib_utils; + + +import android.content.Intent; +import android.net.Uri; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import javax.net.ssl.HttpsURLConnection; + +/** + *
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     user_ic_time  : 2016/05/03
+ *     desc  : utils home_ic_about_us file
+ * 
+ * https://github.com/Blankj/AndroidUtilCode/blob/master/lib/utilcode/src/main/java/com/blankj/utilcode/util/FileUtils.java + */ +public final class FileUtils2 { + + private static final String LINE_SEP = System.getProperty("line.separator"); + + private FileUtils2() { + throw new UnsupportedOperationException("u can't instantiate me..."); + } + + /** + * Return the file by path. + * + * @param filePath The path of file. + * @return the file + */ + public static File getFileByPath(final String filePath) { + return isSpace(filePath) ? null : new File(filePath); + } + + /** + * Return whether the file exists. + * + * @param filePath The path of file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isFileExists(final String filePath) { + return isFileExists(getFileByPath(filePath)); + } + + /** + * Return whether the file exists. + * + * @param file The file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isFileExists(final File file) { + return file != null && file.exists(); + } + + /** + * Rename the file. + * + * @param filePath The path of file. + * @param newName The new name of file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean rename(final String filePath, final String newName) { + return rename(getFileByPath(filePath), newName); + } + + /** + * Rename the file. + * + * @param file The file. + * @param newName The new name of file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean rename(final File file, final String newName) { + // file is null then return false + if (file == null) return false; + // file doesn't exist then return false + if (!file.exists()) return false; + // the new name is space then return false + if (isSpace(newName)) return false; + // the new name equals old name then return true + if (newName.equals(file.getName())) return true; + File newFile = new File(file.getParent() + File.separator + newName); + // the new name of file exists then return false + return !newFile.exists() + && file.renameTo(newFile); + } + + /** + * Return whether it is a directory. + * + * @param dirPath The path of directory. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isDir(final String dirPath) { + return isDir(getFileByPath(dirPath)); + } + + /** + * Return whether it is a directory. + * + * @param file The file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isDir(final File file) { + return file != null && file.exists() && file.isDirectory(); + } + + /** + * Return whether it is a file. + * + * @param filePath The path of file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isFile(final String filePath) { + return isFile(getFileByPath(filePath)); + } + + /** + * Return whether it is a file. + * + * @param file The file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isFile(final File file) { + return file != null && file.exists() && file.isFile(); + } + + /** + * Create a directory if it doesn't exist, otherwise do nothing. + * + * @param dirPath The path of directory. + * @return {@code true}: exists or creates successfully
{@code false}: otherwise + */ + public static boolean createOrExistsDir(final String dirPath) { + return createOrExistsDir(getFileByPath(dirPath)); + } + + /** + * Create a directory if it doesn't exist, otherwise do nothing. + * + * @param file The file. + * @return {@code true}: exists or creates successfully
{@code false}: otherwise + */ + public static boolean createOrExistsDir(final File file) { + return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); + } + + /** + * Create a file if it doesn't exist, otherwise do nothing. + * + * @param filePath The path of file. + * @return {@code true}: exists or creates successfully
{@code false}: otherwise + */ + public static boolean createOrExistsFile(final String filePath) { + return createOrExistsFile(getFileByPath(filePath)); + } + + /** + * Create a file if it doesn't exist, otherwise do nothing. + * + * @param file The file. + * @return {@code true}: exists or creates successfully
{@code false}: otherwise + */ + public static boolean createOrExistsFile(final File file) { + if (file == null) return false; + if (file.exists()) return file.isFile(); + if (!createOrExistsDir(file.getParentFile())) return false; + try { + return file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Create a file if it doesn't exist, otherwise delete old file before creating. + * + * @param filePath The path of file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean createFileByDeleteOldFile(final String filePath) { + return createFileByDeleteOldFile(getFileByPath(filePath)); + } + + /** + * Create a file if it doesn't exist, otherwise delete old file before creating. + * + * @param file The file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean createFileByDeleteOldFile(final File file) { + if (file == null) return false; + // file exists and unsuccessfully delete then return false + if (file.exists() && !file.delete()) return false; + if (!createOrExistsDir(file.getParentFile())) return false; + try { + return file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Copy the directory. + * + * @param srcDirPath The path of source directory. + * @param destDirPath The path of destination directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyDir(final String srcDirPath, + final String destDirPath) { + return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath)); + } + + /** + * Copy the directory. + * + * @param srcDirPath The path of source directory. + * @param destDirPath The path of destination directory. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyDir(final String srcDirPath, + final String destDirPath, + final OnReplaceListener listener) { + return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener); + } + + /** + * Copy the directory. + * + * @param srcDir The source directory. + * @param destDir The destination directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyDir(final File srcDir, + final File destDir) { + return copyOrMoveDir(srcDir, destDir, false); + } + + /** + * Copy the directory. + * + * @param srcDir The source directory. + * @param destDir The destination directory. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyDir(final File srcDir, + final File destDir, + final OnReplaceListener listener) { + return copyOrMoveDir(srcDir, destDir, listener, false); + } + + /** + * Copy the file. + * + * @param srcFilePath The path of source file. + * @param destFilePath The path of destination file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyFile(final String srcFilePath, + final String destFilePath) { + return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath)); + } + + /** + * Copy the file. + * + * @param srcFilePath The path of source file. + * @param destFilePath The path of destination file. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyFile(final String srcFilePath, + final String destFilePath, + final OnReplaceListener listener) { + return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener); + } + + /** + * Copy the file. + * + * @param srcFile The source file. + * @param destFile The destination file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyFile(final File srcFile, + final File destFile) { + return copyOrMoveFile(srcFile, destFile, false); + } + + /** + * Copy the file. + * + * @param srcFile The source file. + * @param destFile The destination file. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyFile(final File srcFile, + final File destFile, + final OnReplaceListener listener) { + return copyOrMoveFile(srcFile, destFile, listener, false); + } + + /** + * Move the directory. + * + * @param srcDirPath The path of source directory. + * @param destDirPath The path of destination directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveDir(final String srcDirPath, + final String destDirPath) { + return moveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath)); + } + + /** + * Move the directory. + * + * @param srcDirPath The path of source directory. + * @param destDirPath The path of destination directory. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveDir(final String srcDirPath, + final String destDirPath, + final OnReplaceListener listener) { + return moveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener); + } + + /** + * Move the directory. + * + * @param srcDir The source directory. + * @param destDir The destination directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveDir(final File srcDir, + final File destDir) { + return copyOrMoveDir(srcDir, destDir, true); + } + + /** + * Move the directory. + * + * @param srcDir The source directory. + * @param destDir The destination directory. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveDir(final File srcDir, + final File destDir, + final OnReplaceListener listener) { + return copyOrMoveDir(srcDir, destDir, listener, true); + } + + /** + * Move the file. + * + * @param srcFilePath The path of source file. + * @param destFilePath The path of destination file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveFile(final String srcFilePath, + final String destFilePath) { + return moveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath)); + } + + /** + * Move the file. + * + * @param srcFilePath The path of source file. + * @param destFilePath The path of destination file. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveFile(final String srcFilePath, + final String destFilePath, + final OnReplaceListener listener) { + return moveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener); + } + + /** + * Move the file. + * + * @param srcFile The source file. + * @param destFile The destination file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveFile(final File srcFile, + final File destFile) { + return copyOrMoveFile(srcFile, destFile, true); + } + + /** + * Move the file. + * + * @param srcFile The source file. + * @param destFile The destination file. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveFile(final File srcFile, + final File destFile, + final OnReplaceListener listener) { + return copyOrMoveFile(srcFile, destFile, listener, true); + } + + private static boolean copyOrMoveDir(final File srcDir, + final File destDir, + final boolean isMove) { + return copyOrMoveDir(srcDir, destDir, new OnReplaceListener() { + @Override + public boolean onReplace() { + return true; + } + }, isMove); + } + + private static boolean copyOrMoveDir(final File srcDir, + final File destDir, + final OnReplaceListener listener, + final boolean isMove) { + if (srcDir == null || destDir == null) return false; + // destDir's path locate in srcDir's path then return false + String srcPath = srcDir.getPath() + File.separator; + String destPath = destDir.getPath() + File.separator; + if (destPath.contains(srcPath)) return false; + if (!srcDir.exists() || !srcDir.isDirectory()) return false; + if (destDir.exists()) { + if (listener == null || listener.onReplace()) {// require delete the old directory + if (!deleteAllInDir(destDir)) {// unsuccessfully delete then return false + return false; + } + } else { + return true; + } + } + if (!createOrExistsDir(destDir)) return false; + File[] files = srcDir.listFiles(); + for (File file : files) { + File oneDestFile = new File(destPath + file.getName()); + if (file.isFile()) { + if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false; + } else if (file.isDirectory()) { + if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false; + } + } + return !isMove || deleteDir(srcDir); + } + + private static boolean copyOrMoveFile(final File srcFile, + final File destFile, + final boolean isMove) { + return copyOrMoveFile(srcFile, destFile, new OnReplaceListener() { + @Override + public boolean onReplace() { + return true; + } + }, isMove); + } + + private static boolean copyOrMoveFile(final File srcFile, + final File destFile, + final OnReplaceListener listener, + final boolean isMove) { + if (srcFile == null || destFile == null) return false; + // srcFile equals destFile then return false + if (srcFile.equals(destFile)) return false; + // srcFile doesn't exist or isn't a file then return false + if (!srcFile.exists() || !srcFile.isFile()) return false; + if (destFile.exists()) { + if (listener == null || listener.onReplace()) {// require delete the old file + if (!destFile.delete()) {// unsuccessfully delete then return false + return false; + } + } else { + return true; + } + } + if (!createOrExistsDir(destFile.getParentFile())) return false; + try { + return writeFileFromIS(destFile, new FileInputStream(srcFile)) + && !(isMove && !deleteFile(srcFile)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Delete the directory. + * + * @param filePath The path of file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean delete(final String filePath) { + return delete(getFileByPath(filePath)); + } + + /** + * Delete the directory. + * + * @param file The file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean delete(final File file) { + if (file == null) return false; + if (file.isDirectory()) { + return deleteDir(file); + } + return deleteFile(file); + } + + /** + * Delete the directory. + * + * @param dirPath The path of directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteDir(final String dirPath) { + return deleteDir(getFileByPath(dirPath)); + } + + /** + * Delete the directory. + * + * @param dir The directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteDir(final File dir) { + if (dir == null) return false; + // dir doesn't exist then return true + if (!dir.exists()) return true; + // dir isn't a directory then return false + if (!dir.isDirectory()) return false; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (file.isFile()) { + if (!file.delete()) return false; + } else if (file.isDirectory()) { + if (!deleteDir(file)) return false; + } + } + } + return dir.delete(); + } + + /** + * Delete the file. + * + * @param srcFilePath The path of source file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFile(final String srcFilePath) { + return deleteFile(getFileByPath(srcFilePath)); + } + + /** + * Delete the file. + * + * @param file The file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFile(final File file) { + return file != null && (!file.exists() || file.isFile() && file.delete()); + } + + /** + * Delete the all in directory. + * + * @param dirPath The path of directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteAllInDir(final String dirPath) { + return deleteAllInDir(getFileByPath(dirPath)); + } + + /** + * Delete the all in directory. + * + * @param dir The directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteAllInDir(final File dir) { + return deleteFilesInDirWithFilter(dir, new FileFilter() { + @Override + public boolean accept(File pathname) { + return true; + } + }); + } + + /** + * Delete all files in directory. + * + * @param dirPath The path of directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFilesInDir(final String dirPath) { + return deleteFilesInDir(getFileByPath(dirPath)); + } + + /** + * Delete all files in directory. + * + * @param dir The directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFilesInDir(final File dir) { + return deleteFilesInDirWithFilter(dir, new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isFile(); + } + }); + } + + /** + * Delete all files that satisfy the filter in directory. + * + * @param dirPath The path of directory. + * @param filter The filter. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFilesInDirWithFilter(final String dirPath, + final FileFilter filter) { + return deleteFilesInDirWithFilter(getFileByPath(dirPath), filter); + } + + /** + * Delete all files that satisfy the filter in directory. + * + * @param dir The directory. + * @param filter The filter. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFilesInDirWithFilter(final File dir, final FileFilter filter) { + if (dir == null) return false; + // dir doesn't exist then return true + if (!dir.exists()) return true; + // dir isn't a directory then return false + if (!dir.isDirectory()) return false; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (filter.accept(file)) { + if (file.isFile()) { + if (!file.delete()) return false; + } else if (file.isDirectory()) { + if (!deleteDir(file)) return false; + } + } + } + } + return true; + } + + /** + * Return the files in directory. + *

Doesn't traverse subdirectories

+ * + * @param dirPath The path of directory. + * @return the files in directory + */ + public static List listFilesInDir(final String dirPath) { + return listFilesInDir(dirPath, false); + } + + /** + * Return the files in directory. + *

Doesn't traverse subdirectories

+ * + * @param dir The directory. + * @return the files in directory + */ + public static List listFilesInDir(final File dir) { + return listFilesInDir(dir, false); + } + + /** + * Return the files in directory. + * + * @param dirPath The path of directory. + * @param isRecursive True to traverse subdirectories, false otherwise. + * @return the files in directory + */ + public static List listFilesInDir(final String dirPath, final boolean isRecursive) { + return listFilesInDir(getFileByPath(dirPath), isRecursive); + } + + /** + * Return the files in directory. + * + * @param dir The directory. + * @param isRecursive True to traverse subdirectories, false otherwise. + * @return the files in directory + */ + public static List listFilesInDir(final File dir, final boolean isRecursive) { + return listFilesInDirWithFilter(dir, new FileFilter() { + @Override + public boolean accept(File pathname) { + return true; + } + }, isRecursive); + } + + /** + * Return the files that satisfy the filter in directory. + *

Doesn't traverse subdirectories

+ * + * @param dirPath The path of directory. + * @param filter The filter. + * @return the files that satisfy the filter in directory + */ + public static List listFilesInDirWithFilter(final String dirPath, + final FileFilter filter) { + return listFilesInDirWithFilter(getFileByPath(dirPath), filter, false); + } + + /** + * Return the files that satisfy the filter in directory. + *

Doesn't traverse subdirectories

+ * + * @param dir The directory. + * @param filter The filter. + * @return the files that satisfy the filter in directory + */ + public static List listFilesInDirWithFilter(final File dir, + final FileFilter filter) { + return listFilesInDirWithFilter(dir, filter, false); + } + + /** + * Return the files that satisfy the filter in directory. + * + * @param dirPath The path of directory. + * @param filter The filter. + * @param isRecursive True to traverse subdirectories, false otherwise. + * @return the files that satisfy the filter in directory + */ + public static List listFilesInDirWithFilter(final String dirPath, + final FileFilter filter, + final boolean isRecursive) { + return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive); + } + + /** + * Return the files that satisfy the filter in directory. + * + * @param dir The directory. + * @param filter The filter. + * @param isRecursive True to traverse subdirectories, false otherwise. + * @return the files that satisfy the filter in directory + */ + public static List listFilesInDirWithFilter(final File dir, + final FileFilter filter, + final boolean isRecursive) { + if (!isDir(dir)) return null; + List list = new ArrayList<>(); + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (filter.accept(file)) { + list.add(file); + } + if (isRecursive && file.isDirectory()) { + //noinspection ConstantConditions + list.addAll(listFilesInDirWithFilter(file, filter, true)); + } + } + } + return list; + } + + /** + * Return the user_ic_time that the file was last modified. + * + * @param filePath The path of file. + * @return the user_ic_time that the file was last modified + */ + + public static long getFileLastModified(final String filePath) { + return getFileLastModified(getFileByPath(filePath)); + } + + /** + * Return the user_ic_time that the file was last modified. + * + * @param file The file. + * @return the user_ic_time that the file was last modified + */ + public static long getFileLastModified(final File file) { + if (file == null) return -1; + return file.lastModified(); + } + + /** + * Return the charset of file simply. + * + * @param filePath The path of file. + * @return the charset of file simply + */ + public static String getFileCharsetSimple(final String filePath) { + return getFileCharsetSimple(getFileByPath(filePath)); + } + + /** + * Return the charset of file simply. + * + * @param file The file. + * @return the charset of file simply + */ + public static String getFileCharsetSimple(final File file) { + int p = 0; + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(file)); + p = (is.read() << 8) + is.read(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (is != null) { + is.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + switch (p) { + case 0xefbb: + return "UTF-8"; + case 0xfffe: + return "Unicode"; + case 0xfeff: + return "UTF-16BE"; + default: + return "GBK"; + } + } + + /** + * Return the number of lines of file. + * + * @param filePath The path of file. + * @return the number of lines of file + */ + public static int getFileLines(final String filePath) { + return getFileLines(getFileByPath(filePath)); + } + + /** + * Return the number of lines of file. + * + * @param file The file. + * @return the number of lines of file + */ + public static int getFileLines(final File file) { + int count = 1; + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(file)); + byte[] buffer = new byte[1024]; + int readChars; + if (LINE_SEP.endsWith("\n")) { + while ((readChars = is.read(buffer, 0, 1024)) != -1) { + for (int i = 0; i < readChars; ++i) { + if (buffer[i] == '\n') ++count; + } + } + } else { + while ((readChars = is.read(buffer, 0, 1024)) != -1) { + for (int i = 0; i < readChars; ++i) { + if (buffer[i] == '\r') ++count; + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (is != null) { + is.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return count; + } + + /** + * Return the size of directory. + * + * @param dirPath The path of directory. + * @return the size of directory + */ + public static String getDirSize(final String dirPath) { + return getDirSize(getFileByPath(dirPath)); + } + + /** + * Return the size of directory. + * + * @param dir The directory. + * @return the size of directory + */ + public static String getDirSize(final File dir) { + long len = getDirLength(dir); + return len == -1 ? "" : byte2FitMemorySize(len); + } + + /** + * Return the length of file. + * + * @param filePath The path of file. + * @return the length of file + */ + public static String getFileSize(final String filePath) { + long len = getFileLength(filePath); + return len == -1 ? "" : byte2FitMemorySize(len); + } + + /** + * Return the length of file. + * + * @param file The file. + * @return the length of file + */ + public static String getFileSize(final File file) { + long len = getFileLength(file); + return len == -1 ? "" : byte2FitMemorySize(len); + } + + /** + * Return the length of directory. + * + * @param dirPath The path of directory. + * @return the length of directory + */ + public static long getDirLength(final String dirPath) { + return getDirLength(getFileByPath(dirPath)); + } + + /** + * Return the length of directory. + * + * @param dir The directory. + * @return the length of directory + */ + public static long getDirLength(final File dir) { + if (!isDir(dir)) return -1; + long len = 0; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (file.isDirectory()) { + len += getDirLength(file); + } else { + len += file.length(); + } + } + } + return len; + } + + /** + * Return the length of file. + * + * @param filePath The path of file. + * @return the length of file + */ + public static long getFileLength(final String filePath) { + boolean isURL = filePath.matches("[a-zA-z]+://[^\\s]*"); + if (isURL) { + try { + HttpsURLConnection conn = (HttpsURLConnection) new URL(filePath).openConnection(); + conn.setRequestProperty("Accept-Encoding", "identity"); + conn.connect(); + if (conn.getResponseCode() == 200) { + return conn.getContentLength(); + } + return -1; + } catch (IOException e) { + e.printStackTrace(); + } + } + return getFileLength(getFileByPath(filePath)); + } + + /** + * Return the length of file. + * + * @param file The file. + * @return the length of file + */ + public static long getFileLength(final File file) { + if (!isFile(file)) return -1; + return file.length(); + } + + /** + * Return the MD5 of file. + * + * @param filePath The path of file. + * @return the md5 of file + */ + public static String getFileMD5ToString(final String filePath) { + File file = isSpace(filePath) ? null : new File(filePath); + return getFileMD5ToString(file); + } + + /** + * Return the MD5 of file. + * + * @param file The file. + * @return the md5 of file + */ + public static String getFileMD5ToString(final File file) { + return bytes2HexString(getFileMD5(file)); + } + + /** + * Return the MD5 of file. + * + * @param filePath The path of file. + * @return the md5 of file + */ + public static byte[] getFileMD5(final String filePath) { + return getFileMD5(getFileByPath(filePath)); + } + + /** + * Return the MD5 of file. + * + * @param file The file. + * @return the md5 of file + */ + public static byte[] getFileMD5(final File file) { + if (file == null) return null; + DigestInputStream dis = null; + try { + FileInputStream fis = new FileInputStream(file); + MessageDigest md = MessageDigest.getInstance("MD5"); + dis = new DigestInputStream(fis, md); + byte[] buffer = new byte[1024 * 256]; + while (true) { + if (!(dis.read(buffer) > 0)) break; + } + md = dis.getMessageDigest(); + return md.digest(); + } catch (NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } finally { + try { + if (dis != null) { + dis.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + + /** + * Return the file's path of directory. + * + * @param file The file. + * @return the file's path of directory + */ + public static String getDirName(final File file) { + if (file == null) return ""; + return getDirName(file.getAbsolutePath()); + } + + /** + * Return the file's path of directory. + * + * @param filePath The path of file. + * @return the file's path of directory + */ + public static String getDirName(final String filePath) { + if (isSpace(filePath)) return ""; + int lastSep = filePath.lastIndexOf(File.separator); + return lastSep == -1 ? "" : filePath.substring(0, lastSep + 1); + } + + /** + * Return the name of file. + * + * @param file The file. + * @return the name of file + */ + public static String getFileName(final File file) { + if (file == null) return ""; + return getFileName(file.getAbsolutePath()); + } + + /** + * Return the name of file. + * + * @param filePath The path of file. + * @return the name of file + */ + public static String getFileName(final String filePath) { + if (isSpace(filePath)) return ""; + int lastSep = filePath.lastIndexOf(File.separator); + return lastSep == -1 ? filePath : filePath.substring(lastSep + 1); + } + + /** + * Return the name of file without extension. + * + * @param file The file. + * @return the name of file without extension + */ + public static String getFileNameNoExtension(final File file) { + if (file == null) return ""; + return getFileNameNoExtension(file.getPath()); + } + + /** + * Return the name of file without extension. + * + * @param filePath The path of file. + * @return the name of file without extension + */ + public static String getFileNameNoExtension(final String filePath) { + if (isSpace(filePath)) return ""; + int lastPoi = filePath.lastIndexOf('.'); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastSep == -1) { + return (lastPoi == -1 ? filePath : filePath.substring(0, lastPoi)); + } + if (lastPoi == -1 || lastSep > lastPoi) { + return filePath.substring(lastSep + 1); + } + return filePath.substring(lastSep + 1, lastPoi); + } + + /** + * Return the extension of file. + * + * @param file The file. + * @return the extension of file + */ + public static String getFileExtension(final File file) { + if (file == null) return ""; + return getFileExtension(file.getPath()); + } + + /** + * Return the extension of file. + * + * @param filePath The path of file. + * @return the extension of file + */ + public static String getFileExtension(final String filePath) { + if (isSpace(filePath)) return ""; + int lastPoi = filePath.lastIndexOf('.'); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastPoi == -1 || lastSep >= lastPoi) return ""; + return filePath.substring(lastPoi + 1); + } + + /** + * Notify system to scan the file. + * + * @param file The file. + */ + public static void notifySystemToScan(final File file) { + if (file == null || !file.exists()) return; + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + Uri uri = Uri.fromFile(file); + intent.setData(uri); + AppUtils.getApp().sendBroadcast(intent); + } + + /** + * Notify system to scan the file. + * + * @param filePath The path of file. + */ + public static void notifySystemToScan(final String filePath) { + notifySystemToScan(getFileByPath(filePath)); + } + + /////////////////////////////////////////////////////////////////////////// + // interface + /////////////////////////////////////////////////////////////////////////// + + public interface OnReplaceListener { + boolean onReplace(); + } + + /////////////////////////////////////////////////////////////////////////// + // other utils methods + /////////////////////////////////////////////////////////////////////////// + + private static final char[] HEX_DIGITS = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + private static String bytes2HexString(final byte[] bytes) { + if (bytes == null) return ""; + int len = bytes.length; + if (len <= 0) return ""; + char[] ret = new char[len << 1]; + for (int i = 0, j = 0; i < len; i++) { + ret[j++] = HEX_DIGITS[bytes[i] >> 4 & 0x0f]; + ret[j++] = HEX_DIGITS[bytes[i] & 0x0f]; + } + return new String(ret); + } + + public static String byte2FitMemorySize(final long byteNum) { + if (byteNum < 0) { + return "shouldn't be less than zero!"; + } else if (byteNum < 1024) { + return String.format(Locale.getDefault(), "%.3fB", (double) byteNum); + } else if (byteNum < 1048576) { + return String.format(Locale.getDefault(), "%.3fKB", (double) byteNum / 1024); + } else if (byteNum < 1073741824) { + return String.format(Locale.getDefault(), "%.3fMB", (double) byteNum / 1048576); + } else { + return String.format(Locale.getDefault(), "%.3fGB", (double) byteNum / 1073741824); + } + } + + private static boolean isSpace(final String s) { + if (s == null) return true; + for (int i = 0, len = s.length(); i < len; ++i) { + if (!Character.isWhitespace(s.charAt(i))) { + return false; + } + } + return true; + } + + private static boolean writeFileFromIS(final File file, + final InputStream is) { + OutputStream os = null; + try { + os = new BufferedOutputStream(new FileOutputStream(file)); + byte[] data = new byte[8192]; + int len; + while ((len = is.read(data, 0, 8192)) != -1) { + os.write(data, 0, len); + } + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + if (os != null) { + os.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/libs/lib_utils/src/main/java/com/example/lib_utils/PathUtils.kt b/libs/lib_utils/src/main/java/com/example/lib_utils/PathUtils.kt index 614768198..3f8a3ea9d 100644 --- a/libs/lib_utils/src/main/java/com/example/lib_utils/PathUtils.kt +++ b/libs/lib_utils/src/main/java/com/example/lib_utils/PathUtils.kt @@ -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之下的文件或子文件夹

+ * + * @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 } -} \ No newline at end of file +}