feat: 应用内更新 & 崩溃报告

This commit is contained in:
HuanCheng65 2021-12-03 20:36:11 +08:00
parent b6c4299398
commit a4046ea98d
6 changed files with 128 additions and 16 deletions

View File

@ -153,4 +153,5 @@ dependencies {
def appCenterSdkVersion = '4.3.1' def appCenterSdkVersion = '4.3.1'
implementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}" implementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}"
implementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}" implementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}"
implementation "com.microsoft.appcenter:appcenter-distribute:${appCenterSdkVersion}"
} }

View File

@ -37,6 +37,13 @@ import com.huanchengfly.tieba.post.utils.QuickPreviewUtil.getForumName
import com.huanchengfly.tieba.post.utils.QuickPreviewUtil.getPreviewInfo import com.huanchengfly.tieba.post.utils.QuickPreviewUtil.getPreviewInfo
import com.huanchengfly.tieba.post.utils.QuickPreviewUtil.isForumUrl import com.huanchengfly.tieba.post.utils.QuickPreviewUtil.isForumUrl
import com.huanchengfly.tieba.post.utils.QuickPreviewUtil.isThreadUrl import com.huanchengfly.tieba.post.utils.QuickPreviewUtil.isThreadUrl
import com.microsoft.appcenter.AppCenter
import com.microsoft.appcenter.analytics.Analytics
import com.microsoft.appcenter.crashes.Crashes
import com.microsoft.appcenter.distribute.Distribute
import com.microsoft.appcenter.distribute.DistributeListener
import com.microsoft.appcenter.distribute.ReleaseDetails
import com.microsoft.appcenter.distribute.UpdateAction
import org.intellij.lang.annotations.RegExp import org.intellij.lang.annotations.RegExp
import org.litepal.LitePal import org.litepal.LitePal
import java.util.* import java.util.*
@ -72,6 +79,11 @@ class BaseApplication : Application(), IApp {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
setWebViewPath(this) setWebViewPath(this)
} }
Distribute.setListener(MyDistributeListener())
AppCenter.start(
this, "b56debcc-264b-4368-a2cd-8c20213f6433",
Analytics::class.java, Crashes::class.java, Distribute::class.java
)
ThemeUtils.init(ThemeDelegate) ThemeUtils.init(ThemeDelegate)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
LitePal.initialize(this) LitePal.initialize(this)
@ -268,6 +280,35 @@ class BaseApplication : Application(), IApp {
var DENSITY = 0f var DENSITY = 0f
} }
class MyDistributeListener : DistributeListener {
override fun onReleaseAvailable(
activity: Activity,
releaseDetails: ReleaseDetails
): Boolean {
val versionName = releaseDetails.shortVersion
val releaseNotes = releaseDetails.releaseNotes
if (activity is BaseActivity) {
activity.showDialog {
setTitle(activity.getString(R.string.title_dialog_update, versionName))
setMessage(releaseNotes)
setCancelable(!releaseDetails.isMandatoryUpdate)
setPositiveButton(R.string.appcenter_distribute_update_dialog_download) { _, _ ->
Distribute.notifyUpdateAction(UpdateAction.UPDATE)
}
if (!releaseDetails.isMandatoryUpdate) {
setNeutralButton(R.string.appcenter_distribute_update_dialog_postpone) { _, _ ->
Distribute.notifyUpdateAction(UpdateAction.POSTPONE)
}
setNegativeButton(R.string.button_next_time, null)
}
}
}
return true
}
override fun onNoReleaseAvailable(activity: Activity) {}
}
companion object { companion object {
val TAG = BaseApplication::class.java.simpleName val TAG = BaseApplication::class.java.simpleName

View File

@ -23,7 +23,10 @@ import com.google.android.material.snackbar.Snackbar
import com.huanchengfly.tieba.post.* import com.huanchengfly.tieba.post.*
import com.huanchengfly.tieba.post.adapters.ViewPagerAdapter import com.huanchengfly.tieba.post.adapters.ViewPagerAdapter
import com.huanchengfly.tieba.post.api.Error import com.huanchengfly.tieba.post.api.Error
import com.huanchengfly.tieba.post.api.LiteApi
import com.huanchengfly.tieba.post.api.interfaces.CommonCallback import com.huanchengfly.tieba.post.api.interfaces.CommonCallback
import com.huanchengfly.tieba.post.api.retrofit.doIfFailure
import com.huanchengfly.tieba.post.api.retrofit.doIfSuccess
import com.huanchengfly.tieba.post.fragments.MainForumListFragment import com.huanchengfly.tieba.post.fragments.MainForumListFragment
import com.huanchengfly.tieba.post.fragments.MessageFragment import com.huanchengfly.tieba.post.fragments.MessageFragment
import com.huanchengfly.tieba.post.fragments.MyInfoFragment import com.huanchengfly.tieba.post.fragments.MyInfoFragment
@ -33,9 +36,12 @@ import com.huanchengfly.tieba.post.models.MyInfoBean
import com.huanchengfly.tieba.post.services.NotifyJobService import com.huanchengfly.tieba.post.services.NotifyJobService
import com.huanchengfly.tieba.post.utils.* import com.huanchengfly.tieba.post.utils.*
import com.huanchengfly.tieba.post.widgets.MyViewPager import com.huanchengfly.tieba.post.widgets.MyViewPager
import com.microsoft.appcenter.AppCenter
import com.microsoft.appcenter.analytics.Analytics
import com.microsoft.appcenter.crashes.Crashes import com.microsoft.appcenter.crashes.Crashes
import com.microsoft.appcenter.distribute.Distribute
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.*
open class MainActivity : BaseActivity(), BottomNavigationView.OnNavigationItemSelectedListener, OnNavigationItemReselectedListener { open class MainActivity : BaseActivity(), BottomNavigationView.OnNavigationItemSelectedListener, OnNavigationItemReselectedListener {
var mAdapter: ViewPagerAdapter = ViewPagerAdapter(supportFragmentManager) var mAdapter: ViewPagerAdapter = ViewPagerAdapter(supportFragmentManager)
@ -147,6 +153,13 @@ open class MainActivity : BaseActivity(), BottomNavigationView.OnNavigationItemS
override fun getLayoutId(): Int = R.layout.activity_main override fun getLayoutId(): Int = R.layout.activity_main
private fun formatDateTime(
pattern: String,
timestamp: Long = System.currentTimeMillis()
): String {
return SimpleDateFormat(pattern, Locale.getDefault()).format(Date(timestamp))
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setSwipeBackEnable(false) setSwipeBackEnable(false)
@ -154,14 +167,47 @@ open class MainActivity : BaseActivity(), BottomNavigationView.OnNavigationItemS
findView() findView()
initView() initView()
initListener() initListener()
AppCenter.start( Distribute.checkForUpdate()
getApplication(), "b56debcc-264b-4368-a2cd-8c20213f6433", Crashes.hasCrashedInLastSession().thenAccept { hasCrashed ->
Analytics::class.java, Crashes::class.java if (hasCrashed) {
Crashes.getLastSessionCrashReport().thenAccept {
val device = it.device
showDialog {
setTitle(R.string.title_dialog_crash)
setMessage(R.string.message_dialog_crash)
setPositiveButton(R.string.button_copy_crash_link) { _, _ ->
launch(IO + job) {
LiteApi.instance.pastebinAsync(
"崩溃报告 ${
formatDateTime(
"yyyy-MM-dd HH:mm:ss",
it.appErrorTime.time
) )
}",
"""
App 版本${device.appVersion}
系统版本${device.osVersion}
机型${device.oemName} ${device.model}
崩溃
${it.stackTrace}
""".trimIndent()
).doIfSuccess {
TiebaUtil.copyText(this@MainActivity, it)
}.doIfFailure {
toastShort(R.string.toast_get_link_failed)
}
}
}
}
}
}
}
if (!SharedPreferencesUtil.get(SharedPreferencesUtil.SP_APP_DATA) if (!SharedPreferencesUtil.get(SharedPreferencesUtil.SP_APP_DATA)
.getBoolean("notice_dialog", false) .getBoolean("notice_dialog", false)
) { ) {
showDialog(DialogUtil.build(this) showDialog(
DialogUtil.build(this)
.setTitle(R.string.title_dialog_notice) .setTitle(R.string.title_dialog_notice)
.setMessage(R.string.message_dialog_notice) .setMessage(R.string.message_dialog_notice)
.setPositiveButton(R.string.button_sure_default) { _, _ -> .setPositiveButton(R.string.button_sure_default) { _, _ ->

View File

@ -2,9 +2,23 @@ package com.huanchengfly.tieba.post.api.retrofit.interfaces
import com.huanchengfly.tieba.post.api.retrofit.ApiResult import com.huanchengfly.tieba.post.api.retrofit.ApiResult
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.POST
interface LiteApiInterface { interface LiteApiInterface {
@GET("https://huancheng65.github.io/TiebaLite/wallpapers.json") @GET("https://huancheng65.github.io/TiebaLite/wallpapers.json")
fun wallpapersAsync(): Deferred<ApiResult<List<String>>> fun wallpapersAsync(): Deferred<ApiResult<List<String>>>
@POST("https://pastebin.com/api/api_post.php")
@FormUrlEncoded
fun pastebinAsync(
@Field("api_paste_name") name: String,
@Field("api_paste_code") content: String,
@Field("api_dev_key") apiDevKey: String = "CB4NlNwnukUaLURHqIbL-IQgdCJTkB7l",
@Field("api_paste_private") private: String = "1",
@Field("api_paste_expire_date") expireDate: String = "1W",
@Field("api_paste_format") format: String = "markdown",
): Deferred<ApiResult<String>>
} }

View File

@ -16,9 +16,8 @@ import com.huanchengfly.tieba.post.interfaces.BackHandledInterface
import com.huanchengfly.tieba.post.interfaces.Refreshable import com.huanchengfly.tieba.post.interfaces.Refreshable
import com.huanchengfly.tieba.post.utils.AppPreferencesUtils import com.huanchengfly.tieba.post.utils.AppPreferencesUtils
import com.huanchengfly.tieba.post.utils.HandleBackUtil import com.huanchengfly.tieba.post.utils.HandleBackUtil
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/** /**
@ -192,6 +191,13 @@ abstract class BaseFragment : Fragment(), BackHandledInterface, CoroutineScope {
return false return false
} }
fun launchIO(
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
return launch(IO + job, start, block)
}
companion object { companion object {
private val TAG = BaseFragment::class.java.simpleName private val TAG = BaseFragment::class.java.simpleName
} }

View File

@ -480,4 +480,8 @@
<string name="text_size_large"></string> <string name="text_size_large"></string>
<string name="text_size_very_large">极大</string> <string name="text_size_very_large">极大</string>
<string name="toast_after_change_will_restart">修改已保存,即将重启 App 以应用设置</string> <string name="toast_after_change_will_restart">修改已保存,即将重启 App 以应用设置</string>
<string name="title_dialog_crash">崩溃了!</string>
<string name="message_dialog_crash">非常抱歉,应用在使用中发生了崩溃。\n您可以复制崩溃报告链接来向开发者反馈。</string>
<string name="button_copy_crash_link">复制报告链接</string>
<string name="toast_get_link_failed">获取链接失败</string>
</resources> </resources>