[dev_opt_2.15.0] patch升级代码提交

This commit is contained in:
renwj
2023-03-06 19:50:41 +08:00
parent 91547ae873
commit 3c58608ca6
55 changed files with 2100 additions and 156 deletions

Binary file not shown.

View File

@@ -0,0 +1,88 @@
package com.mogo.functions.test
import android.util.*
import androidx.test.core.app.*
import androidx.test.ext.junit.runners.*
import androidx.test.filters.*
import androidx.test.platform.app.InstrumentationRegistry
import com.mogo.eagle.core.function.hmi.ui.*
import com.mogo.eagle.core.function.main.*
import com.mogo.eagle.core.utilcode.util.*
import kotlinx.coroutines.*
import org.junit.*
import org.junit.runner.*
import java.util.concurrent.*
import java.util.concurrent.TimeUnit.MILLISECONDS
import kotlin.Result
@RunWith(AndroidJUnit4::class)
@LargeTest
class ApkInstallerTest {
lateinit var launch: ActivityScenario<MainLauncherActivity>
@Before
fun before() {
launch = ActivityScenario.launch(MainLauncherActivity::class.java)
}
@Test
fun testInstall(): Unit = runBlocking {
Log.d("RWJ", "wait fragment show ...")
val f = ensureMoGoHmiFragmentShow()
Log.d("RWJ", "fragment showed, delay 10s ...")
delay(10000)
Log.d("RWJ", "10s end, start install ...")
val context = InstrumentationRegistry.getInstrumentation().context
ApkInstaller.installApp(f.requireContext(), context.assets.open("190000013.apk")) { code, msg ->
Log.d("RWJ", "code: $code, msg: $msg")
}
Log.d("RWJ", "开始延时10分钟....")
delay(TimeUnit.MINUTES.toMillis(10))
Log.d("RWJ", "延时10分钟结束....")
}
@Test
fun testInstall2(): Unit = runBlocking {
Log.d("RWJ", "wait fragment show ...")
val f = ensureMoGoHmiFragmentShow()
Log.d("RWJ", "fragment showed, delay 10s ...")
delay(10000)
Log.d("RWJ", "10s end, start install ...")
val context = InstrumentationRegistry.getInstrumentation().context
ApkInstaller.installApp(f.requireContext(), context.assets.open("HelloActivity.apk")) { code, msg ->
Log.d("RWJ", "code: $code, msg: $msg")
}
Log.d("RWJ", "开始延时10分钟....")
delay(TimeUnit.MINUTES.toMillis(10))
Log.d("RWJ", "延时10分钟结束....")
}
private suspend fun ensureMoGoHmiFragmentShow(): MoGoHmiFragment = suspendCancellableCoroutine {
launch.onActivity { itx ->
val executor = Executors.newSingleThreadScheduledExecutor()
executor.scheduleAtFixedRate({
var find =
itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment
while (find == null) {
find =
itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment
}
while (!find.isResumed) {
Thread.sleep(500)
}
it.resumeWith(Result.success(find))
try {
Thread.sleep(500)
executor.shutdownNow()
} catch (e: Throwable) {
e.printStackTrace()
}
}, 50, 500, MILLISECONDS)
}
}
}

View File

@@ -0,0 +1,89 @@
package com.mogo.functions.test
import androidx.test.core.app.*
import androidx.test.ext.junit.runners.*
import androidx.test.filters.*
import com.mogo.eagle.core.function.hmi.ui.*
import com.mogo.eagle.core.function.main.*
import com.mogo.launcher.patch.*
import com.mogo.launcher.patch.utils.*
import kotlinx.coroutines.*
import org.junit.*
import org.junit.runner.*
import java.io.*
import java.util.concurrent.*
import java.util.concurrent.TimeUnit.MILLISECONDS
import kotlin.Result
@RunWith(AndroidJUnit4::class)
@LargeTest
class PatchUtilsTest {
lateinit var launch: ActivityScenario<MainLauncherActivity>
@Before
fun before() {
launch = ActivityScenario.launch(MainLauncherActivity::class.java)
}
@Test
fun testGeneratePatchAndMergeThenInstall() = runBlocking {
val f = ensureMoGoHmiFragmentShow()
withContext(Dispatchers.Default) {
val context = f.context ?: return@withContext
val oldApkPath = context.let {
it.packageManager.getPackageInfo(it.packageName, 0)?.applicationInfo?.sourceDir
} ?: return@withContext
val oldApk = File(oldApkPath)
if (!oldApk.exists()) {
throw AssertionError("old apk file is not exist.")
}
val oldApkTemp = File(context.getExternalFilesDir(null), "patches/old.apk")
if (oldApkTemp.exists()) {
oldApkTemp.delete()
}
oldApkTemp.parentFile?.takeIf { !it.exists() }?.also { it.mkdirs() }
oldApk.copyTo(oldApkTemp)
val patch = File(context.getExternalFilesDir(null), "patches/patch.zip")
if (!patch.exists()) {
throw AssertionError("patch file is not exist.")
}
val newApk = File(context.getExternalFilesDir(null), "patches/new.apk")
// PatchUtils.applyPatch(context, File(oldApkPath), patch, newApk)
// PatchUtils.install(context, newApk)
}
delay(TimeUnit.MINUTES.toMillis(1))
}
private suspend fun ensureMoGoHmiFragmentShow(): MoGoHmiFragment = suspendCancellableCoroutine {
launch.onActivity { itx ->
val executor = Executors.newSingleThreadScheduledExecutor()
executor.scheduleAtFixedRate({
var find =
itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment
while (find == null) {
find =
itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment
}
while (!find.isResumed) {
Thread.sleep(500)
}
it.resumeWith(Result.success(find))
try {
Thread.sleep(500)
executor.shutdownNow()
} catch (e: Throwable) {
e.printStackTrace()
}
}, 50, 500, MILLISECONDS)
}
}
}

View File

