parent
0b3835362f
commit
3facefa1c1
@ -0,0 +1,66 @@ |
|||||||
|
package com.android.base.utils.debug |
||||||
|
|
||||||
|
import com.android.base.utils.BaseUtils |
||||||
|
import com.android.base.utils.android.cache.SpCache |
||||||
|
import com.android.base.utils.common.ifNonNull |
||||||
|
import com.android.base.utils.common.ifNull |
||||||
|
import com.android.base.utils.common.otherwise |
||||||
|
|
||||||
|
|
||||||
|
object EnvironmentContext { |
||||||
|
|
||||||
|
private val spCache = SpCache(BaseUtils.getAppContext().packageName, false) |
||||||
|
|
||||||
|
private val envMap = HashMap<String, MutableList<Environment>>() |
||||||
|
|
||||||
|
private fun addEnv(category: String, env: Environment) { |
||||||
|
envMap[category].ifNonNull { |
||||||
|
add(env) |
||||||
|
Unit |
||||||
|
} otherwise { |
||||||
|
envMap[category] = mutableListOf<Environment>().apply { add(env) } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun startEdit(adder: EnvironmentAdder.() -> Unit) { |
||||||
|
adder(object : EnvironmentAdder { |
||||||
|
override fun add(category: String, env: Environment) { |
||||||
|
addEnv(category, env) |
||||||
|
} |
||||||
|
}) |
||||||
|
endAdding() |
||||||
|
} |
||||||
|
|
||||||
|
private fun endAdding() { |
||||||
|
envMap.forEach { (category, list) -> |
||||||
|
val url = spCache.getString(category, "") |
||||||
|
list.find { |
||||||
|
url == it.url |
||||||
|
}.ifNull { |
||||||
|
spCache.putString(category, list[0].url) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal fun allCategory(): Map<String, List<Environment>> { |
||||||
|
return envMap |
||||||
|
} |
||||||
|
|
||||||
|
internal fun select(category: String, env: Environment) { |
||||||
|
spCache.putString(category, env.url) |
||||||
|
} |
||||||
|
|
||||||
|
fun selected(category: String): Environment { |
||||||
|
val url = spCache.getString(category, "") |
||||||
|
return envMap[category]?.find { |
||||||
|
url == it.url |
||||||
|
} ?: throw NullPointerException("no selected Environment with category: $category") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
interface EnvironmentAdder { |
||||||
|
fun add(category: String, env: Environment) |
||||||
|
} |
||||||
|
|
||||||
|
class Environment(val name: String, val url: String) |
@ -0,0 +1,45 @@ |
|||||||
|
package com.android.base.utils.debug |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import com.android.base.R |
||||||
|
import com.android.base.app.fragment.BaseFragment |
||||||
|
import com.android.base.utils.android.views.visibleOrGone |
||||||
|
import kotlinx.android.synthetic.main.base_debug_environment.* |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Ztiany |
||||||
|
* Email: ztiany3@gmail.com |
||||||
|
* Date : 2019-08-13 19:17 |
||||||
|
*/ |
||||||
|
class EnvironmentConfigFragment : BaseFragment() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
private const val SHOW_TITLE = "show_title" |
||||||
|
|
||||||
|
fun newInstance(showTitle: Boolean) = EnvironmentConfigFragment().apply { |
||||||
|
arguments = Bundle().apply { |
||||||
|
putBoolean(SHOW_TITLE, showTitle) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun provideLayout() = R.layout.base_debug_environment |
||||||
|
|
||||||
|
override fun onViewPrepared(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewPrepared(view, savedInstanceState) |
||||||
|
|
||||||
|
baseToolbarDebug.visibleOrGone(arguments?.getBoolean(SHOW_TITLE, false) ?: false) |
||||||
|
|
||||||
|
val allCategory = EnvironmentContext.allCategory() |
||||||
|
allCategory.forEach { (category, list) -> |
||||||
|
val environmentItemLayout = EnvironmentItemLayout(requireContext()) |
||||||
|
environmentItemLayout.bindEnvironmentList(category, list) |
||||||
|
baseLlDebugHostContent.addView(environmentItemLayout, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -0,0 +1,56 @@ |
|||||||
|
package com.android.base.utils.debug |
||||||
|
|
||||||
|
import android.annotation.SuppressLint |
||||||
|
import android.content.Context |
||||||
|
import android.util.AttributeSet |
||||||
|
import android.view.View |
||||||
|
import android.widget.LinearLayout |
||||||
|
import androidx.appcompat.app.AlertDialog |
||||||
|
import com.android.base.R |
||||||
|
import com.android.base.utils.android.views.dip |
||||||
|
import com.android.base.utils.android.views.setPaddingAll |
||||||
|
import kotlinx.android.synthetic.main.base_debug_environment_item.view.* |
||||||
|
|
||||||
|
class EnvironmentItemLayout @JvmOverloads constructor( |
||||||
|
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 |
||||||
|
) : LinearLayout(context, attrs, defStyleAttr) { |
||||||
|
|
||||||
|
init { |
||||||
|
orientation = VERTICAL |
||||||
|
setPaddingAll(dip(10)) |
||||||
|
View.inflate(context, R.layout.base_debug_environment_item, this) |
||||||
|
} |
||||||
|
|
||||||
|
private lateinit var list: List<Environment> |
||||||
|
private lateinit var categoryName: String |
||||||
|
|
||||||
|
fun bindEnvironmentList(categoryName: String, list: List<Environment>) { |
||||||
|
this.categoryName = categoryName |
||||||
|
this.list = list |
||||||
|
baseTvDebugHostName.text = categoryName |
||||||
|
|
||||||
|
showSelectedValue(EnvironmentContext.selected(categoryName)) |
||||||
|
|
||||||
|
baseBtnDebugSwitch.setOnClickListener { |
||||||
|
showSwitchDialog() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n") |
||||||
|
private fun showSelectedValue(selected: Environment) { |
||||||
|
baseTvDebugHostValue.text = "${selected.name}:${selected.url}" |
||||||
|
} |
||||||
|
|
||||||
|
private fun showSwitchDialog() { |
||||||
|
val first = list.indexOf(EnvironmentContext.selected(categoryName)) |
||||||
|
|
||||||
|
AlertDialog.Builder(context) |
||||||
|
.setSingleChoiceItems(list.map { it.name }.toTypedArray(), first) { dialog, which -> |
||||||
|
dialog.dismiss() |
||||||
|
val env = list[which] |
||||||
|
showSelectedValue(env) |
||||||
|
EnvironmentContext.select(categoryName, env) |
||||||
|
}.show() |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,137 @@ |
|||||||
|
package com.android.base.utils.upgrade; |
||||||
|
|
||||||
|
import com.android.base.utils.common.Files; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.FileOutputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
|
||||||
|
import okhttp3.OkHttpClient; |
||||||
|
import okhttp3.Request; |
||||||
|
import okhttp3.Response; |
||||||
|
import okhttp3.ResponseBody; |
||||||
|
|
||||||
|
import static com.blankj.utilcode.util.CloseUtils.closeIOQuietly; |
||||||
|
|
||||||
|
/** |
||||||
|
* 下载APK |
||||||
|
* |
||||||
|
* @author Ztiany |
||||||
|
* Email: 1169654504@qq.com |
||||||
|
* Date : 2017-01-04 10:40 |
||||||
|
*/ |
||||||
|
final class ApkDownloader { |
||||||
|
|
||||||
|
private String mUrl; |
||||||
|
private String mDigitalAbstract; |
||||||
|
private ApkDownloaderListener mApkDownloaderListener; |
||||||
|
private File mDesFile; |
||||||
|
private File mTempDesFile; |
||||||
|
private static final String TEMP_MASK = "temp_%s"; |
||||||
|
private long mNotifyTime = 0; |
||||||
|
|
||||||
|
|
||||||
|
ApkDownloader(String url, String desFilePath, String digitalAbstract, ApkDownloaderListener apkDownloaderListener) { |
||||||
|
mUrl = url; |
||||||
|
mDigitalAbstract = digitalAbstract; |
||||||
|
mApkDownloaderListener = apkDownloaderListener; |
||||||
|
mDesFile = new File(desFilePath); |
||||||
|
String name = mDesFile.getName(); |
||||||
|
mTempDesFile = new File(mDesFile.getParent(), String.format(TEMP_MASK, name)); |
||||||
|
} |
||||||
|
|
||||||
|
void start() { |
||||||
|
OkHttpClient.Builder builder = new OkHttpClient.Builder(); |
||||||
|
builder.interceptors().clear(); |
||||||
|
OkHttpClient okHttpClient = builder.build(); |
||||||
|
|
||||||
|
Request request = new Request.Builder() |
||||||
|
.url(mUrl) |
||||||
|
.build(); |
||||||
|
|
||||||
|
ResponseBody body; |
||||||
|
|
||||||
|
try { |
||||||
|
Response response = okHttpClient.newCall(request).execute(); |
||||||
|
if (response.isSuccessful()) { |
||||||
|
body = response.body(); |
||||||
|
} else { |
||||||
|
mApkDownloaderListener.onFail(new RuntimeException("网络错误")); |
||||||
|
return; |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
mApkDownloaderListener.onFail(e); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
//检查body
|
||||||
|
if (body == null) { |
||||||
|
mApkDownloaderListener.onFail(new RuntimeException("网络错误")); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (mDesFile.exists() && mDesFile.length() == body.contentLength() && AppUpgradeChecker.upgradeInteractor.checkApkFile(mDesFile, mDigitalAbstract)) { |
||||||
|
mApkDownloaderListener.onSuccess(mDesFile); |
||||||
|
} else { |
||||||
|
Files.makeParentPath(mDesFile); |
||||||
|
Files.makeParentPath(mTempDesFile); |
||||||
|
boolean success = startDownload(body); |
||||||
|
if (success) { |
||||||
|
boolean renameToSuccess = mTempDesFile.renameTo(mDesFile); |
||||||
|
if (renameToSuccess) { |
||||||
|
if (AppUpgradeChecker.upgradeInteractor.checkApkFile(mDesFile, mDigitalAbstract)) { |
||||||
|
mApkDownloaderListener.onSuccess(mDesFile); |
||||||
|
} else { |
||||||
|
mApkDownloaderListener.onFail(new RuntimeException("数字摘要对比失败")); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private boolean startDownload(ResponseBody body) { |
||||||
|
InputStream is = null; |
||||||
|
FileOutputStream fos = null; |
||||||
|
try { |
||||||
|
byte[] buf = new byte[1024 * 2];//缓冲器
|
||||||
|
int len;//每次读取的长度
|
||||||
|
assert body != null; |
||||||
|
final long total = body.contentLength();//总的长度
|
||||||
|
is = body.byteStream(); |
||||||
|
long sum = 0; |
||||||
|
fos = new FileOutputStream(mTempDesFile); |
||||||
|
|
||||||
|
while ((len = is.read(buf)) != -1) { |
||||||
|
sum += len; |
||||||
|
fos.write(buf, 0, len); |
||||||
|
if ((System.currentTimeMillis() - mNotifyTime) >= 300) { |
||||||
|
mNotifyTime = System.currentTimeMillis(); |
||||||
|
mApkDownloaderListener.onProgress(total, sum); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
mApkDownloaderListener.onProgress(total, total); |
||||||
|
fos.flush(); |
||||||
|
return true; |
||||||
|
} catch (IOException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
mApkDownloaderListener.onFail(e); |
||||||
|
return false; |
||||||
|
} finally { |
||||||
|
closeIOQuietly(is, fos); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
interface ApkDownloaderListener { |
||||||
|
|
||||||
|
void onProgress(long total, long progress); |
||||||
|
|
||||||
|
void onSuccess(File desFile); |
||||||
|
|
||||||
|
void onFail(Exception e); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,202 @@ |
|||||||
|
package com.android.base.utils.upgrade |
||||||
|
|
||||||
|
import android.app.Activity |
||||||
|
import android.content.BroadcastReceiver |
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.content.IntentFilter |
||||||
|
import androidx.lifecycle.LiveData |
||||||
|
import androidx.lifecycle.MutableLiveData |
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager |
||||||
|
import com.android.base.app.Sword |
||||||
|
import com.android.base.data.State |
||||||
|
import com.android.base.rx.observeOnUI |
||||||
|
import com.android.base.utils.BaseUtils |
||||||
|
import com.android.base.utils.android.XAppUtils |
||||||
|
import com.app.base.upgrade.UpgradeInteractor |
||||||
|
import com.blankj.utilcode.util.AppUtils |
||||||
|
import com.blankj.utilcode.util.Utils |
||||||
|
import timber.log.Timber |
||||||
|
import java.io.File |
||||||
|
|
||||||
|
object AppUpgradeChecker { |
||||||
|
|
||||||
|
private var successOnce = false |
||||||
|
|
||||||
|
var isDownloading = false |
||||||
|
private set |
||||||
|
|
||||||
|
private var currentState = State.noChange<UpgradeInfo>() |
||||||
|
set(value) { |
||||||
|
field = value |
||||||
|
notifyStateChanged(value) |
||||||
|
} |
||||||
|
|
||||||
|
private fun notifyStateChanged(state: State<UpgradeInfo>) { |
||||||
|
innerLiveState.postValue(state) |
||||||
|
} |
||||||
|
|
||||||
|
private val innerLiveState = MutableLiveData<State<UpgradeInfo>>() |
||||||
|
|
||||||
|
val upgradeState: LiveData<State<UpgradeInfo>> |
||||||
|
get() = innerLiveState |
||||||
|
|
||||||
|
private var resultReceiver: ResultBroadcastReceiver? = null |
||||||
|
|
||||||
|
private val isUpgradeServiceRunning: Boolean |
||||||
|
get() = XAppUtils.isServiceRunning(BaseUtils.getAppContext(), UpgradeService::class.java.name) |
||||||
|
|
||||||
|
private lateinit var upgradeInfo: UpgradeInfo |
||||||
|
|
||||||
|
lateinit var upgradeInteractor: UpgradeInteractor |
||||||
|
|
||||||
|
fun installInteractor(upgradeInteractor: UpgradeInteractor) { |
||||||
|
AppUpgradeChecker.upgradeInteractor = upgradeInteractor |
||||||
|
} |
||||||
|
|
||||||
|
fun checkAppUpgrade(force: Boolean = false) { |
||||||
|
Timber.d("checkAppUpgrade-->currentState == $currentState") |
||||||
|
/*正在检查*/ |
||||||
|
if (currentState.isLoading) { |
||||||
|
return |
||||||
|
} |
||||||
|
/*已经检查过了*/ |
||||||
|
if (!force && successOnce) { |
||||||
|
return |
||||||
|
} |
||||||
|
/*正在下载*/ |
||||||
|
if (isUpgradeServiceRunning) { |
||||||
|
return |
||||||
|
} |
||||||
|
realCheck() |
||||||
|
} |
||||||
|
|
||||||
|
private fun realCheck() { |
||||||
|
currentState = State.loading() |
||||||
|
|
||||||
|
upgradeInteractor.checkUpgrade() |
||||||
|
.observeOnUI() |
||||||
|
.subscribe( |
||||||
|
{ upgradeInfo -> |
||||||
|
currentState = State.success(upgradeInfo) |
||||||
|
successOnce = true |
||||||
|
processUpdateInfo(upgradeInfo) |
||||||
|
}, |
||||||
|
{ |
||||||
|
currentState = State.error(it) |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
private fun processUpdateInfo(upgradeInfo: UpgradeInfo) { |
||||||
|
if (upgradeInfo.isNewVersion) { |
||||||
|
AppUpgradeChecker.upgradeInfo = upgradeInfo |
||||||
|
safeContext { |
||||||
|
upgradeInteractor.showUpgradeDialog(it, upgradeInfo, |
||||||
|
onCancel = { |
||||||
|
currentState = State.success() |
||||||
|
}, |
||||||
|
onConfirm = { |
||||||
|
doUpdate() |
||||||
|
}) |
||||||
|
} |
||||||
|
} else { |
||||||
|
currentState = State.success() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun doUpdate() { |
||||||
|
//show loading dialog |
||||||
|
safeContext { |
||||||
|
upgradeInteractor.showDownloadingDialog(it, upgradeInfo.isForce) |
||||||
|
} |
||||||
|
//register result receiver |
||||||
|
var receiver = resultReceiver |
||||||
|
if (receiver == null) { |
||||||
|
receiver = ResultBroadcastReceiver() |
||||||
|
resultReceiver = receiver |
||||||
|
LocalBroadcastManager.getInstance(BaseUtils.getAppContext()).registerReceiver(receiver, IntentFilter(UpgradeService.DOWNLOAD_APK_RESULT_ACTION)) |
||||||
|
} |
||||||
|
//start downloading |
||||||
|
isDownloading = true |
||||||
|
UpgradeService.start(BaseUtils.getAppContext(), upgradeInfo.downloadUrl, upgradeInfo.versionName, upgradeInfo.digitalAbstract, upgradeInfo.isForce) |
||||||
|
} |
||||||
|
|
||||||
|
private class ResultBroadcastReceiver : BroadcastReceiver() { |
||||||
|
override fun onReceive(context: Context, intent: Intent) { |
||||||
|
if (intent.action != UpgradeService.DOWNLOAD_APK_RESULT_ACTION) { |
||||||
|
return |
||||||
|
} |
||||||
|
when (intent.getIntExtra(UpgradeService.NOTIFY_TYPE_KEY, -1)) { |
||||||
|
UpgradeService.NOTIFY_TYPE_SUCCESS -> processOnDownloadingFileSuccessful(intent) |
||||||
|
UpgradeService.NOTIFY_TYPE_FAILED -> processOnDownloadingFileFailed() |
||||||
|
UpgradeService.NOTIFY_TYPE_PROGRESS -> { |
||||||
|
val total = intent.getLongExtra(UpgradeService.TOTAL_PROGRESS_KEY, 0) |
||||||
|
val progress = intent.getLongExtra(UpgradeService.PROGRESS_KEY, 0) |
||||||
|
upgradeInteractor.onProgress(total, progress) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun processOnDownloadingFileSuccessful(intent: Intent) { |
||||||
|
isDownloading = false |
||||||
|
//start installing |
||||||
|
val apkFile = intent.getSerializableExtra(UpgradeService.APK_FILE_KEY) as File |
||||||
|
startInstall(apkFile) |
||||||
|
//dismiss download dialog |
||||||
|
upgradeInteractor.dismissDownloadingDialog() |
||||||
|
// if it is force upgrade, we show a no cancelable dialog to make user have to install the new apk. |
||||||
|
safeContext { |
||||||
|
showInstallTipsDialog(it, apkFile) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun showInstallTipsDialog(topActivity: Activity, apkFile: File) { |
||||||
|
upgradeInteractor.showInstallTipsDialog(topActivity, upgradeInfo.isForce, |
||||||
|
onCancel = { |
||||||
|
currentState = State.success() |
||||||
|
}, |
||||||
|
onConfirm = { |
||||||
|
startInstall(apkFile) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
private fun processOnDownloadingFileFailed() { |
||||||
|
isDownloading = false |
||||||
|
upgradeInteractor.dismissDownloadingDialog() |
||||||
|
safeContext { |
||||||
|
showDownloadingFailedTips(it, upgradeInfo.isForce) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun showDownloadingFailedTips(safeContext: Activity, isForce: Boolean) { |
||||||
|
upgradeInteractor.showDownloadingFailed(safeContext, isForce, |
||||||
|
onConfirm = { |
||||||
|
doUpdate() |
||||||
|
}, |
||||||
|
onCancel = { |
||||||
|
currentState = State.success() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
private fun safeContext(onContext: (Activity) -> Unit) { |
||||||
|
val topActivity = Sword.get().topActivity |
||||||
|
if (topActivity != null) { |
||||||
|
onContext(topActivity) |
||||||
|
} else { |
||||||
|
AppUtils.registerAppStatusChangedListener(this, object : Utils.OnAppStatusChangedListener { |
||||||
|
override fun onBackground() = Unit |
||||||
|
override fun onForeground() { |
||||||
|
AppUtils.unregisterAppStatusChangedListener(this) |
||||||
|
Sword.get().topActivity?.let { onContext(it) } |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun startInstall(apkFile: File) { |
||||||
|
upgradeInteractor.installApk(apkFile, upgradeInfo) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
package com.android.base.utils.upgrade |
||||||
|
|
||||||
|
/** |
||||||
|
*@author Ztiany |
||||||
|
* Email: ztiany3@gmail.com |
||||||
|
* Date : 2019-10-29 19:17 |
||||||
|
*/ |
||||||
|
data class UpgradeInfo( |
||||||
|
val isNewVersion: Boolean, |
||||||
|
val isForce: Boolean, |
||||||
|
val versionName: String, |
||||||
|
val downloadUrl: String, |
||||||
|
val description: String, |
||||||
|
val digitalAbstract: String, |
||||||
|
val raw: Any? |
||||||
|
) |
@ -0,0 +1,35 @@ |
|||||||
|
package com.app.base.upgrade |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import com.android.base.utils.upgrade.UpgradeInfo |
||||||
|
import io.reactivex.Flowable |
||||||
|
import java.io.File |
||||||
|
|
||||||
|
/** |
||||||
|
*@author Ztiany |
||||||
|
* Email: ztiany3@gmail.com |
||||||
|
* Date : 2019-10-29 19:16 |
||||||
|
*/ |
||||||
|
interface UpgradeInteractor { |
||||||
|
|
||||||
|
fun checkUpgrade(): Flowable<UpgradeInfo> |
||||||
|
|
||||||
|
fun showUpgradeDialog(context: Context, upgradeInfo: UpgradeInfo, onCancel: () -> Unit, onConfirm: () -> Unit) |
||||||
|
|
||||||
|
fun showInstallTipsDialog(context: Context, force: Boolean, onCancel: () -> Unit, onConfirm: () -> Unit) |
||||||
|
|
||||||
|
fun showDownloadingFailed(context: Context, force: Boolean, onCancel: () -> Unit, onConfirm: () -> Unit) |
||||||
|
|
||||||
|
fun showDownloadingDialog(context: Context, force: Boolean) |
||||||
|
|
||||||
|
fun dismissDownloadingDialog() |
||||||
|
|
||||||
|
fun onProgress(total: Long, progress: Long) |
||||||
|
|
||||||
|
fun installApk(file: File, upgradeInfo: UpgradeInfo) |
||||||
|
|
||||||
|
fun checkApkFile(apkFile: File, digitalAbstract: String): Boolean |
||||||
|
|
||||||
|
fun generateAppDownloadPath(versionName: String): String |
||||||
|
|
||||||
|
} |
@ -0,0 +1,110 @@ |
|||||||
|
package com.android.base.utils.upgrade; |
||||||
|
|
||||||
|
|
||||||
|
import android.app.IntentService; |
||||||
|
import android.content.Context; |
||||||
|
import android.content.Intent; |
||||||
|
import android.os.Handler; |
||||||
|
import android.os.Looper; |
||||||
|
import android.text.TextUtils; |
||||||
|
|
||||||
|
import com.android.base.utils.BaseUtils; |
||||||
|
import com.android.base.utils.common.Strings; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager; |
||||||
|
import timber.log.Timber; |
||||||
|
|
||||||
|
/** |
||||||
|
* 下载服务,不要配置在子进程。 |
||||||
|
*/ |
||||||
|
public class UpgradeService extends IntentService { |
||||||
|
|
||||||
|
static final String DOWNLOAD_APK_RESULT_ACTION = BaseUtils.getAppContext().getPackageName() + ".upgrade.result"; |
||||||
|
|
||||||
|
static final String IS_FORCE = "is_force"; |
||||||
|
static final String NOTIFY_TYPE_KEY = "notify_type"; |
||||||
|
static final String APK_FILE_KEY = "apk_path_key"; |
||||||
|
static final String URL_KEY = "url_key"; |
||||||
|
static final String VERSION_KEY = "version_key"; |
||||||
|
static final String TOTAL_PROGRESS_KEY = "total_progress_key"; |
||||||
|
static final String PROGRESS_KEY = "progress_key"; |
||||||
|
static final String DIGITAL_ABSTRACT_KEY = "digital_abstract_key"; |
||||||
|
|
||||||
|
public static final int NOTIFY_TYPE_FAILED = 1; |
||||||
|
public static final int NOTIFY_TYPE_PROGRESS = 2; |
||||||
|
public static final int NOTIFY_TYPE_SUCCESS = 3; |
||||||
|
|
||||||
|
private Handler mHandler; |
||||||
|
private boolean mIsForceUpdate; |
||||||
|
private String mDigitalAbstract; |
||||||
|
|
||||||
|
private class FlagDownloaderListener implements ApkDownloader.ApkDownloaderListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onProgress(long total, long progress) { |
||||||
|
Timber.d("onProgress() called with: total = [" + total + "], progress = [" + progress + "]"); |
||||||
|
mHandler.post(() -> notifyDownloadResult(NOTIFY_TYPE_PROGRESS, null, total, progress)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSuccess(File desFile) { |
||||||
|
Timber.d("onSuccess() called with: desFile = [" + desFile + "]"); |
||||||
|
mHandler.post(() -> notifyDownloadResult(NOTIFY_TYPE_SUCCESS, desFile, 0, 0)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onFail(Exception e) { |
||||||
|
Timber.d("onFail() called with: e = [" + e + "]"); |
||||||
|
mHandler.post(() -> notifyDownloadResult(NOTIFY_TYPE_FAILED, null, 0, 0)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public UpgradeService() { |
||||||
|
super("GW-UpgradeService"); |
||||||
|
} |
||||||
|
|
||||||
|
public static void start(Context context, String updateUrl, String versionName, String digitalAbstract, boolean isForceUpdate) { |
||||||
|
if (Strings.isEmpty(updateUrl) || Strings.isEmpty(versionName)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
Intent intent = new Intent(context, UpgradeService.class); |
||||||
|
intent.putExtra(URL_KEY, updateUrl); |
||||||
|
intent.putExtra(IS_FORCE, isForceUpdate); |
||||||
|
intent.putExtra(VERSION_KEY, versionName); |
||||||
|
intent.putExtra(DIGITAL_ABSTRACT_KEY, digitalAbstract); |
||||||
|
context.startService(intent); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onHandleIntent(Intent intent) { |
||||||
|
String url = intent.getStringExtra(URL_KEY); |
||||||
|
String versionName = intent.getStringExtra(VERSION_KEY); |
||||||
|
mIsForceUpdate = intent.getBooleanExtra(IS_FORCE, false); |
||||||
|
mDigitalAbstract = intent.getStringExtra(DIGITAL_ABSTRACT_KEY); |
||||||
|
if (!TextUtils.isEmpty(url) && !TextUtils.isEmpty(versionName)) { |
||||||
|
new ApkDownloader(url, AppUpgradeChecker.upgradeInteractor.generateAppDownloadPath(versionName), mDigitalAbstract, new FlagDownloaderListener()).start(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCreate() { |
||||||
|
super.onCreate(); |
||||||
|
mHandler = new Handler(Looper.getMainLooper()); |
||||||
|
} |
||||||
|
|
||||||
|
private void notifyDownloadResult(int notifyType, File desFile, long total, long progress) { |
||||||
|
Intent intent = new Intent(); |
||||||
|
intent.setAction(DOWNLOAD_APK_RESULT_ACTION); |
||||||
|
intent.putExtra(IS_FORCE, mIsForceUpdate); |
||||||
|
intent.putExtra(NOTIFY_TYPE_KEY, notifyType); |
||||||
|
intent.putExtra(TOTAL_PROGRESS_KEY, total); |
||||||
|
intent.putExtra(PROGRESS_KEY, progress); |
||||||
|
if (desFile != null) { |
||||||
|
intent.putExtra(APK_FILE_KEY, desFile); |
||||||
|
} |
||||||
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<shape |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:shape="rectangle"> |
||||||
|
|
||||||
|
<size android:height="20dp" /> |
||||||
|
|
||||||
|
<solid android:color="@color/silver" /> |
||||||
|
|
||||||
|
</shape> |
@ -0,0 +1,32 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<LinearLayout |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar |
||||||
|
android:id="@+id/baseToolbarDebug" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:minHeight="?actionBarSize" |
||||||
|
app:title="环境配置" |
||||||
|
app:titleTextColor="@color/black"/> |
||||||
|
|
||||||
|
<ScrollView |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:fillViewport="true"> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat |
||||||
|
android:id="@+id/baseLlDebugHostContent" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:orientation="vertical" |
||||||
|
app:divider="@drawable/base_shape_debug_divider" |
||||||
|
app:showDividers="middle"/> |
||||||
|
|
||||||
|
</ScrollView> |
||||||
|
|
||||||
|
</LinearLayout> |
@ -0,0 +1,51 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<merge |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:orientation="vertical" |
||||||
|
android:padding="20dp" |
||||||
|
tools:parentTag="android.widget.LinearLayout"> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/baseTvDebugHostName" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:textColor="@color/black" |
||||||
|
android:textSize="18sp" |
||||||
|
android:textStyle="bold" |
||||||
|
app:layout_constraintLeft_toLeftOf="parent" |
||||||
|
app:layout_constraintTop_toTopOf="parent" |
||||||
|
tools:text="请求环境"/> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_marginTop="20dp" |
||||||
|
android:gravity="center_vertical" |
||||||
|
android:orientation="horizontal"> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/baseTvDebugHostValue" |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_marginEnd="5dp" |
||||||
|
android:layout_weight="1" |
||||||
|
android:textColor="@color/red" |
||||||
|
android:textSize="10sp" |
||||||
|
tools:ignore="SmallSp" |
||||||
|
tools:text="正式"/> |
||||||
|
|
||||||
|
<Button |
||||||
|
android:id="@+id/baseBtnDebugSwitch" |
||||||
|
style="@style/Base.Widget.AppCompat.Button.Borderless.Colored" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:text="切换" |
||||||
|
tools:ignore="HardcodedText"/> |
||||||
|
|
||||||
|
</LinearLayout> |
||||||
|
|
||||||
|
</merge> |
Loading…
Reference in new issue