diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4ec2e80671..75316f157c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -515,9 +515,6 @@ android:name=".qa.answer.draft.AnswerDraftActivity" android:screenOrientation="portrait" /> - - ()) + private val mXapkUnzipThreadMap = Collections.synchronizedMap(HashMap()) // 按并行解压 @JvmStatic @@ -45,23 +45,24 @@ object XapkInstaller : IUnzipListener { private var startTime = 0L private fun unzipXapkFile(downloadEntity: DownloadEntity) { - startTime = System.currentTimeMillis() - val thread = Thread(Runnable { - ZipHelper.unzipXapk(downloadEntity, this) - }) - thread.start() - - mXapkUnzipThreadMap[downloadEntity.path] = thread + if (mXapkUnzipThreadMap[downloadEntity.path] == null) { + startTime = System.currentTimeMillis() + val xapkUnzipThread = XapkUnzipThread(downloadEntity, this) + xapkUnzipThread.start() + mXapkUnzipThreadMap[downloadEntity.path] = xapkUnzipThread + } else { + debugOnly { + Utils.log("unzip", "重复解压,该文件解压已在队列中") + } + } } override fun onProgress(downloadEntity: DownloadEntity, unzipPath: String, unzipSize: Long, unzipProgress: Long) { AppExecutor.uiExecutor.execute { val df = DecimalFormat("#.0") var percent = 0.0 - // 有时会产生 “,0” 的数据导致 parseDouble 抛出异常 - try { + tryCatchInRelease { percent = df.format((unzipSize.toFloat() / unzipProgress) * 100).toDouble() - } catch (ignore: NumberFormatException) { } downloadEntity.meta[XAPK_UNZIP_PERCENT] = percent.toString() downloadEntity.meta[XAPK_UNZIP_STATUS] = XapkUnzipStatus.UNZIPPING.name @@ -82,13 +83,27 @@ object XapkInstaller : IUnzipListener { } // update download database - // todo 更新页面需要注意线程切换问题 debugOnly { Utils.log("unzip", "onNext->$unzipPath") } } + /** + * 取消解压回调 + * + * 取消后的表现与下载完成一致 + */ + override fun onCancel(downloadEntity: DownloadEntity) { + mXapkUnzipThreadMap.remove(downloadEntity.path) + + AppExecutor.uiExecutor.execute { + downloadEntity.meta[XAPK_UNZIP_PERCENT] = "0.0" + downloadEntity.meta[XAPK_UNZIP_STATUS] = XapkUnzipStatus.CANCEL.name + DataChanger.notifyDataChanged(downloadEntity) + } + } + override fun onFailure(downloadEntity: DownloadEntity, exception: Exception) { mXapkUnzipThreadMap.remove(downloadEntity.path) @@ -123,15 +138,15 @@ object XapkInstaller : IUnzipListener { } } - // todo 取消解压 + @JvmStatic fun cancelUnzipTask(zipPath: String) { - mXapkUnzipThreadMap[zipPath]?.interrupt() - // todo 取消解压还需要通知页面更新 + mXapkUnzipThreadMap[zipPath]?.canceled = true } } enum class XapkUnzipStatus(status: String) { UNZIPPING("unzipping"), SUCCESS("success"), + CANCEL("cancel"), FAILURE("failure"); } \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/util/ZipHelper.kt b/app/src/main/java/com/gh/common/xapk/XapkUnzipThread.kt similarity index 70% rename from app/src/main/java/com/gh/common/util/ZipHelper.kt rename to app/src/main/java/com/gh/common/xapk/XapkUnzipThread.kt index 7313319f83..b7fc922d30 100644 --- a/app/src/main/java/com/gh/common/util/ZipHelper.kt +++ b/app/src/main/java/com/gh/common/xapk/XapkUnzipThread.kt @@ -1,6 +1,8 @@ -package com.gh.common.util +package com.gh.common.xapk import android.os.Environment +import com.gh.common.util.getExtension +import com.gh.common.util.throwException import com.gh.gamecenter.BuildConfig import com.halo.assistant.HaloApp import com.lightgame.download.DownloadEntity @@ -9,16 +11,17 @@ import com.lightgame.utils.Utils import java.io.File import java.util.zip.ZipFile -object ZipHelper { - private const val DEFAULT_BUFFER_SIZE = 1024 * 1024 +class XapkUnzipThread(private var mDownloadEntity: DownloadEntity, + private var mUnzipListener: IXapkUnzipListener) : Thread() { - // todo apk包存放路径/命名等问题... - // todo apk 解压过慢,目前已知的影响因数(BufferSize,进度回调的同步操作) - // 目前已知的情况时xapk只会有一个apk包,obb资源包的数量时不固定的 - fun unzipXapk(downloadEntity: DownloadEntity, listener: IUnzipListener) { + private val mDefaultBufferSize = 1024 * 1024 + + var canceled = false + + override fun run() { + super.run() try { - // todo check file type??? - val path = downloadEntity.path + val path = mDownloadEntity.path val unzipSize = getUnzipSize(path) val msg = FileUtils.isCanDownload(HaloApp.getInstance().application, unzipSize) @@ -37,7 +40,8 @@ object ZipHelper { File(absolutePath + File.separator + zipEntry.name) } else if (zipEntry.name.getExtension() == XapkInstaller.PACKAGE_EXTENSION_NAME) { // apk文件名称 = xapk文件名+本身的文件名称 - File(FileUtils.getDownloadPath(HaloApp.getInstance().application, xapkFile.nameWithoutExtension + "_" + zipEntry.name)) + val fileName = xapkFile.nameWithoutExtension + "_" + zipEntry.name + File(FileUtils.getDownloadPath(HaloApp.getInstance().application, fileName)) } else continue // 暂时只需要解压xpk/obb文件 if (zipEntry.isDirectory) { @@ -59,49 +63,45 @@ object ZipHelper { throwException("unzip create file failure", !outputFile.createNewFile()) } else { unzipProgress += zipEntry.size - listener.onProgress(downloadEntity, outputFile.path, unzipProgress, unzipSize) - listener.onNext(downloadEntity, outputFile.path) + mUnzipListener.onProgress(mDownloadEntity, outputFile.path, unzipProgress, unzipSize) + mUnzipListener.onNext(mDownloadEntity, outputFile.path) continue } // unzip zip.getInputStream(zipEntry).use { input -> outputFile.outputStream().use { output -> - val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + val buffer = ByteArray(mDefaultBufferSize) var bytes = input.read(buffer) while (bytes >= 0) { output.write(buffer, 0, bytes) unzipProgress += bytes bytes = input.read(buffer) - listener.onProgress(downloadEntity, outputFile.path, unzipProgress, unzipSize) + if (canceled) { + mUnzipListener.onCancel(mDownloadEntity) + return + } else { + mUnzipListener.onProgress(mDownloadEntity, outputFile.path, unzipProgress, unzipSize) + } + } } } - listener.onNext(downloadEntity, outputFile.path) + mUnzipListener.onNext(mDownloadEntity, outputFile.path) } } - listener.onSuccess(downloadEntity) + mUnzipListener.onSuccess(mDownloadEntity) } catch (e: Exception) { if (BuildConfig.DEBUG) throw e - listener.onFailure(downloadEntity, e) + mUnzipListener.onFailure(mDownloadEntity, e) } } - fun getUnzipSize(path: String): Long { + private fun getUnzipSize(path: String): Long { var totalSize = 0L for (entry in ZipFile(File(path)).entries()) { totalSize += entry.size } return totalSize } -} - -interface IUnzipListener { - fun onProgress(downloadEntity: DownloadEntity, unzipPath: String, unzipSize: Long, unzipProgress: Long) - - fun onNext(downloadEntity: DownloadEntity, unzipPath: String) - - fun onFailure(downloadEntity: DownloadEntity, exception: Exception) - - fun onSuccess(downloadEntity: DownloadEntity) } \ No newline at end of file diff --git a/app/src/main/java/com/gh/download/DownloadManager.java b/app/src/main/java/com/gh/download/DownloadManager.java index 3b1ff2c130..a807334a3b 100644 --- a/app/src/main/java/com/gh/download/DownloadManager.java +++ b/app/src/main/java/com/gh/download/DownloadManager.java @@ -29,7 +29,7 @@ import com.gh.common.util.MD5Utils; import com.gh.common.util.NetworkUtils; import com.gh.common.util.PackageUtils; import com.gh.common.util.SPUtils; -import com.gh.common.util.XapkInstaller; +import com.gh.common.xapk.XapkInstaller; import com.gh.gamecenter.BuildConfig; import com.gh.gamecenter.entity.ApkEntity; import com.gh.gamecenter.entity.GameEntity; diff --git a/app/src/main/java/com/gh/gamecenter/XapkTestActivity.kt b/app/src/main/java/com/gh/gamecenter/XapkTestActivity.kt deleted file mode 100644 index 470e91dc87..0000000000 --- a/app/src/main/java/com/gh/gamecenter/XapkTestActivity.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.gh.gamecenter - -import android.app.Activity -import android.content.Intent -import android.util.Log -import android.view.View -import com.gh.base.BaseActivity -import com.gh.common.util.XapkInstaller -import com.lightgame.download.DownloadEntity - -class XapkTestActivity : BaseActivity() { - - override fun getLayoutId(): Int { - return R.layout.activity_test_xapk - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (resultCode != Activity.RESULT_OK || data == null) return - val filePath = data.data?.path ?: return - Log.e("MainActivity", filePath) - unZipFile(filePath) - } - - fun onSelectListener(view: View) { - val getContentIntent = Intent(Intent.ACTION_GET_CONTENT) - getContentIntent.type = "*/*" - getContentIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) - startActivityForResult(Intent.createChooser(getContentIntent, ""), REQUEST_CODE_GET_FILES) - } - - private fun unZipFile(path: String) { - val downloadEntity = DownloadEntity() - downloadEntity.path = path - downloadEntity.meta = hashMapOf() - XapkInstaller.install(downloadEntity) - } - - companion object { - const val REQUEST_CODE_GET_FILES: Int = 112 - } -} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/adapter/viewholder/DetailViewHolder.java b/app/src/main/java/com/gh/gamecenter/adapter/viewholder/DetailViewHolder.java index 9fb640bbb4..ab7946001b 100644 --- a/app/src/main/java/com/gh/gamecenter/adapter/viewholder/DetailViewHolder.java +++ b/app/src/main/java/com/gh/gamecenter/adapter/viewholder/DetailViewHolder.java @@ -28,7 +28,7 @@ import com.gh.common.util.PackageUtils; import com.gh.common.util.PermissionHelper; import com.gh.common.util.ReservationHelper; import com.gh.common.util.StringUtils; -import com.gh.common.util.XapkInstaller; +import com.gh.common.xapk.XapkInstaller; import com.gh.common.view.DownloadProgressBar; import com.gh.download.DownloadManager; import com.gh.download.dialog.DownloadDialog; diff --git a/app/src/main/java/com/gh/gamecenter/download/GameDownloadFragment.java b/app/src/main/java/com/gh/gamecenter/download/GameDownloadFragment.java index 9e36ebf953..24437dda8c 100644 --- a/app/src/main/java/com/gh/gamecenter/download/GameDownloadFragment.java +++ b/app/src/main/java/com/gh/gamecenter/download/GameDownloadFragment.java @@ -7,12 +7,19 @@ import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import com.gh.base.fragment.BaseFragment; import com.gh.common.util.DialogUtils; import com.gh.common.util.EntranceUtils; import com.gh.common.util.NetworkUtils; import com.gh.common.util.PackageUtils; import com.gh.common.view.RecyclerViewExtended; +import com.gh.common.xapk.XapkInstaller; +import com.gh.common.xapk.XapkUnzipStatus; import com.gh.download.DownloadManager; import com.gh.gamecenter.DownloadManagerActivity; import com.gh.gamecenter.MainActivity; @@ -32,10 +39,6 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.DefaultItemAnimator; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.OnClick; @@ -72,12 +75,20 @@ public class GameDownloadFragment extends BaseFragment implements View.OnClickLi private DataWatcher dataWatcher = new DataWatcher() { @Override public void onDataChanged(DownloadEntity downloadEntity) { + Integer location = adapter.getLocation(downloadEntity.getUrl()); + + String xapkStatus = downloadEntity.getMeta().get(XapkInstaller.XAPK_UNZIP_STATUS); + if (XapkUnzipStatus.UNZIPPING.name().equals(xapkStatus)) { + adapter.notifyItemChanged(location + 1); + return; + } + if (downloadEntity.getStatus().equals(DownloadStatus.downloading) || downloadEntity.getStatus().equals(DownloadStatus.pause) || downloadEntity.getStatus().equals(DownloadStatus.waiting)) { adapter.notifyItemChanged(adapter.getBase()); } - Integer location = adapter.getLocation(downloadEntity.getUrl()); + if (location != null) { if (!"pause".equals(adapter.getStatusMap().get(downloadEntity.getUrl()))) { if (downloadEntity.getStatus().equals(DownloadStatus.done)) { diff --git a/app/src/main/java/com/gh/gamecenter/download/GameDownloadFragmentAdapter.java b/app/src/main/java/com/gh/gamecenter/download/GameDownloadFragmentAdapter.java index 6652c0d3b4..7ebbf355e6 100644 --- a/app/src/main/java/com/gh/gamecenter/download/GameDownloadFragmentAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/download/GameDownloadFragmentAdapter.java @@ -1,9 +1,6 @@ package com.gh.gamecenter.download; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.graphics.Bitmap; import android.graphics.Color; import android.os.Message; import android.text.TextUtils; @@ -17,7 +14,6 @@ import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.ViewHolder; -import com.gh.common.util.BitmapUtils; import com.gh.common.util.DataUtils; import com.gh.common.util.DialogUtils; import com.gh.common.util.ImageUtils; @@ -26,6 +22,8 @@ import com.gh.common.util.PackageInstaller; import com.gh.common.util.PackageUtils; import com.gh.common.util.PlatformUtils; import com.gh.common.util.SpeedUtils; +import com.gh.common.xapk.XapkInstaller; +import com.gh.common.xapk.XapkUnzipStatus; import com.gh.download.DownloadManager; import com.gh.gamecenter.R; import com.gh.gamecenter.adapter.viewholder.DownloadHeadViewHolder; @@ -40,8 +38,6 @@ import com.lightgame.download.FileUtils; import com.lightgame.utils.Utils; import org.greenrobot.eventbus.EventBus; -import org.json.JSONException; -import org.json.JSONObject; import java.util.ArrayList; import java.util.Collections; @@ -116,26 +112,7 @@ class GameDownloadFragmentAdapter extends BaseRecyclerAdapter { downloadEntity = downloadingList.get(position - doneList.size() - 2); } String icon = downloadEntity.getIcon(); - if (!TextUtils.isEmpty(icon) && icon.contains("KuaiChuanIcon")) { // 来自快传的apk - try { - JSONObject jsonObject = new JSONObject(icon); - String apkPath = jsonObject.getString("KuaiChuanIcon"); - android.content.pm.PackageManager pm = mContext.getPackageManager(); - PackageInfo info = pm.getPackageArchiveInfo(apkPath, - android.content.pm.PackageManager.GET_ACTIVITIES); - if (info != null) { - ApplicationInfo appInfo = info.applicationInfo; - appInfo.sourceDir = apkPath; - appInfo.publicSourceDir = apkPath; - Bitmap bitmap = BitmapUtils.drawableToBitmap(appInfo.loadIcon(pm), true); - viewHolder.dmIcon.setImageBitmap(bitmap); - } - } catch (JSONException e) { - e.printStackTrace(); - } - - } else if (!TextUtils.isEmpty(icon)) { -// viewHolder.dmIcon.setImageURI(icon); + if (!TextUtils.isEmpty(icon)) { ImageUtils.display(viewHolder.dmIcon, icon); } else { ImageUtils.display(viewHolder.dmIcon, R.mipmap.logo); @@ -176,7 +153,17 @@ class GameDownloadFragmentAdapter extends BaseRecyclerAdapter { viewHolder.dmDownloads.setText("下载完成"); viewHolder.dmDelete.setVisibility(View.VISIBLE); viewHolder.dmStartorpause.setTextColor(Color.WHITE); - if (downloadEntity.isPluggable() + + String xapkStatus = downloadEntity.getMeta().get(XapkInstaller.XAPK_UNZIP_STATUS); + + if (XapkUnzipStatus.UNZIPPING.name().equals(xapkStatus)) { + viewHolder.dmStartorpause.setText("取消"); + viewHolder.dmStartorpause.setBackgroundResource(R.drawable.download_button_normal_style); + + String percent = downloadEntity.getMeta().get(XapkInstaller.XAPK_UNZIP_PERCENT); + viewHolder.dmProgressbar.setProgress((int) (Float.parseFloat(percent == null ? "0" : percent) * 10)); + viewHolder.dmSpeed.setText((percent + "%")); + } else if (downloadEntity.isPluggable() && PackagesManager.INSTANCE.isInstalled(downloadEntity.getPackageName())) { viewHolder.dmStartorpause.setText("安装"); viewHolder.dmStartorpause.setBackgroundResource(R.drawable.download_button_pluggable_style); @@ -336,7 +323,9 @@ class GameDownloadFragmentAdapter extends BaseRecyclerAdapter { , "安装包数据校验失败,无法完成下载,建议删除任务重新下载" , "知道了", null, null, null); break; - + case "取消": + XapkInstaller.cancelUnzipTask(downloadEntity.getPath()); + break; } // DataUtils.onMtaEvent(HaloApp.getInstance().getApplication(), "下载管理", "游戏下载", str); diff --git a/app/src/main/java/com/gh/gamecenter/fragment/SearchToolbarFragment.java b/app/src/main/java/com/gh/gamecenter/fragment/SearchToolbarFragment.java index c019fa61c8..8c4c8330d6 100644 --- a/app/src/main/java/com/gh/gamecenter/fragment/SearchToolbarFragment.java +++ b/app/src/main/java/com/gh/gamecenter/fragment/SearchToolbarFragment.java @@ -27,12 +27,10 @@ import com.gh.common.util.DisplayUtils; import com.gh.common.util.EntranceUtils; import com.gh.common.util.MtaHelper; import com.gh.download.DownloadManager; -import com.gh.gamecenter.BuildConfig; import com.gh.gamecenter.DownloadManagerActivity; import com.gh.gamecenter.MessageActivity; import com.gh.gamecenter.R; import com.gh.gamecenter.SearchActivity; -import com.gh.gamecenter.XapkTestActivity; import com.gh.gamecenter.entity.GameUpdateEntity; import com.gh.gamecenter.eventbus.EBDownloadStatus; import com.gh.gamecenter.eventbus.EBReuse; @@ -252,12 +250,6 @@ public class SearchToolbarFragment extends BaseLazyFragment implements View.OnCl startActivity(intent); break; case R.id.actionbar_notification: - // todo test - if (BuildConfig.DEBUG) { - startActivity(new Intent(getContext(), XapkTestActivity.class)); - return; - } - DataUtils.onEvent(getActivity(), "主页", "消息图标"); MtaHelper.onEvent("首页_点击", "顶栏", "消息中心"); DataCollectionUtils.uploadClick(getActivity(), "消息图标", "主页"); diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/video/TopVideoView.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/video/TopVideoView.kt index 4daeffad0c..48d30987b5 100644 --- a/app/src/main/java/com/gh/gamecenter/gamedetail/video/TopVideoView.kt +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/video/TopVideoView.kt @@ -15,7 +15,6 @@ import com.gh.common.constant.Constants import com.gh.common.observer.MuteCallback import com.gh.common.observer.VolumeObserver import com.gh.common.util.* -import com.gh.download.DownloadManager import com.gh.gamecenter.R import com.gh.gamecenter.entity.GameDetailEntity import com.gh.gamecenter.gamedetail.GameDetailViewModel