feat: 优化代码

pull/167/head
kunfei 5 years ago
parent ca67f585d1
commit bed5c2e34a
  1. 1
      app/src/main/assets/updateLog.md
  2. 5
      app/src/main/java/io/legado/app/model/SearchBook.kt
  3. 5
      app/src/main/java/io/legado/app/model/WebBook.kt
  4. 32
      app/src/main/java/io/legado/app/model/webBook/BookList.kt
  5. 2
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  6. 22
      app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt
  7. 93
      app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt
  8. 17
      app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt
  9. 2
      app/src/main/res/layout/activity_rss_read.xml

@ -8,6 +8,7 @@
* 解决看过书籍的移到顶部需要向上滚动才能看到的bug * 解决看过书籍的移到顶部需要向上滚动才能看到的bug
* 只有再书源被删除找不到书源时才会自动换源 * 只有再书源被删除找不到书源时才会自动换源
* 美化界面by yangyxd * 美化界面by yangyxd
* 订阅后台播放
**2020/03/16** **2020/03/16**
* 修复滚动模式切换章节位置不归0的bug * 修复滚动模式切换章节位置不归0的bug

@ -0,0 +1,5 @@
package io.legado.app.model
class SearchBook {
}

@ -29,11 +29,12 @@ class WebBook(val bookSource: BookSource) {
context: CoroutineContext = Dispatchers.IO context: CoroutineContext = Dispatchers.IO
): Coroutine<List<SearchBook>> { ): Coroutine<List<SearchBook>> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
searchBookSuspend(key, page) searchBookSuspend(scope, key, page)
} }
} }
suspend fun searchBookSuspend( suspend fun searchBookSuspend(
scope: CoroutineScope,
key: String, key: String,
page: Int? = 1 page: Int? = 1
): ArrayList<SearchBook> { ): ArrayList<SearchBook> {
@ -47,6 +48,7 @@ class WebBook(val bookSource: BookSource) {
) )
val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl) val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl)
return BookList.analyzeBookList( return BookList.analyzeBookList(
scope,
res.body, res.body,
bookSource, bookSource,
analyzeUrl, analyzeUrl,
@ -75,6 +77,7 @@ class WebBook(val bookSource: BookSource) {
) )
val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl) val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl)
BookList.analyzeBookList( BookList.analyzeBookList(
scope,
res.body, res.body,
bookSource, bookSource,
analyzeUrl, analyzeUrl,

@ -9,11 +9,15 @@ import io.legado.app.model.Debug
import io.legado.app.model.analyzeRule.AnalyzeRule import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.NetworkUtils import io.legado.app.utils.NetworkUtils
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.isActive
object BookList { object BookList {
@Throws(Exception::class) @Throws(Exception::class)
fun analyzeBookList( fun analyzeBookList(
scope: CoroutineScope,
body: String?, body: String?,
bookSource: BookSource, bookSource: BookSource,
analyzeUrl: AnalyzeUrl, analyzeUrl: AnalyzeUrl,
@ -28,12 +32,13 @@ object BookList {
) )
) )
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}") Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}")
if (!scope.isActive) throw CancellationException()
val analyzeRule = AnalyzeRule(null) val analyzeRule = AnalyzeRule(null)
analyzeRule.setContent(body, baseUrl) analyzeRule.setContent(body, baseUrl)
bookSource.bookUrlPattern?.let { bookSource.bookUrlPattern?.let {
if (baseUrl.matches(it.toRegex())) { if (baseUrl.matches(it.toRegex())) {
Debug.log(bookSource.bookSourceUrl, "≡链接为详情页") Debug.log(bookSource.bookSourceUrl, "≡链接为详情页")
getInfoItem(analyzeRule, bookSource, baseUrl)?.let { searchBook -> getInfoItem(scope, analyzeRule, bookSource, baseUrl)?.let { searchBook ->
searchBook.infoHtml = body searchBook.infoHtml = body
bookList.add(searchBook) bookList.add(searchBook)
} }
@ -59,7 +64,7 @@ object BookList {
collections = analyzeRule.getElements(ruleList) collections = analyzeRule.getElements(ruleList)
if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) { if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) {
Debug.log(bookSource.bookSourceUrl, "└列表为空,按详情页解析") Debug.log(bookSource.bookSourceUrl, "└列表为空,按详情页解析")
getInfoItem(analyzeRule, bookSource, baseUrl)?.let { searchBook -> getInfoItem(scope, analyzeRule, bookSource, baseUrl)?.let { searchBook ->
searchBook.infoHtml = body searchBook.infoHtml = body
bookList.add(searchBook) bookList.add(searchBook)
} }
@ -74,8 +79,9 @@ object BookList {
val ruleWordCount = analyzeRule.splitSourceRule(bookListRule.wordCount) val ruleWordCount = analyzeRule.splitSourceRule(bookListRule.wordCount)
Debug.log(bookSource.bookSourceUrl, "└列表大小:${collections.size}") Debug.log(bookSource.bookSourceUrl, "└列表大小:${collections.size}")
for ((index, item) in collections.withIndex()) { for ((index, item) in collections.withIndex()) {
if (!scope.isActive) throw CancellationException()
getSearchItem( getSearchItem(
item, analyzeRule, bookSource, baseUrl, index == 0, scope, item, analyzeRule, bookSource, baseUrl, index == 0,
ruleName = ruleName, ruleBookUrl = ruleBookUrl, ruleAuthor = ruleAuthor, ruleName = ruleName, ruleBookUrl = ruleBookUrl, ruleAuthor = ruleAuthor,
ruleCoverUrl = ruleCoverUrl, ruleIntro = ruleIntro, ruleKind = ruleKind, ruleCoverUrl = ruleCoverUrl, ruleIntro = ruleIntro, ruleKind = ruleKind,
ruleLastChapter = ruleLastChapter, ruleWordCount = ruleWordCount ruleLastChapter = ruleLastChapter, ruleWordCount = ruleWordCount
@ -93,7 +99,9 @@ object BookList {
return bookList return bookList
} }
@Throws(Exception::class)
private fun getInfoItem( private fun getInfoItem(
scope: CoroutineScope,
analyzeRule: AnalyzeRule, analyzeRule: AnalyzeRule,
bookSource: BookSource, bookSource: BookSource,
baseUrl: String baseUrl: String
@ -108,29 +116,37 @@ object BookList {
with(bookSource.getBookInfoRule()) { with(bookSource.getBookInfoRule()) {
init?.let { init?.let {
if (it.isNotEmpty()) { if (it.isNotEmpty()) {
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则") Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则")
analyzeRule.setContent(analyzeRule.getElement(it)) analyzeRule.setContent(analyzeRule.getElement(it))
} }
} }
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取书名") Debug.log(bookSource.bookSourceUrl, "┌获取书名")
searchBook.name = analyzeRule.getString(name) searchBook.name = analyzeRule.getString(name)
Debug.log(bookSource.bookSourceUrl, "${searchBook.name}") Debug.log(bookSource.bookSourceUrl, "${searchBook.name}")
if (searchBook.name.isNotEmpty()) { if (searchBook.name.isNotEmpty()) {
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取作者") Debug.log(bookSource.bookSourceUrl, "┌获取作者")
searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(author)) searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(author))
Debug.log(bookSource.bookSourceUrl, "${searchBook.author}") Debug.log(bookSource.bookSourceUrl, "${searchBook.author}")
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取分类") Debug.log(bookSource.bookSourceUrl, "┌获取分类")
searchBook.kind = analyzeRule.getStringList(kind)?.joinToString(",") searchBook.kind = analyzeRule.getStringList(kind)?.joinToString(",")
Debug.log(bookSource.bookSourceUrl, "${searchBook.kind}") Debug.log(bookSource.bookSourceUrl, "${searchBook.kind}")
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取字数") Debug.log(bookSource.bookSourceUrl, "┌获取字数")
searchBook.wordCount = analyzeRule.getString(wordCount) searchBook.wordCount = analyzeRule.getString(wordCount)
Debug.log(bookSource.bookSourceUrl, "${searchBook.wordCount}") Debug.log(bookSource.bookSourceUrl, "${searchBook.wordCount}")
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节") Debug.log(bookSource.bookSourceUrl, "┌获取最新章节")
searchBook.latestChapterTitle = analyzeRule.getString(lastChapter) searchBook.latestChapterTitle = analyzeRule.getString(lastChapter)
Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}") Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}")
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取简介") Debug.log(bookSource.bookSourceUrl, "┌获取简介")
searchBook.intro = analyzeRule.getString(intro) searchBook.intro = analyzeRule.getString(intro)
Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", true) Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", true)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接") Debug.log(bookSource.bookSourceUrl, "┌获取封面链接")
searchBook.coverUrl = analyzeRule.getString(coverUrl, true) searchBook.coverUrl = analyzeRule.getString(coverUrl, true)
Debug.log(bookSource.bookSourceUrl, "${searchBook.coverUrl}") Debug.log(bookSource.bookSourceUrl, "${searchBook.coverUrl}")
@ -140,7 +156,9 @@ object BookList {
return null return null
} }
@Throws(Exception::class)
private fun getSearchItem( private fun getSearchItem(
scope: CoroutineScope,
item: Any, item: Any,
analyzeRule: AnalyzeRule, analyzeRule: AnalyzeRule,
bookSource: BookSource, bookSource: BookSource,
@ -162,30 +180,38 @@ object BookList {
searchBook.originOrder = bookSource.customOrder searchBook.originOrder = bookSource.customOrder
analyzeRule.book = searchBook analyzeRule.book = searchBook
analyzeRule.setContent(item) analyzeRule.setContent(item)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取书名", log) Debug.log(bookSource.bookSourceUrl, "┌获取书名", log)
searchBook.name = analyzeRule.getString(ruleName) searchBook.name = analyzeRule.getString(ruleName)
Debug.log(bookSource.bookSourceUrl, "${searchBook.name}", log) Debug.log(bookSource.bookSourceUrl, "${searchBook.name}", log)
if (searchBook.name.isNotEmpty()) { if (searchBook.name.isNotEmpty()) {
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取作者", log) Debug.log(bookSource.bookSourceUrl, "┌获取作者", log)
searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(ruleAuthor)) searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(ruleAuthor))
Debug.log(bookSource.bookSourceUrl, "${searchBook.author}", log) Debug.log(bookSource.bookSourceUrl, "${searchBook.author}", log)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取分类", log) Debug.log(bookSource.bookSourceUrl, "┌获取分类", log)
searchBook.kind = analyzeRule.getStringList(ruleKind)?.joinToString(",") searchBook.kind = analyzeRule.getStringList(ruleKind)?.joinToString(",")
Debug.log(bookSource.bookSourceUrl, "${searchBook.kind}", log) Debug.log(bookSource.bookSourceUrl, "${searchBook.kind}", log)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取字数", log) Debug.log(bookSource.bookSourceUrl, "┌获取字数", log)
searchBook.wordCount = analyzeRule.getString(ruleWordCount) searchBook.wordCount = analyzeRule.getString(ruleWordCount)
Debug.log(bookSource.bookSourceUrl, "${searchBook.wordCount}", log) Debug.log(bookSource.bookSourceUrl, "${searchBook.wordCount}", log)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节", log) Debug.log(bookSource.bookSourceUrl, "┌获取最新章节", log)
searchBook.latestChapterTitle = analyzeRule.getString(ruleLastChapter) searchBook.latestChapterTitle = analyzeRule.getString(ruleLastChapter)
Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}", log) Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}", log)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取简介", log) Debug.log(bookSource.bookSourceUrl, "┌获取简介", log)
searchBook.intro = analyzeRule.getString(ruleIntro) searchBook.intro = analyzeRule.getString(ruleIntro)
Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", log, true) Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", log, true)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log) Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log)
analyzeRule.getString(ruleCoverUrl).let { analyzeRule.getString(ruleCoverUrl).let {
if (it.isNotEmpty()) searchBook.coverUrl = NetworkUtils.getAbsoluteURL(baseUrl, it) if (it.isNotEmpty()) searchBook.coverUrl = NetworkUtils.getAbsoluteURL(baseUrl, it)
} }
Debug.log(bookSource.bookSourceUrl, "${searchBook.coverUrl}", log) Debug.log(bookSource.bookSourceUrl, "${searchBook.coverUrl}", log)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取详情页链接", log) Debug.log(bookSource.bookSourceUrl, "┌获取详情页链接", log)
searchBook.bookUrl = analyzeRule.getString(ruleBookUrl, true) searchBook.bookUrl = analyzeRule.getString(ruleBookUrl, true)
if (searchBook.bookUrl.isEmpty()) { if (searchBook.bookUrl.isEmpty()) {

@ -161,7 +161,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
execute { execute {
App.db.bookSourceDao().allTextEnabled.forEach { source -> App.db.bookSourceDao().allTextEnabled.forEach { source ->
try { try {
val searchBooks = WebBook(source).searchBookSuspend(name) val searchBooks = WebBook(source).searchBookSuspend(this, name)
searchBooks.getOrNull(0)?.let { searchBooks.getOrNull(0)?.let {
if (it.name == name && (it.author == author || author == "")) { if (it.name == name && (it.author == author || author == "")) {
changeTo(it.toBook()) changeTo(it.toBook())

@ -49,33 +49,31 @@ class DiffCallBack(private val oldItems: List<SearchBook>, private val newItems:
} }
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
val payload = Bundle() val payload = Bundle()
if (oldItem.name != newItem.name) { val newItem = newItems.getOrNull(newItemPosition)
val oldItem = oldItems.getOrNull(oldItemPosition)
if (newItem == null) return payload
if (oldItem?.name != newItem.name) {
payload.putString("name", newItem.name) payload.putString("name", newItem.name)
} }
if (oldItem.author != newItem.author) { if (oldItem?.author != newItem.author) {
payload.putString("author", newItem.author) payload.putString("author", newItem.author)
} }
if (oldItem.origins.size != newItem.origins.size) { if (oldItem?.origins?.size != newItem.origins.size) {
payload.putInt("origins", newItem.origins.size) payload.putInt("origins", newItem.origins.size)
} }
if (oldItem.coverUrl != newItem.coverUrl) { if (oldItem?.coverUrl != newItem.coverUrl) {
payload.putString("cover", newItem.coverUrl) payload.putString("cover", newItem.coverUrl)
} }
if (oldItem.kind != newItem.kind) { if (oldItem?.kind != newItem.kind) {
payload.putString("kind", newItem.kind) payload.putString("kind", newItem.kind)
} }
if (oldItem.latestChapterTitle != newItem.latestChapterTitle) { if (oldItem?.latestChapterTitle != newItem.latestChapterTitle) {
payload.putString("last", newItem.latestChapterTitle) payload.putString("last", newItem.latestChapterTitle)
} }
if (oldItem.intro != newItem.intro) { if (oldItem?.intro != newItem.intro) {
payload.putString("intro", newItem.intro) payload.putString("intro", newItem.intro)
} }
if (payload.isEmpty) {
return null
}
return payload return payload
} }
} }

@ -16,6 +16,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.util.concurrent.Executors import java.util.concurrent.Executors
class SearchViewModel(application: Application) : BaseViewModel(application) { class SearchViewModel(application: Application) : BaseViewModel(application) {
@ -33,57 +34,59 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
* 开始搜索 * 开始搜索
*/ */
fun search(key: String) { fun search(key: String) {
task?.cancel() launch {
if (key.isEmpty() && searchKey.isEmpty()) { task?.cancel()
return if (key.isEmpty() && searchKey.isEmpty()) {
} else if (key.isEmpty()) { return@launch
isLoading = true } else if (key.isEmpty()) {
searchPage++ isLoading = true
} else if (key.isNotEmpty()) { searchPage++
isLoading = true } else if (key.isNotEmpty()) {
searchPage = 1 isLoading = true
searchKey = key searchPage = 1
searchBooks.clear() searchKey = key
} searchBooks.clear()
task = execute {
val searchGroup = context.getPrefString("searchGroup") ?: ""
val bookSourceList = if (searchGroup.isBlank()) {
App.db.bookSourceDao().allEnabled
} else {
App.db.bookSourceDao().getEnabledByGroup(searchGroup)
} }
for (item in bookSourceList) { task = execute {
//task取消时自动取消 by (scope = this@execute) val searchGroup = context.getPrefString("searchGroup") ?: ""
WebBook(item).searchBook( val bookSourceList = if (searchGroup.isBlank()) {
searchKey, App.db.bookSourceDao().allEnabled
searchPage, } else {
scope = this, App.db.bookSourceDao().getEnabledByGroup(searchGroup)
context = searchPool }
) for (item in bookSourceList) {
.timeout(30000L) //task取消时自动取消 by (scope = this@execute)
.onSuccess(IO) { WebBook(item).searchBook(
if (isActive) { searchKey,
it?.let { list -> searchPage,
if (context.getPrefBoolean(PreferKey.precisionSearch)) { scope = this,
precisionSearch(this, list) context = searchPool
} else { )
App.db.searchBookDao().insert(*list.toTypedArray()) .timeout(30000L)
mergeItems(this, list) .onSuccess(IO) {
if (isActive) {
it?.let { list ->
if (context.getPrefBoolean(PreferKey.precisionSearch)) {
precisionSearch(this, list)
} else {
App.db.searchBookDao().insert(*list.toTypedArray())
mergeItems(this, list)
}
} }
} }
} }
} }
}.onStart {
isSearchLiveData.postValue(true)
}.onCancel {
isSearchLiveData.postValue(false)
isLoading = false
} }
}.onStart {
isSearchLiveData.postValue(true)
}.onCancel {
isSearchLiveData.postValue(false)
isLoading = false
}
task?.invokeOnCompletion { task?.invokeOnCompletion {
isSearchLiveData.postValue(false) isSearchLiveData.postValue(false)
isLoading = false isLoading = false
}
} }
} }

@ -0,0 +1,17 @@
package io.legado.app.ui.rss.read
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.webkit.WebView
class VisibleWebView(
context: Context,
attrs: AttributeSet? = null
) : WebView(context, attrs) {
override fun onWindowVisibilityChanged(visibility: Int) {
super.onWindowVisibilityChanged(View.VISIBLE)
}
}

@ -15,7 +15,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<WebView <io.legado.app.ui.rss.read.VisibleWebView
android:id="@+id/web_view" android:id="@+id/web_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

Loading…
Cancel
Save