@@ -16,8 +16,10 @@ import com.mogo.eagle.core.data.deva.scene.SceneTAG
import com.mogo.eagle.core.data.msgbox.MsgBoxBean
import com.mogo.eagle.core.function.api.devatools.IDevaToolsProvider
import com.mogo.eagle.core.function.api.devatools.apm.*
import com.mogo.eagle.core.function.api.devatools.download.*
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.zhjt.mogo_core_function_devatools.apm.*
import com.mogo.eagle.core.function.api.upgrade.*
import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager
import com.zhjt.mogo_core_function_devatools.badcase.consts.BadCaseConfig
import com.zhjt.mogo_core_function_devatools.binding.BindingCarManager.Companion.bindingCarManager
@@ -167,8 +169,8 @@ class DevaToolsProvider : IDevaToolsProvider {
iPCReportManager.showReportListWindow(context, isShow)
}
override fun downLoadPackage(downloadKey: String, downloadUrl: String) {
upgradeManager.downLoadPackage(mContext!!, downloadKey, downloadUrl)
override fun downLoadPackage(type: DownloadType, downloadKey: String, downloadUrl: String) {
upgradeManager.downLoadPackage(mContext!!,type, downloadKey, downloadUrl)
}
override fun updateUpgradeProgress() {
@@ -249,4 +251,8 @@ class DevaToolsProvider : IDevaToolsProvider {
override fun queryObuUpgrade(obuVersionName: String) {
bindingCarManager.queryObuUpgrade(obuVersionName)
}
override fun upgradeProvider(): IMoGoUpgradeProvider? {
return upgradeManager.upgradeProvider()
}
}

View File

@@ -162,8 +162,8 @@ class BindingCarManager : IMoGoAutopilotCarConfigListener {
* 查询app是否需要升级
*/
fun queryAppUpgrade() {
UpgradeAppNetWorkManager.getInstance()
.getAppUpgradeInfo(mContext, mAddress, role.toString() + "")
UpgradeAppNetWorkManager.instance
?.getAppUpgradeInfo(mContext, mAddress ?: "", role.toString() + "")
}

View File

@@ -7,6 +7,7 @@ import android.content.Context;
import com.mogo.cloud.passport.MoGoAiCloudClientConfig;
import com.mogo.commons.constants.HostConst;
import com.mogo.eagle.core.data.deva.bindingcar.UpgradeAppInfo;
import com.mogo.eagle.core.function.api.devatools.download.DownloadType;
import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager;
import com.mogo.eagle.core.function.call.obu.CallerObuApiManager;
import com.mogo.eagle.core.network.MoGoRetrofitFactory;
@@ -58,7 +59,7 @@ public class ObuUpgradeAppNetWorkManager {
String sn = MoGoAiCloudClientConfig.getInstance().getSn();
CallerLogger.INSTANCE.d(M_BINDING + TAG, "getObuUpgradeInfo mac = " + mac + " ---sn = " + sn + " ---versionName = " + versionName);
UpgradeAppRequest request = new UpgradeAppRequest(sn, mac, "7");
UpgradeAppRequest request = new UpgradeAppRequest(sn, mac, "7", null, "0");
RequestBody requestBody = RequestBody.create(MediaType.get("application/json;charset=UTF-8"), GsonUtil.jsonFromObject(request));
mUpgradeApiService.getUpgradeInfo(requestBody)
.subscribeOn(Schedulers.io())
@@ -73,7 +74,7 @@ public class ObuUpgradeAppNetWorkManager {
if (info != null && info.result != null) {
CallerLogger.INSTANCE.d(M_BINDING + TAG, "getObuUpgradeInfo appFileName = " + info.result.getAppFileName() + " ----url = " + info.result.getAppUrl() + " ----name = " + info.result.getVersionName() + " --obuVersionName =" + versionName + " ---info.result = " + info.result);
if (!String.valueOf(info.result.getVersionName()).equals(versionName)) { //判断是否下载,当文件名称不一致的时候,就下载
CallerDevaToolsManager.INSTANCE.downLoadPackage(info.result.getAppFileName(), info.result.getAppUrl());
CallerDevaToolsManager.INSTANCE.downLoadPackage(DownloadType.OBU, info.result.getAppFileName(), info.result.getAppUrl());
}
} else {
CallerLogger.INSTANCE.d(M_BINDING + TAG, "getObuUpgradeInfo onNext info == null");

View File

@@ -1,111 +0,0 @@
package com.zhjt.mogo_core_function_devatools.upgrade;
import static com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.M_BINDING;
import android.content.Context;
import com.elegant.utils.UiThreadHandler;
import com.mogo.cloud.passport.MoGoAiCloudClientConfig;
import com.mogo.commons.constants.HostConst;
import com.mogo.eagle.core.data.deva.bindingcar.UpgradeAppInfo;
import com.mogo.eagle.core.function.call.hmi.CallerHmiManager;
import com.mogo.eagle.core.network.MoGoRetrofitFactory;
import com.mogo.eagle.core.network.utils.GsonUtil;
import com.mogo.eagle.core.utilcode.breakpoint.Config;
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger;
import com.mogo.eagle.core.utilcode.util.AppUtils;
import com.mogo.eagle.core.utilcode.util.FileUtils;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.MediaType;
import okhttp3.RequestBody;
/**
* @author lixiaopeng
* @description 获取升级信息
* @since: 3/25/22
*/
public class UpgradeAppNetWorkManager {
private static volatile UpgradeAppNetWorkManager requestNoticeManager;
private final UpgradeApiService mUpgradeApiService;
private static final String TAG = "Upgrade";
private UpgradeAppNetWorkManager() {
mUpgradeApiService = MoGoRetrofitFactory.getInstance(HostConst.getHost())
.create(UpgradeApiService.class);
}
public static UpgradeAppNetWorkManager getInstance() {
if (requestNoticeManager == null) {
synchronized (UpgradeAppNetWorkManager.class) {
if (requestNoticeManager == null) {
requestNoticeManager = new UpgradeAppNetWorkManager();
}
}
}
return requestNoticeManager;
}
/**
* 获取app升级信息
*/
public void getAppUpgradeInfo(Context context, String mac, String screenType) {
// String sn = "X20202203105S688HZ";
// String mac = "48:b0:2d:3a:bc:78";
String sn = MoGoAiCloudClientConfig.getInstance().getSn();
int versionCode = AppUtils.getAppVersionCode();
CallerLogger.INSTANCE.d(M_BINDING + TAG, "getAppUpgradeInfo mac = " + mac + "---type = " + screenType + "---sn = " + sn + "---versionCode =" + versionCode);
UpgradeAppRequest request = new UpgradeAppRequest(sn, mac, screenType);
RequestBody requestBody = RequestBody.create(MediaType.get("application/json;charset=UTF-8"), GsonUtil.jsonFromObject(request));
mUpgradeApiService.getUpgradeInfo(requestBody)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<UpgradeAppInfo>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull UpgradeAppInfo info) {
if (info != null && info.result != null) {
CallerLogger.INSTANCE.d(M_BINDING + TAG, "UpgradeAppInfo url = " + info.result.getAppUrl() + "----code = " + info.result.getVersionCode() + "--versionCode =" + versionCode + "--info.result = " + info.result);
if (info.result.getVersionCode() > versionCode) {
CallerHmiManager.INSTANCE.showUpgradeDialog(info.result.getAppUrl().substring(info.result.getAppUrl().lastIndexOf("/")+1), info.result.getAppUrl(), info.result.getInstallTitle(), info.result.getInstallContent(), info.result.getInstallType());
} else {
deleteApkFile();
}
} else {
CallerLogger.INSTANCE.d(M_BINDING + TAG, "UpgradeAppInfo onNext info == null");
deleteApkFile();
}
}
@Override
public void onError(Throwable e) {
deleteApkFile();
}
@Override
public void onComplete() {
}
});
}
/**
* 删除APK 相关的文件
*/
private void deleteApkFile(){
UiThreadHandler.post(new Runnable() {
@Override
public void run() {
FileUtils.delete(Config.downLoadPath);
}
});
}
}

View File

@@ -0,0 +1,150 @@
package com.zhjt.mogo_core_function_devatools.upgrade
import android.content.*
import android.text.*
import android.util.*
import com.elegant.utils.UiThreadHandler
import com.mogo.cloud.passport.*
import com.mogo.commons.constants.*
import com.mogo.eagle.core.data.config.*
import com.mogo.eagle.core.data.deva.bindingcar.*
import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager.upgradeProvider
import com.mogo.eagle.core.function.call.hmi.CallerHmiManager.showUpgradeDialog
import com.mogo.eagle.core.function.call.patch.CallerPatchManager.addPatchInfo
import com.mogo.eagle.core.function.call.patch.CallerPatchManager.isPatchAccept
import com.mogo.eagle.core.network.*
import com.mogo.eagle.core.network.utils.*
import com.mogo.eagle.core.utilcode.breakpoint.*
import com.mogo.eagle.core.utilcode.breakpoint.Config
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d
import com.mogo.eagle.core.utilcode.mogo.logger.scene.*
import com.mogo.eagle.core.utilcode.util.*
import com.mogo.eagle.core.utilcode.util.FileUtils
import io.reactivex.*
import io.reactivex.android.schedulers.*
import io.reactivex.disposables.*
import io.reactivex.schedulers.*
import kotlinx.coroutines.*
import okhttp3.*
/**
* @author lixiaopeng
* @description 获取升级信息
* @since: 3/25/22
*/
class UpgradeAppNetWorkManager private constructor() {
private val mUpgradeApiService: UpgradeApiService = MoGoRetrofitFactory.getInstance(HostConst.getHost()).create(UpgradeApiService::class.java)
private val scope by lazy { CoroutineScope(Dispatchers.IO + SupervisorJob()) }
/**
* 获取app升级信息
*/
fun getAppUpgradeInfo(context: Context?, mac: String, screenType: String) { // String sn = "X20202203105S688HZ";
// String mac = "48:b0:2d:3a:bc:78";
var mac = mac
var screenType = screenType
var sn = MoGoAiCloudClientConfig.getInstance().sn
val versionCode = AppUtils.getAppVersionCode()
val versionName = AppUtils.getAppVersionName()
d(SceneConstant.M_BINDING + TAG, "getAppUpgradeInfo mac = $mac---type = $screenType---sn = $sn---versionCode =$versionCode---versionName =$versionName")
//TODO renwj undo start
mac = "48:b0:2d:4d:31:7f"
sn = null
screenType = "10" //TODO renwj undo end
val request = UpgradeAppRequest(sn, mac, screenType, versionName, "1")
val requestBody = RequestBody.create(MediaType.get("application/json;charset=UTF-8"), GsonUtil.jsonFromObject(request))
mUpgradeApiService.getUpgradeInfo(requestBody).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : Observer<UpgradeAppInfo> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(info: UpgradeAppInfo) {
doUpgrade(info)
}
override fun onError(e: Throwable) {
deleteApkFile()
}
override fun onComplete() {}
})
}
private fun doUpgrade(info: UpgradeAppInfo) {
scope.launch {
if (info.result != null) {
val versionCode = AppUtils.getAppVersionCode()
d(SceneConstant.M_BINDING + TAG, "UpgradeAppInfo url = " + info.result.appUrl + "----code = " + info.result.versionCode + "--versionCode =" + versionCode + "--info.result = " + info.result)
if (info.result.versionCode > versionCode) {
val patchInfo = info.result.patchInfo
var downloadUrl: String = info.result.appUrl
val provider = upgradeProvider()
if (patchInfo != null) {
val f1 = FunctionBuildConfig.isSupportPatchUpgrade
if (!f1) {
Log.d("ApkInstaller", "当前版本配置不支持增量升级...")
}
val f2 = provider != null
if (f1 && !f2) {
Log.d("ApkInstaller", "provider为空...")
}
var f3 = true
if (f2 && provider != null && !provider.isNeedGoPatchUpgrade()) {
Log.d("ApkInstaller", "上次patch升级失败了...")
f3 = false
}
var f4 = true
if (f3) {
provider?.recordUpgradeRecord(patchInfo.targetVersion, patchInfo.targetMd5, 1)
provider?.recordSourceMd5CheckStart()
f4 = isPatchAccept(Utils.getApp(), patchInfo.sourceMd5, patchInfo.patchSize.toLong())
if (!f4) {
Log.d("ApkInstaller", "旧版本apk包的md5与服务端上的md5不匹配...")
provider?.recordSourceMd5CheckFailed("旧apk的md5与服务端的md5不一致:[server_md5: ${patchInfo.sourceMd5}, apk_md5: ${AppUtils.getAppApkMd5()}]")
} else {
provider?.recordSourceMd5CheckSuccess()
}
}
if (f4) {
downloadUrl = patchInfo.patchDownloadUrl
addPatchInfo(patchInfo)
}
} else {
provider?.recordUpgradeRecord(info.result.versionName, null, 0)
}
withContext(Dispatchers.Main) {
showUpgradeDialog(downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1), downloadUrl, info.result.installTitle, info.result.installContent, info.result.installType)
}
} else {
deleteApkFile()
}
} else {
d(SceneConstant.M_BINDING + TAG, "UpgradeAppInfo onNext info == null")
deleteApkFile()
}
}
}
/**
* 删除APK 相关的文件
*/
private fun deleteApkFile() {
UiThreadHandler.post { FileUtils.delete(Config.downLoadPath) }
}
companion object {
@Volatile private var requestNoticeManager: UpgradeAppNetWorkManager? = null
private const val TAG = "Upgrade"
val instance: UpgradeAppNetWorkManager?
get() {
if (requestNoticeManager == null) {
synchronized(UpgradeAppNetWorkManager::class.java) {
if (requestNoticeManager == null) {
requestNoticeManager = UpgradeAppNetWorkManager()
}
}
}
return requestNoticeManager
}
}
}

View File

