Skip to content

Commit 104d742

Browse files
author
Chenhe
committed
完成应用升级数据迁移
1 parent ab3e87e commit 104d742

File tree

12 files changed

+375
-12
lines changed

12 files changed

+375
-12
lines changed

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ dependencies {
5353
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
5454
exclude group: 'com.android.support', module: 'support-annotations'
5555
})
56+
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
5657
implementation "androidx.preference:preference-ktx:1.1.1"
5758
implementation "androidx.core:core-ktx:1.3.1"
5859
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@
8181
</intent-filter>
8282
</activity>
8383

84+
<service android:name=".service.UpgradeService" />
85+
8486
</application>
8587

8688
</manifest>

app/src/main/java/cc/chenhe/qqnotifyevo/MyApplication.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.media.RingtoneManager
99
import androidx.core.app.NotificationManagerCompat
1010
import cc.chenhe.qqnotifyevo.log.CrashHandler
1111
import cc.chenhe.qqnotifyevo.log.ReleaseTree
12+
import cc.chenhe.qqnotifyevo.service.UpgradeService
1213
import cc.chenhe.qqnotifyevo.utils.*
1314
import timber.log.Timber
1415

@@ -38,6 +39,8 @@ class MyApplication : Application() {
3839
Timber.tag(TAG).i("= App Create")
3940
Timber.tag(TAG).i("==================================================\n")
4041
registerNotificationChannel()
42+
43+
UpgradeService.startIfNecessary(this)
4144
}
4245

