Merge branch 'feature-GHZS-2771-patch-3' into 'release'

fix: 支持xapk格式的apks文件解压-0706测试

See merge request halo/android/assistant-android!1193
This commit is contained in:
曾祥俊
2023-07-11 10:40:24 +08:00
6 changed files with 98 additions and 100 deletions

View File

@ -830,8 +830,9 @@
</intent-filter>
</receiver>
<receiver
<activity
android:name="com.gh.common.xapk.XapkInstallReceiver"
android:theme="@style/Theme.Transparent"
android:exported="false" />
<receiver

View File

@ -4,9 +4,12 @@ import android.app.Activity
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller.SessionCallback
import android.net.Uri
import android.os.Build
import android.util.Log
import android.view.View
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import com.gh.common.dialog.InstallPermissionDialogFragment
@ -18,6 +21,7 @@ import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.common.utils.getExtension
import com.gh.gamecenter.common.utils.getMetaExtra
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.core.utils.ToastUtils
@ -161,14 +165,13 @@ object PackageInstaller {
}
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_CANCEL_CURRENT
}
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, flags)
val pendingIntent = PendingIntent.getActivity(context, sessionId, intent, flags)
// 提交数据流并执行安装
session.commit(pendingIntent.intentSender)
session.close()
}
/**

View File

@ -1,31 +1,35 @@
package com.gh.common.xapk
import android.content.BroadcastReceiver
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.content.pm.PackageInstaller.SessionInfo
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class XapkInstallReceiver : BroadcastReceiver() {
class XapkInstallReceiver : Activity() {
companion object {
const val KEY_PACKAGE_PATH = "package_path"
}
override fun onReceive(context: Context, intent: Intent) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
overridePendingTransition(0, 0)
handleIntent(this, intent)
}
private fun handleIntent(context: Context, intent: Intent) {
when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val installIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
if (installIntent != null) {
val installPackagePath = intent.getStringExtra(KEY_PACKAGE_PATH)
val installSessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
XapkInstaller.onPendingUserAction(installPackagePath!!, installSessionId)
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
installIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
context.startActivity(installIntent)
updatePendingSessionInfoStatus(intent, XapkPendingSessionInfo.STATUS_PENDING_USER_ACTION)
finish()
}
}
PackageInstaller.STATUS_FAILURE,
@ -35,11 +39,21 @@ class XapkInstallReceiver : BroadcastReceiver() {
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE,
PackageInstaller.STATUS_FAILURE_INVALID,
PackageInstaller.STATUS_FAILURE_STORAGE -> {
val installPackagePath = intent.getStringExtra(KEY_PACKAGE_PATH)
if (!installPackagePath.isNullOrEmpty()) {
XapkInstaller.onInstallCanceled(installPackagePath)
}
updatePendingSessionInfoStatus(intent, XapkPendingSessionInfo.STATUS_INSTALL_CANCELED)
finish()
}
else -> {
updatePendingSessionInfoStatus(intent, XapkPendingSessionInfo.STATUS_INSTALL_SUCCESS)
finish()
}
}
}
private fun updatePendingSessionInfoStatus(intent: Intent, status: Int) {
val installPackagePath = intent.getStringExtra(KEY_PACKAGE_PATH)
if (!installPackagePath.isNullOrEmpty()) {
val pendingSessionInfo = XapkInstaller.getPendingSessionInfo(installPackagePath)
pendingSessionInfo?.updateStatus(status)
}
}
}

View File

@ -3,7 +3,6 @@ package com.gh.common.xapk
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.util.Log
import com.gh.common.util.*
import com.gh.download.DownloadDataHelper
import com.gh.download.DownloadManager
@ -61,9 +60,7 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
private val mDownloadEntityMap = Collections.synchronizedMap(HashMap<String, DownloadEntity>())
private val mInstallingPath = Collections.synchronizedList(mutableListOf<String>())
private val mPendingSessionIdMap = Collections.synchronizedMap(HashMap<String, Int>())
private val mPendingSessionInfoMap = HashMap<String, XapkPendingSessionInfo>()
// 按并行解压
@JvmStatic
@ -127,16 +124,7 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
Utils.log("unzip", "onSuccess->${downloadEntity.path}")
}
if (!mInstallingPath.contains(downloadEntity.path)) {
mInstallingPath.add(downloadEntity.path)
}
AppExecutor.ioExecutor.execute {
NDataChanger.notifyDataChanged(downloadEntity)
}
XapkInstallerLooper.add(downloadEntity.path, installer)
XapkInstallerLooper.install(mContext)
installer.install(mContext)
}
override fun onError(apk: XApkFile, exception: Throwable) {
@ -180,11 +168,9 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
}
}
fun isInstalling(packagePath: String): Boolean = mInstallingPath.contains(packagePath)
fun getPendingSessionInfo(packagePath: String): XapkPendingSessionInfo? = mPendingSessionInfoMap[packagePath]
fun onPendingUserAction(packagePath: String, sessionId: Int) {
mPendingSessionIdMap[packagePath] = sessionId
}
fun isInstalling(packagePath: String): Boolean = mPendingSessionInfoMap.containsKey(packagePath)
/**
* 通知XAPK安装完成
@ -192,15 +178,11 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
fun onInstalled(packagePath: String) {
val downloadEntity = mDownloadEntityMap.remove(packagePath) ?: return
mPendingSessionIdMap.remove(packagePath)
mInstallingPath.remove(packagePath)
mPendingSessionInfoMap.remove(packagePath)
downloadEntity.meta[XAPK_UNZIP_PERCENT] = "100.0"
downloadEntity.meta[XAPK_UNZIP_STATUS] = XapkUnzipStatus.INSTALLED.name
XapkInstallerLooper.remove(downloadEntity.path)
XapkInstallerLooper.install(mContext)
AppExecutor.ioExecutor.execute {
NDataChanger.notifyDataChanged(downloadEntity)
DownloadManager.getInstance().updateDownloadEntity(downloadEntity)
@ -212,16 +194,11 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
val downloadEntity = mDownloadEntityMap.remove(packagePath) ?: return
mPendingSessionIdMap.remove(packagePath)
mInstallingPath.remove(packagePath)
mPendingSessionInfoMap.remove(packagePath)
downloadEntity.meta[XAPK_UNZIP_PERCENT] = "0.0"
downloadEntity.meta[XAPK_UNZIP_STATUS] = XapkUnzipStatus.CANCEL.name
XapkInstallerLooper.remove(downloadEntity.path)
XapkInstallerLooper.install(mContext)
AppExecutor.ioExecutor.execute {
NDataChanger.notifyDataChanged(downloadEntity)
DownloadManager.getInstance().updateDownloadEntity(downloadEntity)
@ -234,31 +211,46 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
* 1. 用户触碰安装弹窗(原生Android系统)区域外导致安装取消
*/
fun updateCurrentInstallStatus() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || mPendingSessionIdMap.isEmpty()) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || mPendingSessionInfoMap.isEmpty()) {
return
}
val installer = mContext.packageManager.packageInstaller
val updateList = mutableListOf<XapkPendingSessionInfo>()
for (pendingSessionEntry in mPendingSessionIdMap) {
val sessionInfo = installer.getSessionInfo(pendingSessionEntry.value)
for (pendingSessionInfoEntry in mPendingSessionInfoMap) {
// 1. 用户点击了取消按钮时sessionInfo为空
// 2. 用户点击了确定按钮时sessionInfo的progress大于0.8
if (sessionInfo == null || sessionInfo.progress > 0.8F) {
continue
val pendingSessionInfo = pendingSessionInfoEntry.value
if (pendingSessionInfo.getStatus() == XapkPendingSessionInfo.STATUS_PENDING_USER_ACTION) {// 用户触摸安装弹窗外部区域取消安装后,更新安装状态
val installer = mContext.packageManager.packageInstaller
val sessionId = pendingSessionInfo.sessionId
if (sessionId != -1) {
val sessionInfo = installer.getSessionInfo(sessionId)
// 表示用户点击了安装弹窗外部区域
if (sessionInfo != null && sessionInfo.progress <= 0.8F) {
AppExecutor.ioExecutor.execute {
installer.abandonSession(sessionInfo.sessionId)
}
pendingSessionInfo.updateStatus(XapkPendingSessionInfo.STATUS_INSTALL_CANCELED)
}
}
}
AppExecutor.ioExecutor.execute {
installer.abandonSession(sessionInfo.sessionId)
val installStatus = pendingSessionInfo.getStatus()
if (installStatus == XapkPendingSessionInfo.STATUS_INSTALL_CANCELED
|| installStatus == XapkPendingSessionInfo.STATUS_INSTALL_SUCCESS
) {
updateList.add(pendingSessionInfo)
}
}
val downloadEntity = mDownloadEntityMap[pendingSessionEntry.key] ?: return
if (!PackageUtils.isInstalled(mContext, downloadEntity.packageName)) {
onInstallCanceled(downloadEntity.path)
} else {
for (pendingSessionInfo in updateList) {
val downloadEntity = mDownloadEntityMap[pendingSessionInfo.path] ?: continue
val installStatus = pendingSessionInfo.getStatus()
if (installStatus == XapkPendingSessionInfo.STATUS_INSTALL_SUCCESS) {
onInstalled(downloadEntity.path)
} else if (installStatus == XapkPendingSessionInfo.STATUS_INSTALL_CANCELED) {
onInstallCanceled(downloadEntity.path)
}
}
}
@ -284,10 +276,13 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
private val xApkFile: XApkFile,
private val sessionId: Int,
) : IPackageInstaller {
override fun install(context: Context) {
val downloadEntity = mDownloadEntityMap[xApkFile.file.path] ?: return
val applicationContext = context.applicationContext
mPendingSessionInfoMap[downloadEntity.path] = XapkPendingSessionInfo(downloadEntity.path, sessionId)
AppExecutor.ioExecutor.execute {// 有可能卡顿造成anr
NDataChanger.notifyDataChanged(downloadEntity)
PackageInstaller.installMultiple(applicationContext, downloadEntity.path, sessionId)
}
}
@ -297,6 +292,7 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
private val xApkFile: XApkFile,
private val file: File
) : IPackageInstaller {
override fun install(context: Context) {
val downloadEntity = mDownloadEntityMap[xApkFile.file.path] ?: return
PackageInstaller.install(
@ -307,42 +303,6 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
)
}
}
/**
* XAPK安装每次只能执行一个安装流程因此添加一个安装队列
*/
private object XapkInstallerLooper {
private val packagePathList = mutableListOf<String>()
private val installerList = mutableListOf<IPackageInstaller>()
fun add(packagePath: String, installer: IPackageInstaller) {
if (packagePathList.contains(packagePath)) {
return
}
packagePathList.add(packagePath)
installerList.add(installer)
}
fun install(context: Context) {
if (installerList.isEmpty()) {
return
}
installerList.first().install(context)
}
fun remove(packagePath: String) {
if (!packagePathList.contains(packagePath)) {
return
}
val index = packagePathList.indexOf(packagePath)
if (index != -1) {
packagePathList.removeAt(index)
installerList.removeAt(index)
}
}
}
}
enum class XapkUnzipStatus(status: String) {

View File

@ -0,0 +1,21 @@
package com.gh.common.xapk
class XapkPendingSessionInfo(
val path: String,
val sessionId: Int = -1
) {
companion object {
const val STATUS_INIT = -1
const val STATUS_PENDING_USER_ACTION = 0
const val STATUS_INSTALL_SUCCESS = 1
const val STATUS_INSTALL_CANCELED = 2
}
private var status = STATUS_INIT
internal fun updateStatus(status: Int) {
this.status = status
}
fun getStatus(): Int = status
}

View File

@ -111,8 +111,7 @@ object PackageObserver {
runOnIoThread { FileUtils.deleteFile(mDownloadEntity.path) }
}
if (mDownloadEntity.format == Constants.XAPK_FORMAT
|| mDownloadEntity.format == Constants.XAPK_APKS_FORMAT) {
if (mDownloadEntity.format == Constants.XAPK_FORMAT) {
XapkInstaller.onInstalled(mDownloadEntity.path)
}