@@ -1,20 +1,27 @@
package com.zhjt.mogo_core_function_devatools.upgrade;
import java.io.Serializable;
/**
* @author lixiaopeng
* @description 获取app升级信息
* @since: 11/15/21
*/
public class UpgradeAppRequest {
public class UpgradeAppRequest implements Serializable {
private String mac;
private String sn;
private String screenType;
private String currentVersion;
public UpgradeAppRequest( String sn, String mac, String screenType) {
private String patchStatus;
public UpgradeAppRequest( String sn, String mac, String screenType, String versionName, String patchStatus) {
this.sn = sn;
this.mac = mac;
this.screenType = screenType;
this.currentVersion = versionName;
this.patchStatus = patchStatus;
}
public String getSn() {
@@ -41,4 +48,19 @@ public class UpgradeAppRequest {
this.screenType = screenType;
}
public String getVersionName() {
return currentVersion;
}
public void setVersionName(String versionName) {
this.currentVersion = versionName;
}
public String getPatchStatus() {
return patchStatus;
}
public void setPatchStatus(String patchStatus) {
this.patchStatus = patchStatus;
}
}

View File

@@ -2,11 +2,18 @@ package com.zhjt.mogo_core_function_devatools.upgrade
import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageInstaller
import com.mogo.eagle.core.data.constants.MogoServicePaths
import com.mogo.eagle.core.function.api.upgrade.IMoGoUpgradeProvider
import com.mogo.eagle.core.function.call.base.CallerBase
import androidx.core.app.NotificationCompat
import com.elegant.utils.UiThreadHandler
import com.mogo.eagle.core.data.obu.MogoObuConst
import com.mogo.eagle.core.function.api.devatools.IMogoDevaToolsUpgradeListener
import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsUpgradeListenerManager
import com.mogo.eagle.core.function.api.devatools.download.*
import com.mogo.eagle.core.function.api.devatools.download.DownloadType.*
import com.mogo.eagle.core.function.call.devatools.*
import com.mogo.eagle.core.function.call.patch.CallerPatchManager
import com.mogo.eagle.core.function.call.hmi.CallerHmiManager.updateStatusBarDownloadView
import com.mogo.eagle.core.function.call.obu.CallerObuApiManager
import com.mogo.eagle.core.utilcode.breakpoint.Config
@@ -15,10 +22,15 @@ import com.mogo.eagle.core.utilcode.breakpoint.callback.IDownload
import com.mogo.eagle.core.utilcode.breakpoint.utils.DownloadUtils
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
import com.mogo.eagle.core.utilcode.mogo.logger.Logger
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_DEVA
import com.mogo.eagle.core.utilcode.util.AppUtils
import com.mogo.eagle.core.utilcode.util.ZipUtils
import com.mogo.eagle.core.utilcode.util.*
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import kotlinx.coroutines.*
import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
class UpgradeManager : IDownload {
@@ -34,16 +46,24 @@ class UpgradeManager : IDownload {
private var map: Map<String, String>? = null
private var mDownloadFileName: String? = null
fun downLoadPackage(context: Context, downloadKey: String,downloadUrl: String) {
CallerLogger.d("${SceneConstant.M_OBU}${MogoObuConst.TAG_UPGRADE_OBU}", "UpgradeManager downLoadPackage = " + downloadUrl?.contains(".zip") + "----downloadKey = $downloadKey ---downloadUrl = $downloadUrl")
if (downloadUrl?.contains(".zip")) {
private val upgradeProvider: IMoGoUpgradeProvider? by lazy { CallerBase.getApiInstance(IMoGoUpgradeProvider::class.java, MogoServicePaths.PATH_UPGRADE_TYPE_API) }
private val types by lazy { ConcurrentHashMap<String, DownloadType>() }
fun upgradeProvider(): IMoGoUpgradeProvider? = upgradeProvider
fun downLoadPackage(context: Context, type: DownloadType, downloadKey: String, downloadUrl: String) {
CallerLogger.d("${SceneConstant.M_OBU}${MogoObuConst.TAG_UPGRADE_OBU}", "UpgradeManager downLoadPackage = " + downloadUrl.contains(".zip") + "----downloadKey = $downloadKey ---downloadUrl = $downloadUrl")
if (type == OBU) {
mDownloadFileName = downloadKey
}
types[downloadUrl] = type
DownloadUtils.downLoad(
context,
downloadUrl,
if (downloadUrl?.contains(".zip")) Config.downLoadObuPath else Config.downLoadPath,
if (type == OBU) Config.downLoadObuPath else Config.downLoadPath,
downloadKey,
5,
this
@@ -60,6 +80,12 @@ class UpgradeManager : IDownload {
CallerDevaToolsUpgradeListenerManager.invokeUpgradeStart(it)
}
}
val type = types[downloadUrl]
if (type == PATCH || type == APK) {
runBlocking {
CallerDevaToolsManager.upgradeProvider()?.recordDownloadStart()
}
}
}
}
@@ -90,8 +116,118 @@ class UpgradeManager : IDownload {
}
override fun onFinished(downloadUrl: String?, threadBean: ThreadBean?) {
if (downloadUrl != null) { //TODO 需要判断是否是apk文件
AppUtils.installApp(Config.downLoadPath + downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1))
if (downloadUrl != null) {
val type = types[downloadUrl]
if (type == APK || type == PATCH) {
val patchInfo = CallerPatchManager.getPatchInfoByUrl(downloadUrl)
if (patchInfo != null) {
var isPatchInstallFailed = false
var patchInstallFailedReason = ""
CallerPatchManager.removePatchInfoByUrl(downloadUrl)
val patch = File(Config.downLoadPath, downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1))
if (patch.exists()) {
try {
val newApk = File(Utils.getApp().getExternalFilesDir(null), "patch/merged/${SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.ROOT).format(Date()) }.apk")
val dir = newApk.parentFile
if (dir != null && !dir.exists()) {
val ret = dir.mkdirs()
if (!ret) {
Logger.w(TAG, "create new apk path failed.")
throw AssertionError("创建目录失败")
}
}
if (dir != null){
try {
if (newApk.exists()) {
newApk.delete()
}
} catch (t: Throwable) {
t.printStackTrace()
}
runBlocking {
upgradeProvider?.recordInstallApplyPatchStart()
}
var ret = CallerPatchManager.applyPatch(Utils.getApp(), patch, newApk)
if (!ret) {
runBlocking {
upgradeProvider?.recordInstallApplyPatchFailed("合成patch失败")
}
Logger.w(TAG, "合成patch失败...")
throw AssertionError("合成patch失败...")
} else {
runBlocking {
upgradeProvider?.recordInstallApplyPatchSuccess()
upgradeProvider?.recordTargetMd5CheckStart()
}
ret = CallerPatchManager.checkMd5ForMergedApk(Utils.getApp(), newApk, patchInfo.targetMd5)
if (!ret) {
runBlocking {
upgradeProvider?.recordTargetMd5CheckFailed("合成后的apk的md5与服务端上的目标版本的md5不一致:[server_target_md5: ${patchInfo.targetMd5}, merged_md5: ${Md5Util.getMd5FromFile(newApk)}]")
}
Logger.w(TAG, "md5校验失败...")
throw AssertionError("md5校验失败:[target:${patchInfo.targetMd5}]")
} else {
Logger.w(TAG, "md5校验成功...")
runBlocking {
upgradeProvider?.recordTargetMd5CheckSuccess()
upgradeProvider?.recordInstallStart()
}
ApkInstaller.installApp(Utils.getApp(), newApk) { code, reason ->
if (code != PackageInstaller.STATUS_SUCCESS) {
upgradeProvider?.also {
try {
newApk.delete()
} catch (t: Throwable) {
t.printStackTrace()
}
try {
patch.delete()
} catch (t: Throwable) {
t.printStackTrace()
}
runBlocking {
it.recordInstallFailed(code, reason)
}
}
}
}
}
}
}
} catch (t: Throwable) {
t.printStackTrace()
isPatchInstallFailed = true
patchInstallFailedReason = t.message ?: "安装失败: code -1"
}
} else {
patchInstallFailedReason = "patch下载后文件不存在"
isPatchInstallFailed = true
}
if (isPatchInstallFailed) {
runBlocking {
CallerDevaToolsManager.upgradeProvider()?.recordInstallFailed(ApkInstaller.INSTALL_CODE_INVLID, patchInstallFailedReason)
}
}
} else {
val apkPath = Config.downLoadPath + downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1)
val apk = File(apkPath)
ApkInstaller.installApp(Utils.getApp(), apk) { code, reason ->
if (code != PackageInstaller.STATUS_SUCCESS) {
upgradeProvider?.also { itx ->
try {
apk.delete()
} catch (t: Throwable) {
t.printStackTrace()
}
runBlocking {
itx.recordInstallFailed(code, reason)
}
}
}
}
}
}
}
if (downloadUrl != null) {
if (map.isNullOrEmpty()) {
@@ -114,6 +250,12 @@ class UpgradeManager : IDownload {
CallerDevaToolsUpgradeListenerManager.invokeUpgradeError(it, errorMsg ?: "未知错误")
}
}
val type = types[downloadUrl]
if (type == APK || type == PATCH) {
runBlocking {
CallerDevaToolsManager.upgradeProvider()?.recordDownloadFailed(errorMsg ?: "下载失败")
}
}
}
}

View File

@@ -0,0 +1,185 @@
package com.zhjt.mogo_core_function_devatools.upgrade.provider
import android.content.*
import android.content.pm.PackageInstaller
import com.alibaba.android.arouter.facade.annotation.Route
import com.mogo.eagle.core.data.constants.MogoServicePaths
import com.mogo.eagle.core.function.api.upgrade.*
import com.mogo.eagle.core.utilcode.util.*
import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.UpgradeDbHelper
import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.*
@Route(path = MogoServicePaths.PATH_UPGRADE_TYPE_API)
class MoGoUpgradeProviderImpl: IMoGoUpgradeProvider {
override fun init(context: Context?) {}
override suspend fun recordUpgradeRecord(newVersion: String, newMd5: String?, type: Int) {
UpgradeDbHelper.insertUpgradeRecord(UpgradeRecord(AppUtils.getAppVersionName(), newVersion, AppUtils.getAppApkMd5(), newMd5, if (type == 0) UpgradeType.FULL else UpgradeType.PATCH))
}
override suspend fun recordDownloadStart() {
UpgradeDbHelper.insertDownloadRecord(DownloadRecord(version = AppUtils.getAppVersionName(), status = DownloadStatus.DownloadStart))
}
override suspend fun recordDownloadFailed(error: String) {
UpgradeDbHelper.insertDownloadRecord(DownloadRecord(version = AppUtils.getAppVersionName(), status = DownloadStatus.DownloadFailed, failReason = error))
}
override suspend fun recordDownloadSuccess() {
UpgradeDbHelper.insertDownloadRecord(DownloadRecord(version = AppUtils.getAppVersionName(), status = DownloadStatus.DownloadComplete))
}
override suspend fun recordInstallStart() {
UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.InstallStart))
}
override suspend fun recordSourceMd5CheckStart() {
UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.SourceMd5CheckStart))
}
override suspend fun recordSourceMd5CheckFailed(error: String) {
UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.SourceMd5CheckFailed, failReason = error))
}
override suspend fun recordSourceMd5CheckSuccess() {
UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.SourceMd5CheckSuccess))
}
override suspend fun recordInstallApplyPatchStart() {
UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.ApplyPatchStart))
}
override suspend fun recordInstallApplyPatchFailed(error: String) {
UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.ApplyPatchFailed, failReason = error))
}
override suspend fun recordInstallApplyPatchSuccess() {
UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.ApplyPatchSuccess))
}
override suspend fun recordTargetMd5CheckStart() {
UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.TargetMd5CheckStart))
}
override suspend fun recordTargetMd5CheckFailed(error: String) {
UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.TargetMd5CheckFailed, failReason = error))
}
override suspend fun recordTargetMd5CheckSuccess() {
UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.TargetMd5CheckSuccess))
}
override suspend fun recordInstallFailed(code: Int, error: String) {
UpgradeDbHelper.insertInstallRecord(InstallRecord(version = AppUtils.getAppVersionName(), status = InstallStatus.InstallFailed, code = code, failReason = error))
}
override suspend fun isUpgradeSuccessByPatch(): Boolean {
val record = UpgradeDbHelper.getUpgradeRecordOnlyByTarget(AppUtils.getAppVersionName())
return record?.toVersion == AppUtils.getAppVersionName() && record.type == UpgradeType.PATCH
}
override suspend fun isUpgradeSuccessByFull(): Boolean {
val record = UpgradeDbHelper.getUpgradeRecordOnlyByTarget(AppUtils.getAppVersionName())
return record?.toVersion == AppUtils.getAppVersionName() && record.type == UpgradeType.FULL
}
override suspend fun removeRecordByTargetVersion(version: String) {
UpgradeDbHelper.deleteRecordByTargetVersion(version)
}
override suspend fun removeRecordBySourceVersion(version: String) {
UpgradeDbHelper.deleteRecordBySourceVersion(version)
}
override suspend fun getFullUpgradeFailedReason(): Map<String,List<Pair<Int, String>>>? {
if (isUpgradeSuccessByFull()) {
return null
}
if (isUpgradeSuccessByPatch()) {
return null
}
val full = UpgradeDbHelper.getUpgradeRecordFull(AppUtils.getAppVersionName())
if (full == null || full.upgrade?.type != UpgradeType.FULL) {
return null
}
val map = HashMap<String, List<Pair<Int, String>>>()
full.downloads?.takeIf {
it.isNotEmpty()
}?.sortedBy {
it.status.ordinal
}?.map {
it.status.ordinal to (it.failReason ?: "")
}?.also {
map["download"] = it
}
full.installs?.takeIf {
it.isNotEmpty()
}?.sortedBy {
it.status.ordinal
}?.map {
it.status.ordinal to (it.failReason ?: "")
}?.also {
map["install"] = it
}
return map
}
override suspend fun getPatchUpgradeFailedReason(): Map<String,List<Pair<Int, String>>>? {
if (isUpgradeSuccessByFull()) {
return null
}
if (isUpgradeSuccessByPatch()) {
return null
}
val full = UpgradeDbHelper.getUpgradeRecordFull(AppUtils.getAppVersionName())
if (full == null || full.upgrade?.type != UpgradeType.PATCH) {
return null
}
val map = HashMap<String, List<Pair<Int, String>>>()
full.downloads?.takeIf {
it.isNotEmpty()
}?.sortedBy {
it.status.ordinal
}?.map {
it.status.ordinal to (it.failReason ?: "")
}?.also {
map["download"] = it
}
full.installs?.takeIf {
it.isNotEmpty()
}?.sortedBy {
it.status.ordinal
}?.map {
it.status.ordinal to (it.failReason ?: "")
}?.also {
map["install"] = it
}
return map
}
override suspend fun hasUpgradeRecord(): Boolean {
return UpgradeDbHelper.hasRecords()
}
override suspend fun isNeedGoPatchUpgrade(): Boolean {
val reasons = getPatchUpgradeFailedReason()
return reasons?.let { itx ->
itx["install"]?.takeIf {
it.isNotEmpty()
}?.let {
val last = it.last()
val ordinal = last.first
val status = InstallStatus.values().find { v -> v.ordinal == ordinal }
if (status != null && status.isReallyFailed()) {
val code = UpgradeDbHelper.getUpgradeRecordFull(AppUtils.getAppVersionName())?.installs?.find { s -> s.status == status }?.code
code != PackageInstaller.STATUS_FAILURE_INVALID &&
code != PackageInstaller.STATUS_FAILURE_CONFLICT &&
code != PackageInstaller.STATUS_FAILURE &&
code != PackageInstaller.STATUS_FAILURE_STORAGE
} else {
true
}
} ?: true
} ?: true
}
}

