Skip to content

Commit 6e5a730

Browse files
author
Chenhe
committed
Merge branch 'dev/log'
2 parents d657ad4 + bb427d8 commit 6e5a730

File tree

19 files changed

+432
-70
lines changed

19 files changed

+432
-70
lines changed

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ dependencies {
5353
implementation "androidx.core:core-ktx:1.3.0"
5454
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
5555
implementation 'com.oasisfeng.nevo:sdk:2.0.0-rc01'
56-
56+
implementation "com.jakewharton.timber:timber:4.7.1"
5757

5858
testImplementation 'junit:junit:4.13'
5959
testImplementation 'org.amshove.kluent:kluent-android:1.61'

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

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,73 @@ import android.app.NotificationChannelGroup
55
import android.app.NotificationManager
66
import android.content.Context
77
import android.os.Build
8-
import android.util.Log
9-
import cc.chenhe.qqnotifyevo.utils.NOTIFY_GROUP_ID
10-
import cc.chenhe.qqnotifyevo.utils.getNotificationChannels
8+
import cc.chenhe.qqnotifyevo.log.CrashHandler
9+
import cc.chenhe.qqnotifyevo.log.ReleaseTree
10+
import cc.chenhe.qqnotifyevo.utils.*
11+
import timber.log.Timber
1112

1213

1314
class MyApplication : Application() {
1415

16+
companion object {
17+
private const val TAG = "Application"
18+
}
19+
20+
private lateinit var isLog: SpBooleanLiveData
21+
22+
private var debugTree: Timber.DebugTree? = null
23+
private var releaseTree: ReleaseTree? = null
24+
1525
override fun onCreate() {
1626
super.onCreate()
27+
isLog = fetchLog(this)
28+
setupTimber(isLog.value!!)
29+
isLog.observeForever { log ->
30+
setupTimber(log)
31+
}
32+
33+
Thread.setDefaultUncaughtExceptionHandler(CrashHandler)
34+
Timber.tag(TAG).i("\n\n")
35+
Timber.tag(TAG).i("==================================================")
36+
Timber.tag(TAG).i("= App Create")
37+
Timber.tag(TAG).i("==================================================\n")
1738
registerNotificationChannel()
1839
}
1940

41+
private fun setupTimber(enableLog: Boolean) {
42+
if (BuildConfig.DEBUG) {
43+
if (debugTree == null)
44+
debugTree = Timber.DebugTree()
45+
plantIfNotExist(debugTree!!)
46+
}
47+
if (enableLog) {
48+
if (releaseTree == null)
49+
releaseTree = ReleaseTree(getLogDir(this))
50+
plantIfNotExist(releaseTree!!)
51+
} else {
52+
releaseTree?.also { r ->
53+
Timber.uproot(r)
54+
r.close()
55+
releaseTree = null
56+
}
57+
}
58+
}
59+
60+
private fun plantIfNotExist(tree: Timber.Tree) {
61+
if (!Timber.forest().contains(tree))
62+
Timber.plant(tree)
63+
}
64+
65+
fun deleteLog() {
66+
releaseTree?.close()
67+
releaseTree = null
68+
getLogDir(this).deleteRecursively()
69+
setupTimber(isLog.value!!)
70+
}
71+
2072
private fun registerNotificationChannel() {
2173
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
22-
Log.d("MyApplication", "注册系统通知渠道")
74+
Timber.tag(TAG).d("Register system notification channels")
2375
val group = NotificationChannelGroup(NOTIFY_GROUP_ID, getString(R.string.notify_group_base))
2476

2577
(getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager)?.apply {

app/src/main/java/cc/chenhe/qqnotifyevo/core/AvatarManager.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package cc.chenhe.qqnotifyevo.core
22

33
import android.graphics.Bitmap
44
import android.graphics.BitmapFactory
5-
import android.util.Log
65
import androidx.collection.LruCache
6+
import timber.log.Timber
77
import java.io.File
88
import java.io.FileOutputStream
99
import java.io.IOException
@@ -81,7 +81,7 @@ class AvatarManager private constructor(
8181
lru.put(conversionId, it)
8282
}
8383
} catch (e: Exception) {
84-
Log.e(TAG, "Decode avatar file error, delete the cache. conversionId=$conversionId")
84+
Timber.tag(TAG).e("Decode avatar file error, delete the cache. conversionId=$conversionId")
8585
e.printStackTrace()
8686
file.delete()
8787
lru.remove(conversionId)
@@ -95,6 +95,7 @@ class AvatarManager private constructor(
9595
* 清空磁盘与内存缓存。
9696
*/
9797
fun clearCache() {
98+
Timber.tag(TAG).d("Clear avatar cache in disk and memory.")
9899
cacheDir.listFiles()?.forEach { f ->
99100
f?.deleteRecursively()
100101
}

app/src/main/java/cc/chenhe/qqnotifyevo/core/InnerNotificationProcessor.kt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.app.NotificationManager
55
import android.content.Context
66
import android.service.notification.StatusBarNotification
77
import cc.chenhe.qqnotifyevo.utils.NotifyChannel
8+
import timber.log.Timber
89
import java.util.*
910

1011
/**
@@ -19,6 +20,10 @@ class InnerNotificationProcessor(
1920
context: Context
2021
) : NotificationProcessor(context) {
2122

23+
companion object {
24+
private const val TAG = "InnerNotifyProcessor"
25+
}
26+
2227
interface Commander {
2328
fun cancelNotification(key: String)
2429
}
@@ -45,12 +50,14 @@ class InnerNotificationProcessor(
4550
}
4651
else -> null
4752
}
53+
Timber.tag(TAG).v("Clear all evolutionary notifications.")
4854
(context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).run {
4955
ids?.forEach { id -> cancel(id) }
5056
}
5157
}
5258

53-
private fun sendNotification(context: Context, @Companion.SourceTag tag: Int, id: Int, notification: Notification) {
59+
private fun sendNotification(context: Context, @NotificationProcessor.Companion.SourceTag tag: Int, id: Int,
60+
notification: Notification) {
5461
(context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(id, notification)
5562
addNotifyId(tag, id)
5663
}
@@ -72,14 +79,14 @@ class InnerNotificationProcessor(
7279
conversation: Conversation, sbn: StatusBarNotification,
7380
original: Notification): Notification {
7481
val history = getHistoryMessage(tag)
75-
var notification: Notification = createConversionNotification(context, tag, channel, conversation, original)
82+
var notification: Notification = createConversationNotification(context, tag, channel, conversation, original)
7683
for (c in history) {
7784
if (c.name != conversation.name || c.isGroup && channel != NotifyChannel.GROUP ||
7885
!c.isGroup && channel == NotifyChannel.GROUP) {
7986
// 确保只刷新新增的通知
8087
continue
8188
}
82-
notification = createConversionNotification(context, tag, channel, c, original).apply {
89+
notification = createConversationNotification(context, tag, channel, c, original).apply {
8390
contentIntent = original.contentIntent
8491
deleteIntent = original.deleteIntent
8592
}
@@ -89,7 +96,7 @@ class InnerNotificationProcessor(
8996
return notification
9097
}
9198

92-
private fun addNotifyId(@Companion.SourceTag tag: Int, ids: Int) {
99+
private fun addNotifyId(@NotificationProcessor.Companion.SourceTag tag: Int, ids: Int) {
93100
when (tag) {
94101
TAG_QQ -> qqNotifyIds.add(ids)
95102
TAG_TIM -> timNotifyIds.add(ids)

app/src/main/java/cc/chenhe/qqnotifyevo/core/NevoNotificationProcessor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class NevoNotificationProcessor(context: Context) : NotificationProcessor(contex
1818
override fun renewConversionNotification(context: Context, tag: Int, channel: NotifyChannel,
1919
conversation: Conversation, sbn: StatusBarNotification,
2020
original: Notification): Notification {
21-
return createConversionNotification(context, tag, channel, conversation, original)
21+
return createConversationNotification(context, tag, channel, conversation, original)
2222
}
2323

2424
}

app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import android.app.PendingIntent
55
import android.content.Context
66
import android.graphics.Bitmap
77
import android.service.notification.StatusBarNotification
8-
import android.util.Log
98
import androidx.annotation.IntDef
109
import androidx.annotation.VisibleForTesting
1110
import androidx.core.app.NotificationCompat
@@ -15,6 +14,7 @@ import androidx.core.graphics.drawable.IconCompat
1514
import androidx.core.graphics.drawable.toBitmap
1615
import cc.chenhe.qqnotifyevo.R
1716
import cc.chenhe.qqnotifyevo.utils.*
17+
import timber.log.Timber
1818
import java.util.*
1919
import java.util.regex.Matcher
2020
import java.util.regex.Pattern
@@ -127,6 +127,7 @@ abstract class NotificationProcessor(context: Context) {
127127
* @param tag 来源标记。
128128
*/
129129
fun clearHistory(@SourceTag tag: Int) {
130+
Timber.tag(TAG).v("Clear history. tag=$tag")
130131
when (tag) {
131132
TAG_QQ -> {
132133
qqHistory.clear()
@@ -187,8 +188,10 @@ abstract class NotificationProcessor(context: Context) {
187188
fun resolveNotification(context: Context, packageName: String, sbn: StatusBarNotification): Notification? {
188189
val original = sbn.notification ?: return null
189190
val tag = getTagFromPackageName(packageName)
190-
if (tag == TAG_UNKNOWN)
191+
if (tag == TAG_UNKNOWN) {
192+
Timber.tag(TAG).d("Unknown tag, skip. pkgName=$packageName")
191193
return null
194+
}
192195

193196
val title = original.extras.getString(Notification.EXTRA_TITLE)
194197
val content = original.extras.getString(Notification.EXTRA_TEXT)
@@ -205,11 +208,11 @@ abstract class NotificationProcessor(context: Context) {
205208
// 单独处理QQ空间
206209
val isQzone = title?.let { qzonePattern.matcher(it).matches() } ?: false
207210

208-
Log.v(TAG, "标题: $title; Ticker: $ticker; Q空间: $isQzone; 内容: $content")
209-
211+
Timber.tag(TAG).v("Title: $title; Ticker: $ticker; QZone: $isQzone; Multi: $isMulti; Content: $content")
210212

211213
// 隐藏消息详情
212214
if (ticker != null && ticker == content && hideMsgPattern.matcher(ticker).matches()) {
215+
Timber.tag(TAG).v("Hidden message content, skip.")
213216
return null
214217
}
215218

@@ -221,12 +224,14 @@ abstract class NotificationProcessor(context: Context) {
221224
avatarManager.getAvatar(CONVERSATION_NAME_QZONE.hashCode()), original.contentIntent,
222225
original.deleteIntent)
223226
deleteOldMessage(conversation, matchQzoneNum(title))
224-
Log.d(TAG, "[QZone] Ticker: $ticker")
227+
Timber.tag(TAG).d("[QZone] Ticker: $ticker")
225228
return renewQzoneNotification(context, tag, conversation, sbn, original)
226229
}
227230

228-
if (ticker == null)
231+
if (ticker == null) {
232+
Timber.tag(TAG).i("Ticker is null, skip.")
229233
return null
234+
}
230235

231236
// 群消息
232237
groupMsgPattern.matcher(ticker).also { matcher ->
@@ -239,7 +244,7 @@ abstract class NotificationProcessor(context: Context) {
239244
val conversation = addMessage(tag, name, text, groupName,
240245
avatarManager.getAvatar(name.hashCode()), original.contentIntent, original.deleteIntent)
241246
deleteOldMessage(conversation, if (isMulti) 0 else matchMessageNum(title))
242-
Log.d(TAG, "[Group] Name: $name; Group: $groupName; Text: $text")
247+
Timber.tag(TAG).d("[Group] Name: $name; Group: $groupName; Text: $text")
243248
return renewConversionNotification(context, tag, NotifyChannel.GROUP, conversation, sbn, original)
244249
}
245250
}
@@ -257,15 +262,15 @@ abstract class NotificationProcessor(context: Context) {
257262
original.contentIntent, original.deleteIntent)
258263
deleteOldMessage(conversation, if (isMulti) 0 else matchMessageNum(titleMatcher))
259264
return if (special) {
260-
Log.d(TAG, "[Special] Name: $name; Text: $text")
265+
Timber.tag(TAG).d("[Special] Name: $name; Text: $text")
261266
renewConversionNotification(context, tag, NotifyChannel.FRIEND_SPECIAL, conversation, sbn, original)
262267
} else {
263-
Log.d(TAG, "[Friend] Name: $name; Text: $text")
268+
Timber.tag(TAG).d("[Friend] Name: $name; Text: $text")
264269
renewConversionNotification(context, tag, NotifyChannel.FRIEND, conversation, sbn, original)
265270
}
266271
}
267272
}
268-
Log.w(TAG, "[None] Not match any pattern.")
273+
Timber.tag(TAG).w("[None] Not match any pattern.")
269274
return null
270275
}
271276

@@ -368,6 +373,7 @@ abstract class NotificationProcessor(context: Context) {
368373
}
369374
val num = conversation.messages.size
370375
val subtext = if (num > 1) context.getString(R.string.notify_subtext_qzone_num, num) else null
376+
Timber.tag(TAG).v("Create QZone notification for $num messages.")
371377
return createNotification(context, tag, NotifyChannel.QZONE, style,
372378
avatarManager.getAvatar(CONVERSATION_NAME_QZONE.hashCode()), original, subtext)
373379
}
@@ -379,8 +385,8 @@ abstract class NotificationProcessor(context: Context) {
379385
* @param tag 来源标记。
380386
* @param original 原始通知。
381387
*/
382-
protected fun createConversionNotification(context: Context, @SourceTag tag: Int, channel: NotifyChannel,
383-
conversation: Conversation, original: Notification): Notification {
388+
protected fun createConversationNotification(context: Context, @SourceTag tag: Int, channel: NotifyChannel,
389+
conversation: Conversation, original: Notification): Notification {
384390
val style = NotificationCompat.MessagingStyle(Person.Builder().setName(conversation.name).build())
385391
if (conversation.isGroup) {
386392
style.conversationTitle = conversation.name
@@ -391,6 +397,7 @@ abstract class NotificationProcessor(context: Context) {
391397
}
392398
val num = conversation.messages.size
393399
val subtext = if (num > 1) context.getString(R.string.notify_subtext_message_num, num) else null
400+
Timber.tag(TAG).v("Create conversation notification for $num messages.")
394401
return createNotification(context, tag, channel, style,
395402
avatarManager.getAvatar(conversation.name.hashCode()), original, subtext)
396403
}
@@ -466,6 +473,7 @@ abstract class NotificationProcessor(context: Context) {
466473
return
467474
if (conversation.messages.size <= maxMessageNum)
468475
return
476+
Timber.tag(TAG).d("Delete old messages. conversation: ${conversation.name}, max: $maxMessageNum")
469477
while (conversation.messages.size > maxMessageNum) {
470478
conversation.messages.removeAt(0)
471479
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package cc.chenhe.qqnotifyevo.log
2+
3+
import timber.log.Timber
4+
5+
object CrashHandler : Thread.UncaughtExceptionHandler {
6+
7+
private val default = Thread.getDefaultUncaughtExceptionHandler()
8+
9+
override fun uncaughtException(thread: Thread, t: Throwable) {
10+
try {
11+
Timber.e(t)
12+
} catch (e: Exception) {
13+
e.printStackTrace()
14+
}
15+
default?.uncaughtException(thread, t)
16+
}
17+
18+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package cc.chenhe.qqnotifyevo.log
2+
3+
import java.io.File
4+
import java.io.FileOutputStream
5+
import java.text.SimpleDateFormat
6+
import java.util.*
7+
import kotlin.math.abs
8+
9+
class LogWriter(
10+
private val logDir: File,
11+
time: Long = System.currentTimeMillis()
12+
) : AutoCloseable {
13+
14+
companion object {
15+
private const val MILLIS_PER_DAY = 24 * 3600 * 1000
16+
}
17+
18+
private var logFileTime: Long = time
19+
var logFile: File = logFile(logFileTime)
20+
private set(value) {
21+
field = value
22+
out.flush()
23+
out.close()
24+
out = FileOutputStream(value, true)
25+
}
26+
private var out: FileOutputStream = FileOutputStream(logFile, true)
27+
28+
private fun logFile(time: Long): File {
29+
if (!logDir.isDirectory) {
30+
logDir.mkdirs()
31+
}
32+
val format = SimpleDateFormat("yyyyMMdd-HHmmssSSS", Locale.CHINA)
33+
return File(logDir, format.format(Date(time)) + ".log")
34+
}
35+
36+
fun write(message: String, time: Long = System.currentTimeMillis()) {
37+
if (!isSameDay(logFileTime, time)) {
38+
logFileTime = time
39+
logFile = logFile(time)
40+
}
41+
out.write((message + "\n").toByteArray())
42+
}
43+
44+
private fun isSameDay(t1: Long, t2: Long): Boolean {
45+
if (abs(t1 - t2) > MILLIS_PER_DAY) {
46+
return false
47+
}
48+
val offset = TimeZone.getDefault().rawOffset
49+
return (t1 + offset) / MILLIS_PER_DAY == (t2 + offset) / MILLIS_PER_DAY
50+
}
51+
52+
override fun close() {
53+
out.close()
54+
}
55+
}

0 commit comments

Comments
 (0)