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