View File

@@ -0,0 +1,20 @@
package com.zhjt.mogo_core_function_devatools.upgrade.provider.db
import androidx.room.Database
import androidx.room.RoomDatabase
import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.dao.IUpgradeRecordDao
import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.DownloadRecord
import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.InstallRecord
import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.UpgradeRecord
@Database(
entities = [
UpgradeRecord::class,
DownloadRecord::class,
InstallRecord::class],
version = 1,
exportSchema = false)
internal abstract class UpgradeRecordDb: RoomDatabase() {
abstract fun dao(): IUpgradeRecordDao
}

View File

@@ -0,0 +1,69 @@
package com.zhjt.mogo_core_function_devatools.upgrade.provider.db
import androidx.room.Room
import com.mogo.eagle.core.utilcode.util.Utils
import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.DownloadRecord
import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.InstallRecord
import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.UpgradeRecord
import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.UpgradeRecordFull
internal object UpgradeDbHelper {
private val db by lazy {
Room.databaseBuilder(Utils.getApp(), UpgradeRecordDb::class.java, "upgrade_records").build()
}
suspend fun insertUpgradeRecord(record: UpgradeRecord): Long = try {
db.dao().insertUpgradeRecord(record)
} catch (e: Exception) {
e.printStackTrace()
-1
}
suspend fun insertDownloadRecord(record: DownloadRecord): Long = try {
db.dao().insertDownloadRecord(record)
} catch (e: Exception) {
e.printStackTrace()
-1
}
suspend fun insertInstallRecord(record: InstallRecord): Long = try {
db.dao().insertInstallRecord(record)
} catch (e: Exception) {
e.printStackTrace()
-1
}
suspend fun getUpgradeRecordFull(oldVersion: String): UpgradeRecordFull? = try {
db.dao().getUpgradeRecordFull(oldVersion)
} catch (e: Exception) {
e.printStackTrace()
null
}
suspend fun getUpgradeRecordOnlyByTarget(targetVersion: String): UpgradeRecord? = try {
db.dao().getUpgradeRecordOnlyByTarget(targetVersion)
} catch (e: Exception) {
e.printStackTrace()
null
}
suspend fun deleteRecordBySourceVersion(version: String) = try {
db.dao().deleteRecordBySourceVersion(version)
} catch (e: Exception) {
e.printStackTrace()
}
suspend fun deleteRecordByTargetVersion(version: String) = try {
db.dao().deleteRecordByTargetVersion(version)
} catch (e: Exception) {
e.printStackTrace()
}
suspend fun hasRecords() = try {
db.dao().getRecordCount() > 0
} catch (e: Exception) {
e.printStackTrace()
false
}
}

View File

@@ -0,0 +1,44 @@
package com.zhjt.mogo_core_function_devatools.upgrade.provider.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy.IGNORE
import androidx.room.OnConflictStrategy.REPLACE
import androidx.room.Query
import androidx.room.Transaction
import com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo.*
@Dao
internal interface IUpgradeRecordDao {
@Insert(onConflict = REPLACE)
@Transaction
suspend fun insertUpgradeRecord(record: UpgradeRecord): Long
@Insert(onConflict = IGNORE)
@Transaction
suspend fun insertDownloadRecord(record: DownloadRecord): Long
@Insert(onConflict = IGNORE)
@Transaction
suspend fun insertInstallRecord(record: InstallRecord): Long
@Query("SELECT * FROM upgrade_record WHERE f_v = :oldVersion")
@Transaction
suspend fun getUpgradeRecordFull(oldVersion: String): UpgradeRecordFull?
@Query("SELECT * FROM upgrade_record WHERE f_v = :oldVersion")
suspend fun getUpgradeRecordOnly(oldVersion: String): UpgradeRecord?
@Query("SELECT * FROM upgrade_record WHERE t_v = :toVersion")
suspend fun getUpgradeRecordOnlyByTarget(toVersion: String): UpgradeRecord?
@Query("DELETE FROM upgrade_record WHERE f_v = :version")
suspend fun deleteRecordBySourceVersion(version: String)
@Query("DELETE FROM upgrade_record WHERE t_v = :version")
suspend fun deleteRecordByTargetVersion(version: String)
@Query("SELECT COUNT(*) FROM upgrade_record")
suspend fun getRecordCount(): Int
}

View File

@@ -0,0 +1,187 @@
package com.zhjt.mogo_core_function_devatools.upgrade.provider.db.vo
import androidx.room.*
import androidx.room.ForeignKey.CASCADE
import com.mogo.eagle.core.utilcode.util.*
@Entity(tableName = "upgrade_record")
data class UpgradeRecord(
@PrimaryKey
@ColumnInfo(name = "f_v")
var fromVersion: String,
@ColumnInfo(name = "t_v")
var toVersion: String,
@ColumnInfo(name = "f_m")
var fromMD5: String?,
@ColumnInfo(name = "t_m")
var toMD5: String?,
@field:TypeConverters(UpgradeType::class)
var type: UpgradeType
)
enum class UpgradeType {
FULL,
PATCH;
companion object {
@JvmStatic
@TypeConverter
fun to(value: Int?) = values().find { it.ordinal == value }
@JvmStatic
@TypeConverter
fun from(type: UpgradeType?) = type?.ordinal
}
}
@Entity(
tableName = "download_record",
foreignKeys = [
ForeignKey(
entity = UpgradeRecord::class,
parentColumns = arrayOf("f_v"),
childColumns = arrayOf("d_v"),
deferred = true,
onDelete = CASCADE
)],
indices = [
Index(value = arrayOf("id"), unique = true),
Index(value = arrayOf("d_v"))
]
)
data class DownloadRecord(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@ColumnInfo(name = "d_v")
var version: String,
@field:TypeConverters(DownloadStatus::class)
var status: DownloadStatus,
@ColumnInfo(name = "reason")
var failReason: String? = null
)
enum class DownloadStatus {
DownloadStart,
DownloadFailed,
DownloadComplete;
companion object {
@JvmStatic
@TypeConverter
fun to(value: Int?) = values().find { it.ordinal == value }
@JvmStatic
@TypeConverter
fun from(type: DownloadStatus?) = type?.ordinal
}
}
@Entity(
tableName = "install_record",
foreignKeys = [ForeignKey(
entity = UpgradeRecord::class,
parentColumns = arrayOf("f_v"),
childColumns = arrayOf("i_v"),
deferred = true,
onDelete = CASCADE
)],
indices = [
Index(value = arrayOf("id"), unique = true),
Index(value = arrayOf("i_v"))
]
)
data class InstallRecord(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@ColumnInfo(name = "i_v")
var version: String,
@field:TypeConverters(InstallStatus::class)
var status: InstallStatus,
@ColumnInfo(defaultValue = "${ApkInstaller.INSTALL_CODE_INVLID}")
var code: Int = 0,
@ColumnInfo(name = "reason")
var failReason: String? = null
)
enum class InstallStatus {
SourceMd5CheckStart,
SourceMd5CheckFailed,
SourceMd5CheckSuccess,
ApplyPatchStart,
ApplyPatchFailed,
ApplyPatchSuccess,
TargetMd5CheckStart,
TargetMd5CheckFailed,
TargetMd5CheckSuccess,
InstallStart,
InstallFailed,
InstallSuccess;
companion object {
@JvmStatic
@TypeConverter
fun to(value: Int?) = values().find { it.ordinal == value }
@JvmStatic
@TypeConverter
fun from(status: InstallStatus?) = status?.ordinal
}
fun isReallyFailed() = (this == SourceMd5CheckFailed || this == ApplyPatchFailed || this == TargetMd5CheckFailed)
}
class UpgradeRecordFull {
@Embedded
var upgrade: UpgradeRecord? = null
@Relation(
parentColumn = "f_v",
entityColumn = "d_v",
entity = DownloadRecord::class
)
var downloads: List<DownloadRecord>? = null
@Relation(
parentColumn = "f_v",
entityColumn = "i_v",
entity = InstallRecord::class
)
var installs: List<InstallRecord>? = null
}

