diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 482907c7c..d6a435788 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -8,6 +8,7 @@ * 解决看过书籍的移到顶部需要向上滚动才能看到的bug * 只有再书源被删除找不到书源时才会自动换源 * 美化界面by yangyxd +* 订阅后台播放 **2020/03/16** * 修复滚动模式切换章节位置不归0的bug diff --git a/app/src/main/java/io/legado/app/model/SearchBook.kt b/app/src/main/java/io/legado/app/model/SearchBook.kt new file mode 100644 index 000000000..7b344e60f --- /dev/null +++ b/app/src/main/java/io/legado/app/model/SearchBook.kt @@ -0,0 +1,5 @@ +package io.legado.app.model + +class SearchBook { + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/WebBook.kt b/app/src/main/java/io/legado/app/model/WebBook.kt index 7d14ea8bc..6fee7add6 100644 --- a/app/src/main/java/io/legado/app/model/WebBook.kt +++ b/app/src/main/java/io/legado/app/model/WebBook.kt @@ -29,11 +29,12 @@ class WebBook(val bookSource: BookSource) { context: CoroutineContext = Dispatchers.IO ): Coroutine> { return Coroutine.async(scope, context) { - searchBookSuspend(key, page) + searchBookSuspend(scope, key, page) } } suspend fun searchBookSuspend( + scope: CoroutineScope, key: String, page: Int? = 1 ): ArrayList { @@ -47,6 +48,7 @@ class WebBook(val bookSource: BookSource) { ) val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl) return BookList.analyzeBookList( + scope, res.body, bookSource, analyzeUrl, @@ -75,6 +77,7 @@ class WebBook(val bookSource: BookSource) { ) val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl) BookList.analyzeBookList( + scope, res.body, bookSource, analyzeUrl, diff --git a/app/src/main/java/io/legado/app/model/webBook/BookList.kt b/app/src/main/java/io/legado/app/model/webBook/BookList.kt index a205c950a..372a4eec6 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookList.kt @@ -9,11 +9,15 @@ import io.legado.app.model.Debug import io.legado.app.model.analyzeRule.AnalyzeRule import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.utils.NetworkUtils +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.isActive object BookList { @Throws(Exception::class) fun analyzeBookList( + scope: CoroutineScope, body: String?, bookSource: BookSource, analyzeUrl: AnalyzeUrl, @@ -28,12 +32,13 @@ object BookList { ) ) Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}") + if (!scope.isActive) throw CancellationException() val analyzeRule = AnalyzeRule(null) analyzeRule.setContent(body, baseUrl) bookSource.bookUrlPattern?.let { if (baseUrl.matches(it.toRegex())) { Debug.log(bookSource.bookSourceUrl, "≡链接为详情页") - getInfoItem(analyzeRule, bookSource, baseUrl)?.let { searchBook -> + getInfoItem(scope, analyzeRule, bookSource, baseUrl)?.let { searchBook -> searchBook.infoHtml = body bookList.add(searchBook) } @@ -59,7 +64,7 @@ object BookList { collections = analyzeRule.getElements(ruleList) if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) { Debug.log(bookSource.bookSourceUrl, "└列表为空,按详情页解析") - getInfoItem(analyzeRule, bookSource, baseUrl)?.let { searchBook -> + getInfoItem(scope, analyzeRule, bookSource, baseUrl)?.let { searchBook -> searchBook.infoHtml = body bookList.add(searchBook) } @@ -74,8 +79,9 @@ object BookList { val ruleWordCount = analyzeRule.splitSourceRule(bookListRule.wordCount) Debug.log(bookSource.bookSourceUrl, "└列表大小:${collections.size}") for ((index, item) in collections.withIndex()) { + if (!scope.isActive) throw CancellationException() getSearchItem( - item, analyzeRule, bookSource, baseUrl, index == 0, + scope, item, analyzeRule, bookSource, baseUrl, index == 0, ruleName = ruleName, ruleBookUrl = ruleBookUrl, ruleAuthor = ruleAuthor, ruleCoverUrl = ruleCoverUrl, ruleIntro = ruleIntro, ruleKind = ruleKind, ruleLastChapter = ruleLastChapter, ruleWordCount = ruleWordCount @@ -93,7 +99,9 @@ object BookList { return bookList } + @Throws(Exception::class) private fun getInfoItem( + scope: CoroutineScope, analyzeRule: AnalyzeRule, bookSource: BookSource, baseUrl: String @@ -108,29 +116,37 @@ object BookList { with(bookSource.getBookInfoRule()) { init?.let { if (it.isNotEmpty()) { + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则") analyzeRule.setContent(analyzeRule.getElement(it)) } } + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取书名") searchBook.name = analyzeRule.getString(name) Debug.log(bookSource.bookSourceUrl, "└${searchBook.name}") if (searchBook.name.isNotEmpty()) { + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取作者") searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(author)) Debug.log(bookSource.bookSourceUrl, "└${searchBook.author}") + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取分类") searchBook.kind = analyzeRule.getStringList(kind)?.joinToString(",") Debug.log(bookSource.bookSourceUrl, "└${searchBook.kind}") + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取字数") searchBook.wordCount = analyzeRule.getString(wordCount) Debug.log(bookSource.bookSourceUrl, "└${searchBook.wordCount}") + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取最新章节") searchBook.latestChapterTitle = analyzeRule.getString(lastChapter) Debug.log(bookSource.bookSourceUrl, "└${searchBook.latestChapterTitle}") + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取简介") searchBook.intro = analyzeRule.getString(intro) Debug.log(bookSource.bookSourceUrl, "└${searchBook.intro}", true) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取封面链接") searchBook.coverUrl = analyzeRule.getString(coverUrl, true) Debug.log(bookSource.bookSourceUrl, "└${searchBook.coverUrl}") @@ -140,7 +156,9 @@ object BookList { return null } + @Throws(Exception::class) private fun getSearchItem( + scope: CoroutineScope, item: Any, analyzeRule: AnalyzeRule, bookSource: BookSource, @@ -162,30 +180,38 @@ object BookList { searchBook.originOrder = bookSource.customOrder analyzeRule.book = searchBook analyzeRule.setContent(item) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取书名", log) searchBook.name = analyzeRule.getString(ruleName) Debug.log(bookSource.bookSourceUrl, "└${searchBook.name}", log) if (searchBook.name.isNotEmpty()) { + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取作者", log) searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(ruleAuthor)) Debug.log(bookSource.bookSourceUrl, "└${searchBook.author}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取分类", log) searchBook.kind = analyzeRule.getStringList(ruleKind)?.joinToString(",") Debug.log(bookSource.bookSourceUrl, "└${searchBook.kind}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取字数", log) searchBook.wordCount = analyzeRule.getString(ruleWordCount) Debug.log(bookSource.bookSourceUrl, "└${searchBook.wordCount}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取最新章节", log) searchBook.latestChapterTitle = analyzeRule.getString(ruleLastChapter) Debug.log(bookSource.bookSourceUrl, "└${searchBook.latestChapterTitle}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取简介", log) searchBook.intro = analyzeRule.getString(ruleIntro) Debug.log(bookSource.bookSourceUrl, "└${searchBook.intro}", log, true) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log) analyzeRule.getString(ruleCoverUrl).let { if (it.isNotEmpty()) searchBook.coverUrl = NetworkUtils.getAbsoluteURL(baseUrl, it) } Debug.log(bookSource.bookSourceUrl, "└${searchBook.coverUrl}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取详情页链接", log) searchBook.bookUrl = analyzeRule.getString(ruleBookUrl, true) if (searchBook.bookUrl.isEmpty()) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 25d94bdea..2994dcac2 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -161,7 +161,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { execute { App.db.bookSourceDao().allTextEnabled.forEach { source -> try { - val searchBooks = WebBook(source).searchBookSuspend(name) + val searchBooks = WebBook(source).searchBookSuspend(this, name) searchBooks.getOrNull(0)?.let { if (it.name == name && (it.author == author || author == "")) { changeTo(it.toBook()) diff --git a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt index 6f14699a9..999f2a941 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt @@ -49,33 +49,31 @@ class DiffCallBack(private val oldItems: List, private val newItems: } override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { - val oldItem = oldItems[oldItemPosition] - val newItem = newItems[newItemPosition] 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) } - if (oldItem.author != newItem.author) { + if (oldItem?.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) } - if (oldItem.coverUrl != newItem.coverUrl) { + if (oldItem?.coverUrl != newItem.coverUrl) { payload.putString("cover", newItem.coverUrl) } - if (oldItem.kind != newItem.kind) { + if (oldItem?.kind != newItem.kind) { payload.putString("kind", newItem.kind) } - if (oldItem.latestChapterTitle != newItem.latestChapterTitle) { + if (oldItem?.latestChapterTitle != newItem.latestChapterTitle) { payload.putString("last", newItem.latestChapterTitle) } - if (oldItem.intro != newItem.intro) { + if (oldItem?.intro != newItem.intro) { payload.putString("intro", newItem.intro) } - if (payload.isEmpty) { - return null - } return payload } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index f1a4fc858..6577c88fe 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import java.util.concurrent.Executors class SearchViewModel(application: Application) : BaseViewModel(application) { @@ -33,57 +34,59 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { * 开始搜索 */ fun search(key: String) { - task?.cancel() - if (key.isEmpty() && searchKey.isEmpty()) { - return - } else if (key.isEmpty()) { - isLoading = true - searchPage++ - } else if (key.isNotEmpty()) { - isLoading = true - searchPage = 1 - 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) + launch { + task?.cancel() + if (key.isEmpty() && searchKey.isEmpty()) { + return@launch + } else if (key.isEmpty()) { + isLoading = true + searchPage++ + } else if (key.isNotEmpty()) { + isLoading = true + searchPage = 1 + searchKey = key + searchBooks.clear() } - for (item in bookSourceList) { - //task取消时自动取消 by (scope = this@execute) - WebBook(item).searchBook( - searchKey, - searchPage, - scope = this, - context = searchPool - ) - .timeout(30000L) - .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) + 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取消时自动取消 by (scope = this@execute) + WebBook(item).searchBook( + searchKey, + searchPage, + scope = this, + context = searchPool + ) + .timeout(30000L) + .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 { - isSearchLiveData.postValue(false) - isLoading = false + task?.invokeOnCompletion { + isSearchLiveData.postValue(false) + isLoading = false + } } } diff --git a/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt b/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt new file mode 100644 index 000000000..0e6e1b40b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt @@ -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) + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_rss_read.xml b/app/src/main/res/layout/activity_rss_read.xml index 76f630002..ea4131764 100644 --- a/app/src/main/res/layout/activity_rss_read.xml +++ b/app/src/main/res/layout/activity_rss_read.xml @@ -15,7 +15,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> -