4346
private fun setupTimber(enableLog: Boolean) {

app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
package cc.chenhe.qqnotifyevo.preference
22

3+
import android.content.BroadcastReceiver
4+
import android.content.Context
35
import android.content.Intent
6+
import android.content.IntentFilter
47
import android.os.Bundle
58
import androidx.appcompat.app.AlertDialog
69
import androidx.appcompat.app.AppCompatActivity
710
import androidx.core.app.NotificationManagerCompat
11+
import androidx.localbroadcastmanager.content.LocalBroadcastManager
812
import cc.chenhe.qqnotifyevo.R
913
import cc.chenhe.qqnotifyevo.StaticReceiver
10-
import cc.chenhe.qqnotifyevo.utils.ACTION_MULTI_MSG_DONT_SHOW
11-
import cc.chenhe.qqnotifyevo.utils.MODE_LEGACY
12-
import cc.chenhe.qqnotifyevo.utils.NOTIFY_ID_MULTI_MSG
13-
import cc.chenhe.qqnotifyevo.utils.getShowInRecent
14+
import cc.chenhe.qqnotifyevo.service.UpgradeService
15+
import cc.chenhe.qqnotifyevo.utils.*
1416

1517
class PreferenceAty : AppCompatActivity() {
1618

@@ -23,19 +25,58 @@ class PreferenceAty : AppCompatActivity() {
2325
const val EXTRA_NEVO_MULTI_MSG = "nevo_multi_msg"
2426
}
2527

26-
private lateinit var mainPreferenceFr: MainPreferenceFr
28+
private var mainPreferenceFr: MainPreferenceFr? = null
29+
30+
private var upgradeReceiver: BroadcastReceiver? = null
2731

2832
override fun onCreate(savedInstanceState: Bundle?) {
2933
super.onCreate(savedInstanceState)
30-
setContentView(R.layout.preference_layout)
31-
mainPreferenceFr = MainPreferenceFr()
32-
supportFragmentManager.beginTransaction()
33-
.replace(R.id.frameLayout, mainPreferenceFr)
34-
.commit()
34+
setContentView(R.layout.aty_preference)
35+
36+
if (!UpgradeService.isRunningOrPrepared()) {
37+
initPreferenceFragment()
38+
} else {
39+
supportFragmentManager.beginTransaction()
40+
.replace(R.id.frameLayout, UpgradingFr())
41+
.commit()
42+
upgradeReceiver = object : BroadcastReceiver() {
43+
override fun onReceive(ctx: Context, i: Intent?) {
44+
if (i?.action == ACTION_APPLICATION_UPGRADE_COMPLETE) {
45+
LocalBroadcastManager.getInstance(this@PreferenceAty).unregisterReceiver(this)
46+
upgradeReceiver = null
47+
initPreferenceFragment()
48+
}
49+
}
50+
}
51+
LocalBroadcastManager.getInstance(this).registerReceiver(upgradeReceiver!!,
52+
IntentFilter(ACTION_APPLICATION_UPGRADE_COMPLETE))
53+
if (!UpgradeService.isRunningOrPrepared()) {
54+
// 避免极端情况下在注册监听器之前更新完成
55+
initPreferenceFragment()
56+
}
57+
}
3558

3659
showNevoMultiMsgDialogIfNeeded()
3760
}
3861

62+
override fun onDestroy() {
63+
super.onDestroy()
64+
upgradeReceiver?.also { receiver ->
65+
LocalBroadcastManager.getInstance(this@PreferenceAty).unregisterReceiver(receiver)
66+
}
67+
}
68+
69+
private fun initPreferenceFragment() {
70+
if (mainPreferenceFr != null) {
71+
return
72+
}
73+
mainPreferenceFr = MainPreferenceFr().also { fr ->
74+
supportFragmentManager.beginTransaction()
75+
.replace(R.id.frameLayout, fr)
76+
.commit()
77+
}
78+
}
79+
3980
override fun onNewIntent(intent: Intent?) {
4081
super.onNewIntent(intent)
4182
setIntent(intent)
@@ -50,7 +91,7 @@ class PreferenceAty : AppCompatActivity() {
5091
.setMessage(R.string.multi_msg_dialog)
5192
.setNeutralButton(R.string.multi_msg_dialog_neutral, null)
5293
.setPositiveButton(R.string.multi_msg_dialog_positive) { _, _ ->
53-
mainPreferenceFr.setMode(MODE_LEGACY)
94+
mainPreferenceFr?.setMode(MODE_LEGACY)
5495
}
5596
.setNegativeButton(R.string.dont_show) { _, _ ->
5697
sendBroadcast(Intent(this, StaticReceiver::class.java).also {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package cc.chenhe.qqnotifyevo.preference
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.fragment.app.Fragment
8+
import cc.chenhe.qqnotifyevo.R
9+
10+
class UpgradingFr : Fragment() {
11+
12+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
13+
return inflater.inflate(R.layout.fr_upgrading, container, false)
14+
}
15+
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package cc.chenhe.qqnotifyevo.service
2+
3+
import android.app.NotificationChannel
4+
import android.app.NotificationManager
5+
import android.app.Service
6+
import android.content.Context
7+
import android.content.Intent
8+
import android.os.IBinder
9+
import androidx.core.app.NotificationCompat
10+
import androidx.core.app.NotificationManagerCompat
11+
import androidx.core.content.edit
12+
import androidx.core.os.UserManagerCompat
13+
import androidx.localbroadcastmanager.content.LocalBroadcastManager
14+
import androidx.preference.PreferenceManager
15+
import cc.chenhe.qqnotifyevo.R
16+
import cc.chenhe.qqnotifyevo.utils.ACTION_APPLICATION_UPGRADE_COMPLETE
17+
import cc.chenhe.qqnotifyevo.utils.NOTIFY_ID_UPGRADE
18+
import cc.chenhe.qqnotifyevo.utils.NOTIFY_SELF_FOREGROUND_SERVICE_CHANNEL_ID
19+
import cc.chenhe.qqnotifyevo.utils.UpgradeUtils
20+
import kotlinx.coroutines.Dispatchers
21+
import kotlinx.coroutines.GlobalScope
22+
import kotlinx.coroutines.launch
23+
import kotlinx.coroutines.withContext
24+
import timber.log.Timber
25+
26+
@Suppress("FunctionName")
27+
class UpgradeService : Service() {
28+
29+
companion object {
30+
31+
private const val TAG = "UpgradeService"
32+
33+
private const val EXTRA_OLD_VERSION = "old"
34+
private const val EXTRA_CURRENT_VERSION = "new"
35+
36+
private const val VERSION_2_0_2 = 20023
37+
38+
private var instance: UpgradeService? = null
39+
private var preparedToRunning = false
40+
41+
/**
42+
* 是否正在运行或者正准备运行。
43+
*/
44+
fun isRunningOrPrepared(): Boolean {
45+
return preparedToRunning || isRunning()
46+
}
47+
48+
private fun isRunning(): Boolean {
49+
return try {
50+
// 如果服务被强制结束,标记没有释放,那么此处会抛出异常。
51+
instance?.ping() ?: false
52+
} catch (e: Exception) {
53+
false
54+
}
55+
}
56+
57+
fun startIfNecessary(context: Context): Boolean {
58+
val old: Long = UpgradeUtils.getOldVersion(context)
59+
val new: Long = UpgradeUtils.getCurrentVersion(context)
60+
if (old == new) {
61+
Timber.tag(TAG).d("Old version equals to the current, no need to upgrade. v=$new")
62+
return false
63+
} else if (old > new) {
64+
// should never happen
65+
Timber.tag(TAG).e("Current version is lower than old version! current=$new, old=$old")
66+
return false
67+
}
68+
69+
// old < new
70+
return if (shouldPerformUpgrade(old)) {
71+
preparedToRunning = true
72+
val i = Intent(context.applicationContext, UpgradeService::class.java).apply {
73+
putExtra(EXTRA_OLD_VERSION, old)
74+
putExtra(EXTRA_CURRENT_VERSION, new)
75+
}
76+
context.startService(i)
77+
true
78+
} else {
79+
Timber.tag(TAG).i("No need to perform data migration, update version code directly $old$new.")
80+
UpgradeUtils.setOldVersion(context, new)
81+
false
82+
}
83+
}
84+
85+
private fun shouldPerformUpgrade(old: Long): Boolean {
86+
return old in 1..VERSION_2_0_2
87+
}
88+
}
89+
90+
private var running = false
91+
92+
private lateinit var ctx: Context
93+
94+
private fun ping() = true
95+
96+
override fun onCreate() {
97+
super.onCreate()
98+
instance = this
99+
ctx = this.application
100+
createNotificationChannel()
101+
}
102+
103+
override fun onDestroy() {
104+
instance = null
105+
super.onDestroy()
106+
}
107+
108+
private fun createNotificationChannel() {
109+
val channel = NotificationChannel(NOTIFY_SELF_FOREGROUND_SERVICE_CHANNEL_ID,
110+
getString(R.string.notify_self_foreground_service_channel_name),
111+
NotificationManager.IMPORTANCE_LOW).apply {
112+
description = getString(R.string.notify_self_foreground_service_channel_name_des)
113+
}
114+
NotificationManagerCompat.from(this).createNotificationChannel(channel)
115+
}
116+
117+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
118+
if (!running) {
119+
running = true
120+
preparedToRunning = false
121+
122+
val notify = NotificationCompat.Builder(this, NOTIFY_SELF_FOREGROUND_SERVICE_CHANNEL_ID)
123+
.setSmallIcon(R.drawable.ic_notify_upgrade)
124+
.setContentTitle(getString(R.string.notify_upgrade))
125+
.setContentText(getString(R.string.notify_upgrade_text))
126+
.setPriority(NotificationCompat.PRIORITY_LOW)
127+
.setOngoing(true)
128+
.setOnlyAlertOnce(true)
129+
.build()
130+
startForeground(NOTIFY_ID_UPGRADE, notify)
131+
132+
GlobalScope.launch {
133+
val old = intent!!.getLongExtra(EXTRA_OLD_VERSION, -10)
134+
val new = intent.getLongExtra(EXTRA_CURRENT_VERSION, -10)
135+
if (old == -10L || new == -10L) {
136+
Timber.tag(TAG).e("onStartCommand: unknown version. old=$old, new=$new")
137+
complete(false, 0)
138+
} else {
139+
startUpgrade(old, new)
140+
}
141+
}
142+
} else {
143+
preparedToRunning = false
144+
}
145+
return super.onStartCommand(intent, flags, startId)
146+
}
147+
148+
/**
149+
* 升级完成后调用此函数。
150+
*/
151+
private fun complete(success: Boolean, currentVersion: Long) {
152+
if (success) {
153+
Timber.tag(TAG).d("Upgrade complete.")
154+
UpgradeUtils.setOldVersion(this, currentVersion)
155+
} else {
156+
Timber.tag(TAG).e("Upgrade error!")
157+
}
158+
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(ACTION_APPLICATION_UPGRADE_COMPLETE))
159+
stopForeground(true)
160+
stopSelf()
161+
}
162+
163+
private suspend fun startUpgrade(oldVersion: Long, currentVersion: Long) = withContext(Dispatchers.Main) {
164+
Timber.tag(TAG).d("Start upgrade process. $oldVersion$currentVersion")
165+
166+
if (oldVersion in 1..VERSION_2_0_2) {
167+
migrate_1_to_2_0_2()
168+
}
169+
170+
complete(true, currentVersion)
171+
}
172+
173+
private suspend fun migrate_1_to_2_0_2() = withContext(Dispatchers.Main) {
174+
if (UserManagerCompat.isUserUnlocked(ctx)) {
175+
Timber.tag(TAG).d("Remove deprecated preferences.")
176+
PreferenceManager.getDefaultSharedPreferences(ctx).edit {
177+
remove("friend_vibrate")
178+
remove("friend_ringtone")
179+
remove("group_notify")
180+
remove("group_ringtone")
181+
remove("group_vibrate")
182+
remove("qzone_notify")
183+
remove("qzone_ringtone")
184+
remove("qzone_vibrate")
185+
}
186+
}
187+
}
188+
189+
override fun onBind(i: Intent?): IBinder? {
190+
return null
191+
}
192+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package cc.chenhe.qqnotifyevo.utils
2+
3+
import android.content.Context
4+
import android.content.SharedPreferences
5+
import android.content.pm.PackageManager
6+
import android.os.Build
7+
import androidx.core.content.edit
8+
9+
object UpgradeUtils {
10+
11+
private const val NAME = "upgrade"
12+
private const val ITEM_OLD_VERSION = "oldVersion"
13+
14+
private fun Context.deviceProtected(): Context {
15+
return if (isDeviceProtectedStorage)
16+
this
17+
else
18+
createDeviceProtectedStorageContext()
19+
}
20+
21+
private fun deviceProtectedSp(context: Context, name: String = NAME): SharedPreferences {
22+
return context.deviceProtected().getSharedPreferences(name, Context.MODE_PRIVATE)
23+
}
24+
25+
fun getOldVersion(context: Context): Long {
26+
return deviceProtectedSp(context).getLong(ITEM_OLD_VERSION, 0L)
27+
}
28+
29+
fun setOldVersion(context: Context, value: Long) {
30+
deviceProtectedSp(context).edit {
31+
putLong(ITEM_OLD_VERSION, value)
32+
}
33+
}
34+
35+
fun getCurrentVersion(context: Context): Long {
36+
try {
37+
val pi = context.deviceProtected().packageManager.getPackageInfo(context.packageName, 0)
38+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
39+
pi.longVersionCode
40+
} else {
41+
@Suppress("DEPRECATION")
42+
pi.versionCode.toLong()
43+
}
44+
} catch (e: PackageManager.NameNotFoundException) {
45+
e.printStackTrace()
46+
}
47+
return 0L
48+
}
49+
}

0 commit comments

Comments
 (0)