View File

@@ -71,7 +71,7 @@ dependencies {
implementation rootProject.ext.dependencies.cicle_indicator
implementation rootProject.ext.dependencies.koomnative
implementation rootProject.ext.dependencies.koomxhook
implementation project(':core:function-impl:mogo-core-function-patch')
api project(':test:crashreport-apmbyte')
compileOnly project(':core:function-impl:mogo-core-function-datacenter')
implementation project(':foudations:mogo-commons')

View File

@@ -4,10 +4,16 @@ import android.content.Context
import android.view.View
import android.widget.TextView
import androidx.lifecycle.LifecycleObserver
import com.mogo.eagle.core.function.api.devatools.download.DownloadType.APK
import com.mogo.eagle.core.function.api.devatools.download.DownloadType.PATCH
import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager
import com.mogo.eagle.core.function.call.patch.*
import com.mogo.eagle.core.function.hmi.R
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.mogo.eagle.core.function.hmi.dialog.BaseFloatDialog
import com.mogo.eagle.core.utilcode.kotlin.*
import kotlinx.coroutines.*
import java.lang.ref.WeakReference
/**
* @brief APP升级提示弹框
@@ -18,7 +24,7 @@ class UpgradeAppDialog(context: Context) : BaseFloatDialog(context), LifecycleOb
companion object{
private const val TAG = "UpgradeAppDialog"
private var upgradeAppDialog: UpgradeAppDialog? = null
private var upgradeAppDialog: WeakReference<UpgradeAppDialog>? = null
fun show(context: Context?,
name: String,
@@ -27,10 +33,12 @@ class UpgradeAppDialog(context: Context) : BaseFloatDialog(context), LifecycleOb
content: String,
installType: String) {
context?.let {
if (upgradeAppDialog == null) {
upgradeAppDialog = UpgradeAppDialog(it)
var dialog = upgradeAppDialog?.get()
if (dialog == null) {
dialog = UpgradeAppDialog(it)
upgradeAppDialog = WeakReference(dialog)
}
upgradeAppDialog?.let { dialog ->
dialog.let { d ->
if (dialog.isShowing) {
return
}
@@ -78,10 +86,19 @@ class UpgradeAppDialog(context: Context) : BaseFloatDialog(context), LifecycleOb
/**
* 去下载
*/
fun downloadApp() {
private fun downloadApp() {
ToastUtils.showLong("开始下载APK,稍后可前往downloads文件夹查看,通知栏查看下载进度")
tag?.let { downloadUrl?.let { it1 -> CallerDevaToolsManager.downLoadPackage(it, it1) } }
tag?.let {
downloadUrl?.let { url ->
window?.decorView?.scope?.launch {
if (CallerPatchManager.getPatchInfoByUrl(url) != null) {
CallerDevaToolsManager.downLoadPackage(PATCH, it, url)
} else {
CallerDevaToolsManager.downLoadPackage(APK, it, url)
}
}
}
}
dismiss()
}

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,180 @@
import org.gradle.api.execution.*
import com.android.build.gradle.tasks.*
import com.android.build.gradle.*
import org.gradle.process.*
import org.gradle.api.invocation.*
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'com.alibaba.arouter'
id 'kotlin-kapt'
}
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
//ARouter apt 参数
kapt {
useBuildCache = false
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
sourceSets {
main {
jniLibs.srcDirs = ['jniLibs']
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation rootProject.ext.dependencies.androidxccorektx
implementation rootProject.ext.dependencies.androidxappcompat
implementation rootProject.ext.dependencies.material
androidTestImplementation rootProject.ext.dependencies.androidxjunit
androidTestImplementation rootProject.ext.dependencies.junit
implementation rootProject.ext.dependencies.lancetx_runtime
implementation rootProject.ext.dependencies.arouter
kapt rootProject.ext.dependencies.aroutercompiler
if (Boolean.valueOf(USE_MAVEN_PACKAGE)) {
implementation rootProject.ext.dependencies.moduleservice
implementation rootProject.ext.dependencies.mogo_core_data
implementation rootProject.ext.dependencies.mogo_core_utils
implementation rootProject.ext.dependencies.mogo_core_function_api
implementation rootProject.ext.dependencies.mogo_core_res
} else {
implementation project(':core:mogo-core-data')
implementation project(':core:mogo-core-utils')
implementation project(':core:mogo-core-function-api')
implementation project(':core:mogo-core-res')
}
}
rootProject.gradle.taskGraph.whenReady { TaskExecutionGraph graph ->
def tasks = graph.allTasks.findAll { t0 ->
t0 instanceof PackageApplication
}
def app = rootProject.subprojects.find {
it.plugins.hasPlugin("com.android.application")
}
if (app != null) {
def android = (AppExtension)app.extensions.findByName("android")
def signConfig = android.signingConfigs.getByName("release")
if (signConfig != null && isOnlineReleaseBuild(gradle)) {
if (tasks != null) {
tasks.forEach { t1 ->
t1.doLast { t2 ->
def packageTask = (PackageApplication) t2
def outDir = packageTask.outputDirectory
def apkNames = packageTask.apkNames
if (apkNames != null && apkNames.size() > 0) {
apkNames.forEach { apkName ->
def apk = new File(outDir, apkName)
def lp = new File(rootProject.rootDir, "local.properties")
if (apk.exists() && lp.exists()) {
def p = new Properties()
lp.withInputStream { instr ->
p.load(instr)
}
def sdkDir = p.getProperty('sdk.dir')
def apkSigner = new File(sdkDir, isWindows() ? "build-tools${File.separator}${android.buildToolsVersion}${File.separator}apksigner.bat" : "build-tools${File.separator}${android.buildToolsVersion}${File.separator}apksigner")
def newApk = new File(outDir, "normalized_" + apkName)
def outApk = new File(outDir, "signed_" + apkName)
def workDir = new File(projectDir, "shell")
def shell = isMac() ? ".${File.separator}macos${File.separator}ApkNormalized" : (isLinux() ? ".${File.separator}linux64${File.separator}ApkNormalized" : ".${File.separator}windows64${File.separator}ApkNormalized.exe")
println "***** apk:${apk.name} -> 开始执行apk文件格式对齐 *****"
ExecResult result = exec {
workingDir workDir
commandLine shell, "${apk.absolutePath}", "${newApk.absolutePath}"
}
def code = result.exitValue
if (code == 0) {
println "***** apk:${apk.name} -> apk文件格式对齐完成 *****"
}
println "***** apk:${newApk.name} -> 对文件格式对齐后的apk重新签名 *****"
result = exec {
workingDir apkSigner.parentFile
commandLine ".${File.separator}${apkSigner.name}", "sign", "-v",
"--out", "${outApk.absolutePath}",
"--ks", "${signConfig.storeFile.absolutePath}",
"--ks-key-alias", "${signConfig.keyAlias}",
"--ks-pass", "pass:${signConfig.storePassword}",
"--key-pass", "pass:${signConfig.keyPassword}",
"--pass-encoding", "utf-8",
"${newApk.absolutePath}"
}
code = result.exitValue
if (code == 0) {
println "***** apk:${newApk.name} -> 签名成功 *****"
}
println "***** 正在删除临时文件 *****"
try {
newApk.delete()
} catch (Throwable t) {
t.printStackTrace()
throw t
}
try {
outApk.renameTo(apk)
} catch (Throwable t) {
t.printStackTrace()
throw t
}
}
}
}
}
}
}
}
}
}
static boolean isWindows() {
return System.getProperty("os.name").toLowerCase().contains('windows')
}
static boolean isLinux() {
return System.getProperty("os.name").toLowerCase().contains('linux')
}
static boolean isMac() {
return System.getProperty("os.name").toLowerCase().contains('mac')
}
static boolean isOnlineReleaseBuild(Gradle g) {
StringBuilder sb = new StringBuilder()
for (String s : g.startParameter.taskNames) {
sb.append(s)
}
String buildScript = sb.toString().toLowerCase()
return buildScript.endsWith("release") && buildScript.contains("online")
}

View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,21 @@
package com.mogo.launcer.patch
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest {
@Test fun useAppContext() { // Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.mogo.launcer.patch.test", appContext.packageName)
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.launcher.patch">
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<meta-data android:name="SUPPORT_PATCH" android:value="1" />
</application>
</manifest>

View File

@@ -0,0 +1,12 @@
package com.github.sisong;
public class ApkPatch{
static {
System.loadLibrary("apkpatch");
}
// return TPatchResult, 0 is ok; patchFilePath file created by ZipDiff;
public static native int patch(String oldApkPath,String patchFilePath,String outNewApkPath,
long maxUncompressMemory,String tempUncompressFilePath,int threadNum);
}

View File

@@ -0,0 +1,80 @@
package com.mogo.launcher.patch
import android.content.*
import android.text.TextUtils
import android.util.*
import com.github.sisong.*
import com.mogo.eagle.core.utilcode.util.AppUtils
import com.mogo.launcher.patch.utils.*
import java.io.File
internal object PatchManager {
private const val TAG = "PatchManager"
fun isPatchAccept(context: Context, expectSourceMd5: String, patchSize: Long): Boolean {
val apkMd5 = AppUtils.getAppApkMd5()
if (TextUtils.isEmpty(apkMd5)) {
return false
}
if (apkMd5 != expectSourceMd5) {
return false
}
return true
}
fun applyPatch(ctx: Context, patch: File, newApk: File): Boolean {
val oldApkPath = ctx.packageManager.getPackageInfo(ctx.packageName, 0).applicationInfo.sourceDir
if (TextUtils.isEmpty(oldApkPath)) {
throw AssertionError("旧的apk文件的文件路径为空.")
}
val oldApk = File(oldApkPath)
Log.d(TAG, "applyPatch -- 1 --:oldApk -> ${oldApk.absolutePath}, patch: -> ${patch.absolutePath}, newApk: ${newApk.absolutePath}")
if (!oldApk.exists()) {
throw AssertionError("旧的apk文件不存在, 文件所在路径:${oldApk.absolutePath}")
}
Log.d(TAG, "applyPatch -- 2 --")
if (!patch.exists()) {
throw AssertionError("差分包文件不存在,文件所在路径:${patch.absolutePath}")
}
Log.d(TAG, "applyPatch -- 3 --")
val newApkParent = newApk.parentFile
if (newApkParent != null && !newApkParent.exists()) {
val ret = newApkParent.mkdirs()
if (!ret) {
throw AssertionError("新包创建父目录失败")
}
}
Log.d(TAG, "applyPatch -- 4 --")
if (newApk.exists()) {
newApk.delete()
}
if (newApkParent == null) {
throw AssertionError("新包父目录为空")
}
Log.d(TAG, "applyPatch -- 5 --")
val tempFilePath = File(ctx.getExternalFilesDir(null), "temp/temp.patch")
if (tempFilePath.exists()) {
tempFilePath.delete()
}
tempFilePath.parentFile?.takeIf { !it.exists() }?.mkdirs()
val result = ApkPatch.patch(oldApk.absolutePath, patch.absolutePath, newApk.absolutePath, 4 * 1024 * 1024, "", 4)
Log.d(TAG, "applyPatch -- end ---: result: $result")
if (result != 0) {
throw AssertionError("文件合成失败")
}
return true
}
fun checkMd5ForMergedApk(mergedApk: File, expectNewApkMd5: String): Boolean {
if (!mergedApk.exists()) {
return false
}
val md5 = Md5Util.getMd5FromFile(mergedApk)
if (md5 != expectNewApkMd5) {
return false
}
return true
}
}

View File

@@ -0,0 +1,33 @@
package com.mogo.launcher.patch.provider
import android.content.*
import com.alibaba.android.arouter.facade.annotation.Route
import com.mogo.eagle.core.data.constants.MogoServicePaths
import com.mogo.eagle.core.function.api.patch.*
import com.mogo.launcher.patch.*
import java.io.*
@Route(path = MogoServicePaths.PATH_PATCH_UPGRADE)
class MoGoPatchProviderImpl : IMoGoPatchProvider {
override fun isPatchAccept(context: Context, expectOldApkMd5: String, patchSize: Long): Boolean {
return PatchManager.isPatchAccept(context, expectOldApkMd5, patchSize)
}
override fun applyPatch(context: Context, patch: File, newApk: File): Boolean {
return try {
PatchManager.applyPatch(context, patch, newApk)
} catch (t: Throwable) {
t.printStackTrace()
false
}
}
override fun checkMd5ForMergedApk(context: Context, mergedApk: File, expectNewApkMd5: String): Boolean {
return PatchManager.checkMd5ForMergedApk(mergedApk, expectNewApkMd5)
}
override fun init(context: Context?) { }
}

View File

@@ -0,0 +1,42 @@
package com.mogo.launcher.patch.utils
import java.io.*
import java.nio.channels.FileChannel.MapMode
import java.security.*
class Md5Util {
companion object {
/**
* 获取单个文件的MD5值
* @param file
* @return
* 解决首位0被省略问题
*/
fun getMd5FromFile(file: File): String? {
var stringbuffer: StringBuffer? = null
try {
val hexDigits = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
val input = FileInputStream(file)
val ch = input.channel
val byteBuffer = ch.map(MapMode.READ_ONLY, 0, file.length())
val digest = MessageDigest.getInstance("MD5")
digest.update(byteBuffer)
val bytes = digest.digest()
val n = bytes.size
stringbuffer = StringBuffer(2 * n)
for (l in 0 until n) {
val bt = bytes[l]
val c0 = hexDigits[bt.toInt() and 0xf0 shr 4]
val c1 = hexDigits[bt.toInt() and 0xf]
stringbuffer.append(c0)
stringbuffer.append(c1)
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return stringbuffer?.toString()
}
}
}

View File

@@ -0,0 +1,16 @@
package com.mogo.launcer.patch
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View File

@@ -212,4 +212,12 @@ object FunctionBuildConfig {
@Volatile
@JvmField
var unableLaunchAutopilotGear: Set<Chassis.GearPosition>? = null
/**
* 当前应用是否支持patch升级
*/
@Volatile
@JvmField
var isSupportPatchUpgrade = true
}

View File

@@ -98,4 +98,10 @@ public class MogoServicePaths {
@Keep
public static final String PATH_VISUAL_ANGLE = "/map/angle_change";
@Keep
public static final String PATH_PATCH_UPGRADE = "/patch/api";
public static final String PATH_UPGRADE_TYPE_API = "/upgrade/type/api";
}

View File

@@ -1,6 +1,9 @@
package com.mogo.eagle.core.data.deva.bindingcar;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.Objects;
/**
* @author lixiaopeng
@@ -19,6 +22,9 @@ public class AppInfo implements Serializable {
private String endTime;
private String appFileName;
@SerializedName("patchInfoView")
public PatchInfo patchInfo;
public String getAppUrl() {
return appUrl;
}
@@ -111,7 +117,155 @@ public class AppInfo implements Serializable {
", installType='" + installType + '\'' +
", beginTime='" + beginTime + '\'' +
", endTime='" + endTime + '\'' +
", patchInfo=" + patchInfo +
", appFileName='" + appFileName + '\'' +
'}';
}
public static class PatchInfo implements Serializable {
private Integer sourceId;
private String sourceMd5;
private String sourceVersion;
private Integer targetId;
private String targetMd5;
private String targetVersion;
private String patchName;
private String patchLocalPath;
private String patchMd5;
private String patchDownloadUrl;
private Integer patchSize;
private Integer patchStatus;
public Integer getSourceId() {
return sourceId;
}
public void setSourceId(Integer sourceId) {
this.sourceId = sourceId;
}
public String getSourceMd5() {
return sourceMd5;
}
public void setSourceMd5(String sourceMd5) {
this.sourceMd5 = sourceMd5;
}
public String getSourceVersion() {
return sourceVersion;
}
public void setSourceVersion(String sourceVersion) {
this.sourceVersion = sourceVersion;
}
public Integer getTargetId() {
return targetId;
}
public void setTargetId(Integer targetId) {
this.targetId = targetId;
}
public String getTargetMd5() {
return targetMd5;
}
public void setTargetMd5(String targetMd5) {
this.targetMd5 = targetMd5;
}
public String getTargetVersion() {
return targetVersion;
}
public void setTargetVersion(String targetVersion) {
this.targetVersion = targetVersion;
}
public String getPatchName() {
return patchName;
}
public void setPatchName(String patchName) {
this.patchName = patchName;
}
public String getPatchLocalPath() {
return patchLocalPath;
}
public void setPatchLocalPath(String patchLocalPath) {
this.patchLocalPath = patchLocalPath;
}
public String getPatchMd5() {
return patchMd5;
}
public void setPatchMd5(String patchMd5) {
this.patchMd5 = patchMd5;
}
public String getPatchDownloadUrl() {
return patchDownloadUrl;
}
public void setPatchDownloadUrl(String patchDownloadUrl) {
this.patchDownloadUrl = patchDownloadUrl;
}
public Integer getPatchSize() {
return patchSize;
}
public Integer getPatchStatus() {
return patchStatus;
}
@Override
public String toString() {
return "PatchInfo{" +
"sourceId=" + sourceId +
", sourceMd5='" + sourceMd5 + '\'' +
", sourceVersion='" + sourceVersion + '\'' +
", targetId=" + targetId +
", targetMd5='" + targetMd5 + '\'' +
", targetVersion='" + targetVersion + '\'' +
", patchName='" + patchName + '\'' +
", patchLocalPath='" + patchLocalPath + '\'' +
", patchMd5='" + patchMd5 + '\'' +
", patchDownloadUrl='" + patchDownloadUrl + '\'' +
", patchSize=" + patchSize +
", patchStatus=" + patchStatus +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PatchInfo patchInfo = (PatchInfo) o;
return patchMd5.equals(patchInfo.patchMd5);
}
@Override
public int hashCode() {
return Objects.hash(patchMd5);
}
}
}

View File

@@ -12,6 +12,8 @@ import com.mogo.eagle.core.data.deva.scene.SceneModule
import com.mogo.eagle.core.data.deva.scene.SceneTAG
import com.mogo.eagle.core.data.msgbox.MsgBoxBean
import com.mogo.eagle.core.function.api.devatools.apm.*
import com.mogo.eagle.core.function.api.devatools.download.*
import com.mogo.eagle.core.function.api.upgrade.*
/**
* 开发套件工具接口
@@ -118,7 +120,7 @@ interface IDevaToolsProvider : IProvider {
/**
* 下载指定包
*/
fun downLoadPackage(downloadKey: String, downloadUrl: String)
fun downLoadPackage(type: DownloadType, downloadKey: String, downloadUrl: String)
/**
* 更新下载进度
@@ -195,4 +197,9 @@ interface IDevaToolsProvider : IProvider {
fun dockerVersion(dockerVersion: String?)
fun apmEnvProvider(): IApmEnvProvider
/**
* 升级相关状态查询
*/
fun upgradeProvider(): IMoGoUpgradeProvider?
}

View File

@@ -0,0 +1,7 @@
package com.mogo.eagle.core.function.api.devatools.download
enum class DownloadType {
APK,
OBU,
PATCH
}

View File

@@ -0,0 +1,35 @@
package com.mogo.eagle.core.function.api.patch
import android.content.Context
import com.alibaba.android.arouter.facade.template.IProvider
import java.io.*
interface IMoGoPatchProvider : IProvider {
/**
* @param context 上下文环境
* @param expectOldApkMd5 服务端返回的旧apk文件的md5
* @param patchSize patch文件大小
* @return true: 本地apk与服务端旧apk版本的md5文件一致; false: 本地apk与服务端旧apk版本md5不一致
*/
fun isPatchAccept(context: Context, expectOldApkMd5: String, patchSize: Long): Boolean
/**
* @param context 上下文环境
* @param patch 已下载文件的patch路径
* @param newApk 合成后文件的文件路径
* @return true: patch与旧apk文件合成成功; false: patch与旧apk文件合成失败
*/
fun applyPatch(context: Context, patch: File, newApk: File): Boolean
/**
* @param context 上下文环境
* @param mergedApk patch合成后的文件路径
* @param expectNewApkMd5 期望合成后文件的md5值
* @return true: md5校验成功; false: md5校验失败
*/
fun checkMd5ForMergedApk(context: Context, mergedApk: File, expectNewApkMd5: String): Boolean
}

View File

@@ -0,0 +1,52 @@
package com.mogo.eagle.core.function.api.upgrade
import com.alibaba.android.arouter.facade.template.IProvider
interface IMoGoUpgradeProvider: IProvider {
suspend fun recordUpgradeRecord(newVersion: String,newMd5: String?, type: Int)
suspend fun recordDownloadStart()
suspend fun recordDownloadFailed(error: String)
suspend fun recordDownloadSuccess()
suspend fun recordInstallStart()
suspend fun recordSourceMd5CheckStart()
suspend fun recordSourceMd5CheckFailed(error: String)
suspend fun recordSourceMd5CheckSuccess()
suspend fun recordInstallApplyPatchStart()
suspend fun recordInstallApplyPatchFailed(error: String)
suspend fun recordInstallApplyPatchSuccess()
suspend fun recordTargetMd5CheckStart()
suspend fun recordTargetMd5CheckFailed(error: String)
suspend fun recordTargetMd5CheckSuccess()
suspend fun recordInstallFailed(code: Int, error: String)
suspend fun isUpgradeSuccessByPatch(): Boolean
suspend fun isUpgradeSuccessByFull(): Boolean
suspend fun getFullUpgradeFailedReason(): Map<String,List<Pair<Int, String>>>?
suspend fun getPatchUpgradeFailedReason(): Map<String,List<Pair<Int, String>>>?
suspend fun removeRecordByTargetVersion(version: String)
suspend fun removeRecordBySourceVersion(version: String)
suspend fun hasUpgradeRecord(): Boolean
suspend fun isNeedGoPatchUpgrade(): Boolean
}

View File

@@ -14,6 +14,8 @@ import com.mogo.eagle.core.data.deva.scene.SceneTAG
import com.mogo.eagle.core.data.msgbox.MsgBoxBean
import com.mogo.eagle.core.function.api.devatools.IDevaToolsProvider
import com.mogo.eagle.core.function.api.devatools.apm.*
import com.mogo.eagle.core.function.api.devatools.download.*
import com.mogo.eagle.core.function.api.upgrade.*
import com.mogo.eagle.core.function.call.base.CallerBase
import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils
@@ -158,8 +160,8 @@ object CallerDevaToolsManager {
/**
* 下载指定包
*/
fun downLoadPackage(downloadKey: String, downloadUrl: String) {
devaToolsProviderApi?.downLoadPackage(downloadKey, downloadUrl)
fun downLoadPackage(type: DownloadType, downloadKey: String, downloadUrl: String) {
devaToolsProviderApi?.downLoadPackage(type, downloadKey, downloadUrl, )
}
/**
@@ -248,4 +250,6 @@ object CallerDevaToolsManager {
fun queryObuUpgrade(obuVersionName: String) {
devaToolsProviderApi?.queryObuUpgrade(obuVersionName)
}
fun upgradeProvider(): IMoGoUpgradeProvider? = devaToolsProviderApi?.upgradeProvider()
}

View File

@@ -0,0 +1,61 @@
package com.mogo.eagle.core.function.call.patch
import android.content.*
import com.mogo.eagle.core.data.constants.MogoServicePaths
import com.mogo.eagle.core.data.deva.bindingcar.AppInfo.PatchInfo
import com.mogo.eagle.core.function.api.patch.*
import com.mogo.eagle.core.function.call.base.CallerBase
import java.io.*
import java.util.concurrent.CopyOnWriteArraySet
object CallerPatchManager {
private val patchInfo by lazy { CopyOnWriteArraySet<PatchInfo>() }
private val provider: IMoGoPatchProvider? by lazy {
CallerBase.getApiInstance(IMoGoPatchProvider::class.java, MogoServicePaths.PATH_PATCH_UPGRADE)
}
/**
* @param context 上下文环境
* @param expectOldApkMd5 服务端返回的旧apk文件的md5
* @return true: 本地apk与服务端旧apk版本的md5文件一致; false: 本地apk与服务端旧apk版本md5不一致
*/
fun isPatchAccept(context: Context, expectOldApkMd5: String, patchSize: Long): Boolean {
return provider?.isPatchAccept(context, expectOldApkMd5, patchSize) ?: false
}
/**
* @param context 上下文环境
* @param patch 已下载文件的patch路径
* @param newApk 合成后文件的文件路径
* @return true: patch与旧apk文件合成成功; false: patch与旧apk文件合成失败
*/
fun applyPatch(context: Context, patch: File, newApk: File): Boolean {
return provider?.applyPatch(context, patch, newApk) ?: false
}
/**
* @param context 上下文环境
* @param mergedApk patch合成后的文件路径
* @param expectNewApkMd5 期望合成后文件的md5值
* @return true: md5校验成功; false: md5校验失败
*/
fun checkMd5ForMergedApk(context: Context, mergedApk: File, expectNewApkMd5: String): Boolean {
return provider?.checkMd5ForMergedApk(context, mergedApk, expectNewApkMd5) ?: false
}
fun addPatchInfo(info: PatchInfo) {
patchInfo += info
}
fun getPatchInfoByUrl(patchUrl: String): PatchInfo? = patchInfo.find { it.patchDownloadUrl == patchUrl }
fun removePatchInfoByUrl(patchUrl: String) {
try {
patchInfo.remove(patchInfo.find { it.patchDownloadUrl == patchUrl })
} catch (t: Throwable) {
t.printStackTrace()
}
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Provides access to the installable APK in the package installer demo. -->
<files-path name="all" path="." />
</paths>

View File

@@ -37,5 +37,11 @@
</provider>
<receiver android:name=".NetworkUtils$NetworkChangedReceiver" />
<receiver android:name=".AppInstallReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.mogo.launcher.f.action.SESSION_API_PACKAGE_INSTALLED" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@@ -73,6 +73,7 @@ public class DownloadService implements InitThread.InitCallBack, DownloadCallBac
FileBean fileBean = (FileBean) intent.getSerializableExtra("FileBean");
if (fileBean == null) {
Log.e(DOWN_LOAD_TAG, "onStartCommand bean is null");
return;
}

View File

@@ -12,6 +12,7 @@ import com.mogo.eagle.core.utilcode.breakpoint.db.dao.ThreadDao;
import com.mogo.eagle.core.utilcode.breakpoint.db.impl.ThreadDaoImpl;
import com.mogo.eagle.core.utilcode.breakpoint.event.DownloadData;
import com.mogo.eagle.core.utilcode.breakpoint.services.DownloadService;
import com.mogo.eagle.core.utilcode.util.UiThreadHandler;
import java.util.ArrayList;
import java.util.List;
@@ -73,7 +74,7 @@ public class DownloadTask implements DownloadCallBack {
DownloadService.executorService.execute(downloadThread);
downloadThreads.add(downloadThread);
}
downloadCallBack.startDownload(fileBean.getUrl());
UiThreadHandler.post(() -> downloadCallBack.startDownload(fileBean.getUrl()));
Log.d(DOWN_LOAD_TAG, " 开始下载:" + finishedProgress);
}
@@ -100,7 +101,7 @@ public class DownloadTask implements DownloadCallBack {
@Override
public void startDownload(String url) {
downloadCallBack.startDownload(url);
UiThreadHandler.post(() -> downloadCallBack.startDownload(url));
}
@Override
@@ -108,7 +109,7 @@ public class DownloadTask implements DownloadCallBack {
//保存下载进度到数据库
Log.d(DOWN_LOAD_TAG, "保存数据:" + threadBean.toString());
dao.updateThread(threadBean.getUrl(), threadBean.getId(), threadBean.getFinished());
downloadCallBack.pauseCallBack(url,threadBean);
UiThreadHandler.post(() -> downloadCallBack.pauseCallBack(url,threadBean));
}
private long curTime = 0;
@@ -117,18 +118,17 @@ public class DownloadTask implements DownloadCallBack {
public void progressCallBack(String url, int length) {
finishedProgress += length;
//每500毫秒发送刷新进度事件
Log.d(DOWN_LOAD_TAG, "process:" + finishedProgress);
if (System.currentTimeMillis() - curTime > 500 || finishedProgress == fileBean.getLength()) {
int progress = (int) (finishedProgress * 1.0 / fileBean.getLength() * 100);
// Log.d(DOWN_LOAD_TAG, "DownloadTask ----length = " + length + " ---progress = " + progress);
Log.d(DOWN_LOAD_TAG, "DownloadTask ----length = " + length + " ---progress = " + progress);
fileBean.setFinished(finishedProgress);
DownloadData downloadData = new DownloadData();
downloadData.setUrl(fileBean.getUrl());
downloadData.setProgress(progress);
downloadData.setLength(fileBean.getLength());
downloadData.setMsg("下载进度回调");
downloadCallBack.progressCallBack(url,progress);
UiThreadHandler.post(() -> downloadCallBack.progressCallBack(url,progress));
curTime = System.currentTimeMillis();
}
}
@@ -151,8 +151,7 @@ public class DownloadTask implements DownloadCallBack {
downloadData.setUrl(fileBean.getUrl());
downloadData.setMsg("下载完成");
downloadData.setFilePath(fileBean.getSavePath() + fileBean.getFileName());
downloadCallBack.threadDownLoadFinished(url,threadBean);
UiThreadHandler.post(() -> downloadCallBack.threadDownLoadFinished(url,threadBean));
}
}

View File

@@ -10,6 +10,8 @@ import com.mogo.eagle.core.utilcode.breakpoint.bean.FileBean;
import com.mogo.eagle.core.utilcode.breakpoint.bean.ThreadBean;
import com.mogo.eagle.core.utilcode.breakpoint.callback.DownloadCallBack;
import com.mogo.eagle.core.utilcode.breakpoint.event.DownloadData;
import com.mogo.eagle.core.utilcode.util.ThreadUtils;
import com.mogo.eagle.core.utilcode.util.UiThreadHandler;
import java.io.File;
import java.io.InputStream;
@@ -63,21 +65,22 @@ public class DownloadThread extends Thread {
while ((len = inputStream.read(bytes))!=-1){
raf.write(bytes,0,len);
//将加载的进度回调出去
callback.progressCallBack(this.fileBean.getUrl(),len);
callback.progressCallBack(fileBean.getUrl(), len);
//保存进度
threadBean.setFinished(threadBean.getFinished()+len);
//在下载暂停的时候将下载进度保存到数据库
if(isPause){
callback.pauseCallBack(this.fileBean.getUrl(),threadBean);
UiThreadHandler.post(() -> callback.pauseCallBack(this.fileBean.getUrl(),threadBean));
return;
}
}
//下载完成
callback.threadDownLoadFinished(this.fileBean.getUrl(),threadBean);
UiThreadHandler.post(() -> callback.threadDownLoadFinished(this.fileBean.getUrl(),threadBean));
}
} catch (Exception e) {
e.printStackTrace();
Log.e(DOWN_LOAD_TAG, "error: " + e.getMessage());
DownloadData downloadData = new DownloadData();
downloadData.setUrl(fileBean.getUrl());
downloadData.setMsg(e.getMessage());
@@ -90,6 +93,7 @@ public class DownloadThread extends Thread {
connection.disconnect();
}catch (Exception e){
e.printStackTrace();
Log.e(DOWN_LOAD_TAG, "error2: " + e.getMessage());
DownloadData downloadData = new DownloadData();
downloadData.setUrl(fileBean.getUrl());
downloadData.setMsg(e.getMessage());

View File

@@ -0,0 +1,164 @@
package com.mogo.eagle.core.utilcode.util
import android.annotation.*
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.content.pm.PackageInstaller
import android.content.pm.PackageInstaller.Session
import android.content.pm.PackageInstaller.SessionCallback
import android.content.pm.PackageInstaller.SessionParams
import android.os.*
import android.util.Log
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.lang.ref.WeakReference
import java.util.concurrent.ConcurrentHashMap
class ApkInstaller {
companion object {
private const val TAG = "ApkInstaller"
const val INSTALL_CODE_INVLID = -2
@JvmStatic
fun installApp(context: Context, apkFile: File, block: ((Int, String) -> Unit)?) {
if (!apkFile.exists()) {
val msg = "要安装的apk文件不存在"
block?.invoke(INSTALL_CODE_INVLID, msg)
return
}
val isApk = try {
context.packageManager.getPackageArchiveInfo(apkFile.absolutePath, 0)?.versionName != null
} catch (t: Throwable) {
false
}
if (!isApk) {
val msg = "文件: ${apkFile.absolutePath}, 不是一个有效的安装包"
block?.invoke(INSTALL_CODE_INVLID, msg)
return
}
installApp(context, FileInputStream(apkFile), block)
}
@JvmStatic
fun installApp(context: Context, input: InputStream, block: ((Int, String) -> Unit)?) {
ProcessLifecycleOwner.get().lifecycleScope.launch(Dispatchers.IO) {
var session: Session? = null
try {
val installer = context.packageManager.packageInstaller
installer.registerSessionCallback(object : SessionCallback() {
override fun onCreated(sessionId: Int) {
Log.d(TAG, "onCreate: sessionId -> $sessionId")
}
override fun onBadgingChanged(sessionId: Int) {
Log.d(TAG, "onBadgingChanged: sessionId -> $sessionId")
}
override fun onActiveChanged(sessionId: Int, active: Boolean) {
Log.d(TAG, "onActiveChanged: sessionId -> $sessionId, active: $active")
}
override fun onProgressChanged(sessionId: Int, progress: Float) {
Log.d(TAG, "onProgressChanged: sessionId -> $sessionId, process: $progress")
}
override fun onFinished(sessionId: Int, success: Boolean) {
Log.d(TAG, "onFinished: sessionId -> $sessionId, success: $success")
}
}, Handler(Looper.getMainLooper()))
val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
val sessionId = installer.createSession(params)
session = installer.openSession(sessionId)
val output = session.openWrite("install.apk", 0, -1)
output.use { o ->
input.copyTo(o)
}
withContext(Dispatchers.Main) {
session.commit(createIntent(context, sessionId))
}
block?.also {
AppInstallReceiver.listeners[sessionId] = WeakReference(it)
}
} catch (t: Throwable) {
block?.invoke(-2, t.message ?: "未知异常")
session?.close()
}
}
}
@SuppressLint("UnspecifiedImmutableFlag")
private fun createIntent(context: Context, sessionId: Int): IntentSender {
val intent = Intent(context, AppInstallReceiver::class.java)
intent.action = AppInstallReceiver.INTENT_ACTION_SESSION_INSTALL
intent.putExtra(AppInstallReceiver.INTENT_EXTRA_SESSION_ID, sessionId)
return PendingIntent.getBroadcast(context, sessionId, intent, PendingIntent.FLAG_UPDATE_CURRENT).intentSender
}
}
}
class AppInstallReceiver: BroadcastReceiver() {
companion object {
const val TAG = "AppInstallReceiver"
const val INTENT_ACTION_SESSION_INSTALL = "com.mogo.launcher.f.action.SESSION_API_PACKAGE_INSTALLED"
const val INTENT_EXTRA_SESSION_ID = "intent.extra.session_id"
val listeners by lazy { ConcurrentHashMap<Int, WeakReference<((Int, String) -> Unit)>>() }
}
override fun onReceive(context: Context?, intent: Intent?) {
val c = context ?: return
val i = intent ?: return
val a = i.action ?: return
if (a == INTENT_ACTION_SESSION_INSTALL) {
val e = i.extras ?: return
val sessionId = e.getInt(INTENT_EXTRA_SESSION_ID, 0)
Log.d(TAG, "action -> $a, sessionId: $sessionId")
when(val status = e.getInt(PackageInstaller.EXTRA_STATUS)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val confirm = e.get(Intent.EXTRA_INTENT) as? Intent
if (confirm != null) {
Log.d(TAG, "confirm -> status:$status, sessionID: $sessionId")
val activityInfo = c.packageManager.resolveActivity(confirm, 0)?.activityInfo
if (activityInfo != null) {
confirm.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
c.startActivity(confirm)
} else {
notifyListeners(status, "action: ${confirm.action} 不存在")
}
} else {
notifyListeners(status, "需要用户确认的应用程序不存在")
}
}
PackageInstaller.STATUS_FAILURE_ABORTED,
PackageInstaller.STATUS_FAILURE,
PackageInstaller.STATUS_FAILURE_BLOCKED,
PackageInstaller.STATUS_FAILURE_CONFLICT,
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE,
PackageInstaller.STATUS_FAILURE_INVALID,
PackageInstaller.STATUS_FAILURE_STORAGE -> {
Log.d(TAG, "failed -> status:$status, sessionID: $sessionId")
notifyListeners(status, "安装失败: $status")
}
else -> {
Log.d(TAG, "other status:$status, sessionID: $sessionId")
notifyListeners(status, "未知状态")
}
}
}
}
private fun notifyListeners(code: Int, reason: String) {
listeners.values.forEach {
it.get()?.invoke(code, reason)
}
}
}

View File

@@ -27,6 +27,7 @@ import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* <pre>
@@ -40,6 +41,12 @@ public final class AppUtils {
private static final String MOGO_MAP_SDK_VERSION = "MAP_SDK_VERSION";
private static final AtomicReference<Integer> sVersionCode = new AtomicReference<>();
private static final AtomicReference<String> sVersionName = new AtomicReference<>();
private static final AtomicReference<String> sAppApkMd5 = new AtomicReference<>();
private AppUtils() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
@@ -180,6 +187,12 @@ public final class AppUtils {
Utils.getApp().startActivity(installAppIntent);
}
public static void installApp(Activity activity, File apk, int requestCode) {
Intent installAppIntent = UtilsBridge.getInstallAppIntent(apk);
if (installAppIntent == null) return;
activity.startActivityForResult(installAppIntent, requestCode);
}
/**
* Install the app.
* <p>Target APIs greater than 25 must hold
@@ -556,6 +569,27 @@ public final class AppUtils {
}
}
public static String getAppApkMd5() {
if (sAppApkMd5.get() != null) {
return sAppApkMd5.get();
}
try {
String apkPath = Utils.getApp().getPackageManager().getPackageInfo(Utils.getApp().getPackageName(), 0).applicationInfo.sourceDir;
File apk = new File(apkPath);
if (apk.exists()) {
String ret = Md5Util.getMd5FromFile(apk);
if (!TextUtils.isEmpty(ret)) {
sAppApkMd5.set(ret);
}
return ret;
}
} catch (Throwable t) {
t.printStackTrace();
return "";
}
return "";
}
/**
* Return the application's version name.
*
@@ -575,10 +609,17 @@ public final class AppUtils {
@NonNull
public static String getAppVersionName(final String packageName) {
if (UtilsBridge.isSpace(packageName)) return "";
if (sVersionName.get() != null) {
return sVersionName.get();
}
try {
PackageManager pm = Utils.getApp().getPackageManager();
PackageInfo pi = pm.getPackageInfo(packageName, 0);
return pi == null ? "" : pi.versionName;
String s = pi == null ? "" : pi.versionName;
if (!TextUtils.isEmpty(s)) {
sVersionName.set(s);
}
return s;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return "";
@@ -602,10 +643,18 @@ public final class AppUtils {
*/
public static int getAppVersionCode(final String packageName) {
if (UtilsBridge.isSpace(packageName)) return -1;
Integer oldVersionCode = sVersionCode.get();
if (oldVersionCode != null && oldVersionCode > 0) {
return oldVersionCode;
}
try {
PackageManager pm = Utils.getApp().getPackageManager();
PackageInfo pi = pm.getPackageInfo(packageName, 0);
return pi == null ? -1 : pi.versionCode;
int ret = pi == null ? -1 : pi.versionCode;
if (ret > 0) {
sVersionCode.set(ret);
}
return ret;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return -1;

View File

@@ -0,0 +1,43 @@
package com.mogo.eagle.core.utilcode.util
import java.io.*
import java.nio.channels.FileChannel.MapMode
import java.security.*
class Md5Util {
companion object {
/**
* 获取单个文件的MD5值
* @param file
* @return
* 解决首位0被省略问题
*/
@JvmStatic
fun getMd5FromFile(file: File): String? {
var stringbuffer: StringBuffer? = null
try {
val hexDigits = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
val input = FileInputStream(file)
val ch = input.channel
val byteBuffer = ch.map(MapMode.READ_ONLY, 0, file.length())
val digest = MessageDigest.getInstance("MD5")
digest.update(byteBuffer)
val bytes = digest.digest()
val n = bytes.size
stringbuffer = StringBuffer(2 * n)
for (l in 0 until n) {
val bt = bytes[l]
val c0 = hexDigits[bt.toInt() and 0xf0 shr 4]
val c1 = hexDigits[bt.toInt() and 0xf]
stringbuffer.append(c0)
stringbuffer.append(c1)
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return stringbuffer?.toString()
}
}
}

View File

@@ -21,10 +21,10 @@ public class HostConst {
public static final String CMDB_HOST = "http://eagle-mis.zhidaozhixing.com/eagleEye-mis/cmdbapi/";
public static final String HOST_DEV = "http://eagle-mis-a.zhidaozhixing.com";
public static final String HOST_DEV = "http://eagle-qa.zhidaozhixing.com";
public static final String HOST_RELEASE = "http://eagle-mis.zhidaozhixing.com";
public static final String HOST_EAGLE_QA = "http://eagle-dns-a.zhidaozhixing.com/";
public static final String HOST_EAGLE_QA = "http://eagle-dns-qa.zhidaozhixing.com/";
public static String getHost() {
String host = HOST_RELEASE;

View File

@@ -146,3 +146,6 @@ ADAS_DATA_LIB_CHILD_VERSION=.0
# 线程优化版本
THREAD_OPT_VERSION=4.0.0
# 是否支持patch升级
PATCH_UPGRADE_SUPPORT=true

View File

@@ -65,3 +65,5 @@ include ':OCH:mogo-och-taxi-passenger'
include ':OCH:mogo-och-noop'
include(':OCH:mogo-och-common-module')
include ':OCH:mogo-och-sweeper'
include ':core:function-impl:mogo-core-function-patch'
include ':core:function-impl:mogo-core-function-patch'