feat: 应用内更新 & 崩溃报告
This commit is contained in:
parent
b6c4299398
commit
a4046ea98d
|
|
@ -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}"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,20 +167,53 @@ 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(
|
||||||
.setTitle(R.string.title_dialog_notice)
|
DialogUtil.build(this)
|
||||||
.setMessage(R.string.message_dialog_notice)
|
.setTitle(R.string.title_dialog_notice)
|
||||||
.setPositiveButton(R.string.button_sure_default) { _, _ ->
|
.setMessage(R.string.message_dialog_notice)
|
||||||
SharedPreferencesUtil.put(
|
.setPositiveButton(R.string.button_sure_default) { _, _ ->
|
||||||
this,
|
SharedPreferencesUtil.put(
|
||||||
SharedPreferencesUtil.SP_APP_DATA,
|
this,
|
||||||
|
SharedPreferencesUtil.SP_APP_DATA,
|
||||||
"notice_dialog",
|
"notice_dialog",
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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>>
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue