Compare commits

..

No commits in common. 'main' and 'dev' have entirely different histories.
main ... dev

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
</component>
</project>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="14">
<list size="7">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
@ -12,35 +13,21 @@
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
<item index="7" class="java.lang.String" itemvalue="org.jspecify.annotations.Nullable" />
<item index="8" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
<item index="11" class="java.lang.String" itemvalue="jakarta.annotation.Nullable" />
<item index="12" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="13" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="13">
<list size="6">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="7" class="java.lang.String" itemvalue="org.jspecify.annotations.NonNull" />
<item index="8" class="java.lang.String" itemvalue="jakarta.annotation.Nonnull" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
<item index="11" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="corretto-1.8" project-jdk-type="JavaSDK" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
</project>

@ -2,15 +2,9 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/NovelReader.iml" filepath="$PROJECT_DIR$/.idea/modules/NovelReader.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/NovelReader.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/NovelReader.app.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/NovelReader.app.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/NovelReader.app.androidTest.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/NovelReader.app.main.iml" filepath="$PROJECT_DIR$/.idea/modules/app/NovelReader.app.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/NovelReader.app.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/NovelReader.app.unitTest.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/basemvplib/NovelReader.basemvplib.iml" filepath="$PROJECT_DIR$/.idea/modules/basemvplib/NovelReader.basemvplib.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/basemvplib/NovelReader.basemvplib.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/basemvplib/NovelReader.basemvplib.androidTest.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/basemvplib/NovelReader.basemvplib.main.iml" filepath="$PROJECT_DIR$/.idea/modules/basemvplib/NovelReader.basemvplib.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/basemvplib/NovelReader.basemvplib.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/basemvplib/NovelReader.basemvplib.unitTest.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/MONKOVEL.iml" filepath="$PROJECT_DIR$/.idea/MONKOVEL.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
<module fileurl="file://$PROJECT_DIR$/basemvplib/basemvplib.iml" filepath="$PROJECT_DIR$/basemvplib/basemvplib.iml" />
</modules>
</component>
</project>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -4,6 +4,7 @@ apply plugin: 'org.greenrobot.greendao'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
defaultConfig {
applicationId "com.monke.monkeybook"
minSdkVersion 17

@ -1,15 +1,12 @@
// 声明包名
package com.monke.monkeybook;
import android.app.Application;// 导入Application类代表整个应用程序
import android.test.ApplicationTestCase;// 导入ApplicationTestCase类它提供了应用测试的基础
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
* Android
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
// 构造函数,参数为应用程序类的.class文件对象
public ApplicationTest() {
super(Application.class);
}

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.monke.monkeybook"> <!-- 声明包名 -->
package="com.monke.monkeybook">
<application
android:name=".MApplication"
@ -12,60 +12,39 @@
android:theme="@style/CAppTheme"
tools:replace="android:theme"
android:networkSecurityConfig="@xml/network_security_config">
<!-- 自定义应用程序类 -->
<!-- 允许备份 -->
<!-- 应用图标 -->
<!-- 应用名称 -->
<!-- 支持从右向左的布局 -->
<!-- 应用主题 -->
<!-- 指示工具覆盖主题 -->
<!-- 网络安全配置 -->
<!-- 欢迎界面 -->
<activity
android:name=".view.impl.WelcomeActivity"
android:screenOrientation="portrait">
<!-- 欢迎活动类 -->
<!-- 竖屏模式 -->
<intent-filter> <!-- 指定启动活动的意图过滤器 -->
<action android:name="android.intent.action.MAIN" /><!-- 主活动 -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /><!-- 启动器 -->
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 主活动 -->
<activity
android:name=".view.impl.MainActivity"
android:launchMode="singleTask" <!-- 单一任务模式 -->
android:screenOrientation="portrait" /><!-- 竖屏模式 -->
<!-- 搜索活动 -->
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<activity
android:name=".view.impl.SearchActivity"
android:configChanges="locale|keyboardHidden|orientation|screenSize"
android:screenOrientation="portrait"
android:theme="@style/CAppTransparentTheme"
android:windowSoftInputMode="stateHidden|adjustPan" /><!-- 软件输入法隐藏时更改视图 -->
android:windowSoftInputMode="stateHidden|adjustPan" />
<service android:name=".service.DownloadService" /><!-- 下载服务 -->
<service android:name=".service.DownloadService" />
<!-- 库活动 -->
<activity
android:name=".view.impl.LibraryActivity"
android:screenOrientation="portrait"
android:theme="@style/CAppTransparentTheme" />
<!-- 选择书籍活动 -->
<activity
android:name=".view.impl.ChoiceBookActivity"
android:screenOrientation="portrait" />
<!-- 书籍详情活动 -->
<activity
android:name=".view.impl.BookDetailActivity"
android:screenOrientation="portrait"
android:theme="@style/CAppTransparentTheme" />
<!-- 阅读书籍活动 -->
<activity
android:name=".view.impl.ReadBookActivity"
android:launchMode="singleTask"
@ -73,28 +52,24 @@
<intent-filter>
<action android:name="android.intent.action.VIEW"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<data android:mimeType="text/plain"></data><!-- 处理文本类型数据 -->
<data android:mimeType="text/plain"></data>
</intent-filter>
</activity>
<!-- 导入书籍活动 -->
<activity
android:name=".view.impl.ImportBookActivity"
android:screenOrientation="portrait"
android:theme="@style/CAppTransparentTheme" />
<!-- 元数据 -->
<meta-data
android:name="UMENG_CHANNEL_VALUE"
android:value="${UMENG_CHANNEL_VALUE}" /><!-- 用于第三方渠道的元数据 -->
android:value="${UMENG_CHANNEL_VALUE}" />
</application>
<!-- 权限要求 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><!-- 读取外部存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!-- 写入外部存储权限 -->
<uses-permission android:name="android.permission.INTERNET" /><!-- 访问互联网权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><!-- 访问网络状态权限 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><!-- 访问Wi-Fi状态权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" /><!-- 读取电话状态权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
</manifest>

@ -1,25 +1,25 @@
//定义当前类所在的包路径为"com.monke.monkeybook.common",表明它属于"monkeybook"项目的公共部分
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.common;
//定义一个名为 RxBusTag 的公共类包含一组常量字符串用于标识RxBus事件
public class RxBusTag {
//定义一个静态常量字符串,表示添加书籍的事件
public final static String HAD_ADD_BOOK = "rxbus_add_book";
//定义一个静态常量字符串,表示移除书籍的事件
public final static String HAD_REMOVE_BOOK = "rxbus_remove_book";
//定义一个静态常量字符串,表示更新书籍进度的事件
public final static String UPDATE_BOOK_PROGRESS = "rxbus_update_book_progress";
//定义一个静态常量字符串,表示暂停下载的监听事件
public final static String PAUSE_DOWNLOAD_LISTENER = "rxbus_pause_download_listener";
//定义一个静态常量字符串,表示下载进度监听事件
public final static String PROGRESS_DOWNLOAD_LISTENER = "rxbus_progress_download_listener";
//定义一个静态常量字符串,表示下载完成监听事件
public final static String FINISH_DOWNLOAD_LISTENER = "rxbus_finish_download_listener";
//定义一个静态常量字符串,表示暂停下载的事件
public final static String PAUSE_DOWNLOAD = "rxbus_pause_download";
//定义一个静态常量字符串,表示开始下载的事件
public final static String START_DOWNLOAD = "rxbus_start_download";
//定义一个静态常量字符串,表示取消下载的事件
public final static String CANCEL_DOWNLOAD = "rxbus_cancel_download";
//定义一个静态常量字符串,表示添加下载任务的事件
public final static String ADD_DOWNLOAD_TASK = "rxbus_add_download_task";
}

@ -1,59 +1,44 @@
//定义当前接口所在的包 com.monke.monkeybook.common.api
//这个包路径表示该接口用于应用程序的公共API部分。
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.common.api;
//引入所需要的库
import io.reactivex.Observable; //导入Observable类这是RxJava中的一个核心类表示一个可以发射数据的流
import retrofit2.http.GET; //导入Retrofit中的GET注解用于表示一个HTTP GET请求
import retrofit2.http.Headers; //导入Retrofit中的Headers注解用于向HTTP请求添加自定义头部信息。
import retrofit2.http.Url; //导入Retrofit中的Url注解用于动态传递URL。
import io.reactivex.Observable;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.Url;
/**
* API ( )
*/
//定义一个接口IEasouApi该接口包含了一些方法用于与宜搜小说的API进行交互。
public interface IEasouApi {
//定义获取书籍信息的方法通过HTTP GET请求并添加自定义请求头
@GET //定义GET请求
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //请求头:表示支持的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //设置User-Agent模拟浏览器请求
"Accept-Charset:UTF-8", //请求头:指定字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //禁用缓存
//返回值为Observable<String>这是一个异步操作最后会返回一个String类型的数据流
//@Url表示该方法接收一个动态URL作为参数
@GET
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> getBookInfo(@Url String url);
//定义搜索书籍的方法类似getBookInfo
@GET //定义GET请求
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //请求头:表示支持的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //设置User-Agent模拟浏览器请求
"Accept-Charset:UTF-8", //请求头:指定字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //禁用缓存
//该方法接受动态URL返回书籍搜索结果
@GET
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> searchBook(@Url String url);
//定义获取书籍内容的方法,类似前两个方法
@GET //定义GET请求
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //请求头:表示支持的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //设置User-Agent模拟浏览器请求
"Accept-Charset:UTF-8", //请求头:指定字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //禁用缓存
//该方法获取书籍的内容通过URL传递获取内容的地址
@GET
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> getBookContent(@Url String url);
//定义获取章节列表的方法,同样类似上述方法
@GET //定义GET请求
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //请求头:表示支持的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //设置User-Agent模拟浏览器请求
"Accept-Charset:UTF-8", //请求头:指定字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //禁用缓存
//该方法获取书籍的章节列表通过URL传递获取章节的地址
@GET
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> getChapterList(@Url String url);
}

@ -1,79 +1,61 @@
//定义包名,表明该接口属于 "monkeybook" 项目中的 API 部分
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.common.api;
//引入所需的库
import io.reactivex.Observable; //引入RxJava中的Observable用于定义响应式流
import retrofit2.http.Field; //引入Retrofit中的@Field 注解用于表示POST请求中的表单字段
import retrofit2.http.FormUrlEncoded; //引入Retrofit中的@FormUrlEncoded注解用于标记表单请求
import retrofit2.http.GET; //引入Retrofit中的@GET注解用于定义GET请求
import retrofit2.http.Headers; //引入Retrofit中的@Headers注解用于添加HTTP请求头
import retrofit2.http.Query; //引入Retrofit中的@Query注解用于在URL中添加查询参数
import retrofit2.http.Url; //引入Retrofit中的@Url注解用于动态传递URL
//该API接口定义了与小说网站进行交互的方法用于获取书籍信息、搜索书籍、获取书籍内容等。
import io.reactivex.Observable;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.Query;
import retrofit2.http.Url;
public interface IGxwztvApi {
//获取书籍信息的请求
@GET //定义GET请求
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //设置请求头:接受的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //设置User-Agent模拟浏览器请求
"Accept-Charset:UTF-8", //设置字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //禁用缓存
//该方法接收一个动态URL返回Observable<String>,表示异步的响应数据流
@GET
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> getBookInfo(@Url String url);
//搜索书籍的请求
@GET("/search.htm") //定义固定的GET请求路径为"/search.htm"
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //设置请求头:接受的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //设置User-Agent模拟浏览器请求
"Accept-Charset:UTF-8", //设置字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //禁用缓存
//使用@Query注解传递查询参数表示关键词和页码
@GET("/search.htm")
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> searchBook(@Query("keyword")String content, @Query("pn")int page);
//获取书籍内容的请求
@GET //定义GET请求
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //请求头:表示支持的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //设置User-Agent模拟浏览器请求
"Accept-Charset:UTF-8", //设置字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //禁用缓存
//该方法接收动态URL返回书籍的内容
@GET
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> getBookContent(@Url String url);
//获取章节列表的请求
@GET //定义GET请求
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //请求头:表示支持的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //设置User-Agent模拟浏览器请求
"Accept-Charset:UTF-8", //设置字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //禁用缓存
//该方法接收动态URL返回章节列表
@GET
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> getChapterList(@Url String url);
//获取书籍类型相关书籍的请求
@GET //定义GET请求
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //请求头:表示支持的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //设置User-Agent模拟浏览器请求
"Accept-Charset:UTF-8", //设置字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //禁用缓存
//该方法接收动态URL返回该类型的书籍
@GET
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> getKindBooks(@Url String url);
// 获取图书馆数据的请求
@GET //定义GET请求
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //请求头:表示支持的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //设置User-Agent模拟浏览器请求
"Accept-Charset:UTF-8", //设置字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //禁用缓存
//该方法接收动态URL返回图书馆相关的数据
@GET
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> getLibraryData(@Url String url);
}

@ -1,54 +1,43 @@
//定义包路径,表示该接口属于"monkeybook"项目中的公共API部分
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.common.api;
//引入所需的库
import io.reactivex.Observable; //引入RxJava中的Observable类表示异步流
import retrofit2.http.GET; //引入Retrofit中的@GET注解定义GET请求
import retrofit2.http.Headers; //引入Retrofit中的 @Headers 注解,用于添加自定义请求头
import retrofit2.http.Query; //引入Retrofit中的@Query注解用于在URL中添加查询参数
import retrofit2.http.Url; //引入Retrofit中的@Url注解表示动态传递URL
//该API接口定义了与零点看书小说网站进行交互的方法用于获取书籍信息、搜索书籍、获取书籍内容等。
import io.reactivex.Observable;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.Query;
import retrofit2.http.Url;
public interface ILingdiankanshuApi {
//获取书籍信息的请求
@GET //定义GET请求
//设置自定义的HTTP请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //请求头:表示支持的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //模拟浏览器的User-Agent
"Accept-Charset:UTF-8", //请求头:指定字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //请求头:禁用缓存
//该方法接受一个动态URL返回一个Observable<String>类型的响应流,表示书籍信息
@GET
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> getBookInfo(@Url String url);
//搜索书籍的请求
@GET("/cse/search") //定义GET请求的URL路径"/cse/search"
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //请求头:表示支持的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //模拟浏览器的User-Agent
"Accept-Charset:UTF-8", //请求头:指定字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //请求头:禁用缓存
//该方法接受三个查询参数:书籍关键词`q`,页码`p`,以及时间`s`,返回书籍搜索结果
@GET("/cse/search")
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> searchBook(@Query("q") String content, @Query("p") int page, @Query("s") String time);
//获取书籍内容的请求
@GET //定义GET请求
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //请求头:表示支持的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //模拟浏览器的User-Agent
"Accept-Charset:UTF-8", //请求头:指定字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //请求头:禁用缓存
//该方法接受一个动态URL返回书籍内容
@GET
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> getBookContent(@Url String url);
@GET //定义GET请求
//设置请求头
@Headers({"Accept:text/html,application/xhtml+xml,application/xml", //请求头:表示支持的响应类型
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3", //模拟浏览器的User-Agent
"Accept-Charset:UTF-8", //请求头:指定字符集为 UTF-8
"Connection:close", //请求头:请求完成后关闭连接
"Cache-Control:no-cache"}) //请求头:禁用缓存
//该方法接受一个动态URL返回书籍的章节列表
@GET
@Headers({"Accept:text/html,application/xhtml+xml,application/xml",
"User-Agent:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3",
"Accept-Charset:UTF-8",
"Connection:close",
"Cache-Control:no-cache"})
Observable<String> getChapterList(@Url String url);
}

@ -1,177 +1,143 @@
//指定了当前类BookContentBeanDao所在的包路径即com.monke.monkeybook.dao
package com.monke.monkeybook.dao;
//导入必要的类包含GreenDAO、SQLite和Android数据库操作的相关类
import android.database.Cursor;// 导入Cursor类用于数据库查询结果
import android.database.sqlite.SQLiteStatement;// 导入SQLiteStatement类用于预编译SQL语句
import org.greenrobot.greendao.AbstractDao;// 导入AbstractDao类这是GreenDAO的基类用于实现DAO功能
import org.greenrobot.greendao.Property;// 导入Property类用于属性映射
import org.greenrobot.greendao.internal.DaoConfig;// 导入DaoConfig类包含DAO的配置
import org.greenrobot.greendao.database.Database;// 导入Database类代表数据库操作
import org.greenrobot.greendao.database.DatabaseStatement;// 导入DatabaseStatement类用于执行数据库语句
// 导入实体类 BookContentBean
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.Property;
import org.greenrobot.greendao.internal.DaoConfig;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.DatabaseStatement;
import com.monke.monkeybook.bean.BookContentBean;
// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
/**
/**
* DAO for table "BOOK_CONTENT_BEAN".
*/
//继承自GreenDAO的AbstractDao类BookContentBean为实体类String为主键类型
*/
public class BookContentBeanDao extends AbstractDao<BookContentBean, String> {
//定义常量TABLENAME表示该表的名称
public static final String TABLENAME = "BOOK_CONTENT_BEAN";// 表名常量
public static final String TABLENAME = "BOOK_CONTENT_BEAN";
/**
* Properties of entity BookContentBean.<br/>
* Can be used for QueryBuilder and for referencing column names.
* BookContentBeanQueryBuilder
*/
// 定义实体类属性映射:对应数据库表字段名的属性
*/
public static class Properties {
public final static Property DurChapterUrl = new Property(0, String.class, "durChapterUrl", true, "DUR_CHAPTER_URL");// 章节URL属性
public final static Property DurChapterIndex = new Property(1, int.class, "durChapterIndex", false, "DUR_CHAPTER_INDEX");// 章节索引属性
public final static Property DurCapterContent = new Property(2, String.class, "durCapterContent", false, "DUR_CAPTER_CONTENT");// 章节内容属性
public final static Property Tag = new Property(3, String.class, "tag", false, "TAG");// 标签属性
public final static Property DurChapterUrl = new Property(0, String.class, "durChapterUrl", true, "DUR_CHAPTER_URL");
public final static Property DurChapterIndex = new Property(1, int.class, "durChapterIndex", false, "DUR_CHAPTER_INDEX");
public final static Property DurCapterContent = new Property(2, String.class, "durCapterContent", false, "DUR_CAPTER_CONTENT");
public final static Property Tag = new Property(3, String.class, "tag", false, "TAG");
};
//构造函数传入DaoConfig用于初始化DAO
public BookContentBeanDao(DaoConfig config) {
super(config); //调用父类构造函数初始化Dao配置
super(config);
}
// 构造函数传入DaoConfig和DaoSession用于初始化DAO和会话管理
public BookContentBeanDao(DaoConfig config, DaoSession daoSession) {
super(config, daoSession); //调用父类构造函数初始化Dao配置和会话
super(config, daoSession);
}
/**
* Creates the underlying database table.
*
*/
/** Creates the underlying database table. */
public static void createTable(Database db, boolean ifNotExists) {
//如果ifNotExists为true则在创建表时确保该表不存在
String constraint = ifNotExists? "IF NOT EXISTS ": "";
//使用SQL执行表创建语句
db.execSQL("CREATE TABLE " + constraint + "\"BOOK_CONTENT_BEAN\" (" + //
"\"DUR_CHAPTER_URL\" TEXT PRIMARY KEY NOT NULL ," + // 0: durChapterUrl, 主键列
"\"DUR_CHAPTER_INDEX\" INTEGER NOT NULL ," + // 1: durChapterIndex, 整数列
"\"DUR_CAPTER_CONTENT\" TEXT," + // 2: durCapterContent,章节内容列
"\"TAG\" TEXT);"); // 3: tag, 标签列
"\"DUR_CHAPTER_URL\" TEXT PRIMARY KEY NOT NULL ," + // 0: durChapterUrl
"\"DUR_CHAPTER_INDEX\" INTEGER NOT NULL ," + // 1: durChapterIndex
"\"DUR_CAPTER_CONTENT\" TEXT," + // 2: durCapterContent
"\"TAG\" TEXT);"); // 3: tag
}
/** Drops the underlying database table.
*
*/
/** Drops the underlying database table. */
public static void dropTable(Database db, boolean ifExists) {
//使用SQL删除表确保表存在时删除
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"BOOK_CONTENT_BEAN\"";// 生成删除表的SQL语句
db.execSQL(sql);// 执行删除表操作
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"BOOK_CONTENT_BEAN\"";
db.execSQL(sql);
}
@Override
protected final void bindValues(DatabaseStatement stmt, BookContentBean entity) {
//清除绑定的值,确保每次绑定时都是干净的
stmt.clearBindings();// 清除先前绑定的参数
//绑定durChapterUrl字段到SQL语句的第一个位置
String durChapterUrl = entity.getDurChapterUrl();// 获取durChapterUrl属性值
stmt.clearBindings();
String durChapterUrl = entity.getDurChapterUrl();
if (durChapterUrl != null) {
//将章节URL字符串绑定到SQL语句的第一个位置
stmt.bindString(1, durChapterUrl);// 将durChapterUrl绑定到SQL语句的第一个参数
stmt.bindString(1, durChapterUrl);
}
//绑定durChapterIndex字段到SQL语句的第二个位置
stmt.bindLong(2, entity.getDurChapterIndex());//将章节索引绑定到SQL语句的第二个位置
//绑定durCapterContent字段到SQL语句的第三个位置
String durCapterContent = entity.getDurCapterContent();// 获取durCapterContent属性值
stmt.bindLong(2, entity.getDurChapterIndex());
String durCapterContent = entity.getDurCapterContent();
if (durCapterContent != null) {
//将章节内容绑定到SQL语句的第三个位置
stmt.bindString(3, durCapterContent);// 将durCapterContent绑定到SQL语句的第三个参数
stmt.bindString(3, durCapterContent);
}
//绑定tag字段到SQL语句的第四个位置
String tag = entity.getTag();// 获取tag属性值
String tag = entity.getTag();
if (tag != null) {
//将标签绑定到SQL语句的第四个位置
stmt.bindString(4, tag);// 将tag绑定到SQL语句的第四个参数
stmt.bindString(4, tag);
}
}
@Override
protected final void bindValues(SQLiteStatement stmt, BookContentBean entity) {
//清除绑定的值,确保每次绑定时都是干净的
stmt.clearBindings();// 清除先前绑定的参数
//绑定durChapterUrl字段到SQL语句的第一个位置
String durChapterUrl = entity.getDurChapterUrl();// 获取durChapterUrl属性值
stmt.clearBindings();
String durChapterUrl = entity.getDurChapterUrl();
if (durChapterUrl != null) {
//将章节URL字符串绑定到SQL语句的第一个位置
stmt.bindString(1, durChapterUrl);// 将durChapterUrl绑定到SQL语句的第一个参数
stmt.bindString(1, durChapterUrl);
}
//绑定durChapterIndex字段到SQL语句的第二个位置
stmt.bindLong(2, entity.getDurChapterIndex()); //将章节索引绑定到SQL语句的第二个位置
//绑定durCapterContent字段到SQL语句的第三个位置
String durCapterContent = entity.getDurCapterContent();// 获取durCapterContent属性值
stmt.bindLong(2, entity.getDurChapterIndex());
String durCapterContent = entity.getDurCapterContent();
if (durCapterContent != null) {
//将章节内容绑定到SQL语句的第三个位置
stmt.bindString(3, durCapterContent);// 将durCapterContent绑定到SQL语句的第三个参数
stmt.bindString(3, durCapterContent);
}
//绑定tag字段到SQL语句的第四个位置
String tag = entity.getTag();// 获取tag属性值
String tag = entity.getTag();
if (tag != null) {
//将标签绑定到SQL语句的第四个位置
stmt.bindString(4, tag);// 将tag绑定到SQL语句的第四个参数
stmt.bindString(4, tag);
}
}
@Override
public String readKey(Cursor cursor, int offset) {
//从Cursor中读取主键(durChapterUrl)若为null则返回null
return cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0);// 根据游标读取主键值
}
return cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0);
}
@Override
public BookContentBean readEntity(Cursor cursor, int offset) {
//根据Cursor数据构建一个BookContentBean实体类对象并返回
BookContentBean entity = new BookContentBean( //
cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0), // durChapterUrl
cursor.getInt(offset + 1), // durChapterIndex
cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // durCapterContent
cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3) // tag
cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0), // durChapterUrl
cursor.getInt(offset + 1), // durChapterIndex
cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // durCapterContent
cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3) // tag
);
return entity;// 返回创建的实体对象
return entity;
}
@Override
public void readEntity(Cursor cursor, BookContentBean entity, int offset) {
//从Cursor中读取值并设置到BookContentBean对象的相应属性中
entity.setDurChapterUrl(cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0));// 设置durChapterUrl属性
entity.setDurChapterIndex(cursor.getInt(offset + 1));// 设置durChapterIndex属性
entity.setDurCapterContent(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2));// 设置durCapterContent属性
entity.setTag(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3));// 设置tag属性
}
entity.setDurChapterUrl(cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0));
entity.setDurChapterIndex(cursor.getInt(offset + 1));
entity.setDurCapterContent(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2));
entity.setTag(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3));
}
@Override
protected final String updateKeyAfterInsert(BookContentBean entity, long rowId) {
//返回更新时需要的主键字段这里是durChapterUrl
return entity.getDurChapterUrl();// 返回用于更新的主键
return entity.getDurChapterUrl();
}
@Override
public String getKey(BookContentBean entity) {
//获取实体对象的主键值返回durChapterUrl
if(entity != null) {
return entity.getDurChapterUrl();// 返回实体的主键值
return entity.getDurChapterUrl();
} else {
return null;// 如果实体为空返回null
return null;
}
}
@Override
protected final boolean isEntityUpdateable() {
//指示实体是否可以被更新这里返回true表示支持更新
return true;// 表示该实体可被更新
return true;
}
}

@ -1,271 +1,213 @@
//定义类所在的包路径,表示该文件属于com.monke.monkeybook.dao包
package com.monke.monkeybook.dao;
//导入必要的类包含GreenDAO和Android数据库操作的相关类
import android.database.Cursor;// 导入Cursor类用于数据库查询结果的处理
import android.database.sqlite.SQLiteStatement;//导入SQLiteStatement类用于执行SQL语句
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import org.greenrobot.greendao.AbstractDao;//导入AbstractDao类这是一个GreenDao框架的抽象类用于定义数据库操作的基类
import org.greenrobot.greendao.Property;//导入Property类用于定义数据库表中的字段属性
import org.greenrobot.greendao.internal.DaoConfig;//导入DaoConfig类用于配置DAO对象
import org.greenrobot.greendao.database.Database;//导入Database类用于操作数据库
import org.greenrobot.greendao.database.DatabaseStatement;//导入BookInfoBean实体类
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.Property;
import org.greenrobot.greendao.internal.DaoConfig;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.DatabaseStatement;
import com.monke.monkeybook.bean.BookInfoBean;//导入BookInfoBean类这是数据库中要操作的实体类
import com.monke.monkeybook.bean.BookInfoBean;
// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
/**
/**
* DAO for table "BOOK_INFO_BEAN".
* DAO "BOOK_INFO_BEAN"
*/
//BookInfoBeanDao继承自AbstractDao操作BookInfoBean对象主键类型为String
*/
public class BookInfoBeanDao extends AbstractDao<BookInfoBean, String> {
//定义常量TABLENAME表示数据库表名
public static final String TABLENAME = "BOOK_INFO_BEAN";
/**
* Properties of entity BookInfoBean.<br/>
* Can be used for QueryBuilder and for referencing column names.
* BookInfoBean QueryBuilder
*/
*/
public static class Properties {
//定义实体类属性映射:对应数据库表字段名的属性
public final static Property Name = new Property(0, String.class, "name", false, "NAME");//属性0名称nameString类型非主键数据库列名NAME
public final static Property Tag = new Property(1, String.class, "tag", false, "TAG");//属性1标签tagString类型非主键数据库列名TAG
public final static Property NoteUrl = new Property(2, String.class, "noteUrl", true, "NOTE_URL");// 属性2笔记URLnoteUrlString类型主键非空数据库列名NOTE_URL
public final static Property ChapterUrl = new Property(3, String.class, "chapterUrl", false, "CHAPTER_URL");//属性3章节URLchapterUrlString类型非主键数据库列名CHAPTER_URL
public final static Property FinalRefreshData = new Property(4, long.class, "finalRefreshData", false, "FINAL_REFRESH_DATA");//属性4最后刷新时间finalRefreshDatalong类型非主键数据库列名FINAL_REFRESH_DATA
public final static Property CoverUrl = new Property(5, String.class, "coverUrl", false, "COVER_URL");//属性5封面URLcoverUrlString类型非主键数据库列名COVER_URL
public final static Property Author = new Property(6, String.class, "author", false, "AUTHOR");//属性6作者authorString类型非主键数据库列名AUTHOR
public final static Property Introduce = new Property(7, String.class, "introduce", false, "INTRODUCE");//属性7简介introduceString类型非主键数据库列名INTRODUCE
public final static Property Origin = new Property(8, String.class, "origin", false, "ORIGIN");//属性8来源originString类型非主键数据库列名ORIGIN
public final static Property Name = new Property(0, String.class, "name", false, "NAME");
public final static Property Tag = new Property(1, String.class, "tag", false, "TAG");
public final static Property NoteUrl = new Property(2, String.class, "noteUrl", true, "NOTE_URL");
public final static Property ChapterUrl = new Property(3, String.class, "chapterUrl", false, "CHAPTER_URL");
public final static Property FinalRefreshData = new Property(4, long.class, "finalRefreshData", false, "FINAL_REFRESH_DATA");
public final static Property CoverUrl = new Property(5, String.class, "coverUrl", false, "COVER_URL");
public final static Property Author = new Property(6, String.class, "author", false, "AUTHOR");
public final static Property Introduce = new Property(7, String.class, "introduce", false, "INTRODUCE");
public final static Property Origin = new Property(8, String.class, "origin", false, "ORIGIN");
};
//构造函数使用DaoConfig配置创建DAO对象
public BookInfoBeanDao(DaoConfig config) {
super(config);// 调用父类的构造函数传入DaoConfig配置
super(config);
}
//构造函数使用DaoConfig和DaoSession配置创建DAO对象
public BookInfoBeanDao(DaoConfig config, DaoSession daoSession) {
super(config, daoSession);// 调用父类的构造函数传入DaoConfig和DaoSession配置
super(config, daoSession);
}
/** Creates the underlying database table.
*
*/
//创建数据库表的方法
/** Creates the underlying database table. */
public static void createTable(Database db, boolean ifNotExists) {
//判断是否需要添加IF NOT EXISTS条件
String constraint = ifNotExists? "IF NOT EXISTS ": "";
//使用SQL执行表创建语句
db.execSQL("CREATE TABLE " + constraint + "\"BOOK_INFO_BEAN\" (" +
"\"NAME\" TEXT," + //NAME字段TEXT类型
"\"TAG\" TEXT," + //TAG字段TEXT类型
"\"NOTE_URL\" TEXT PRIMARY KEY NOT NULL ," + //NOTE_URL字段TEXT类型主键非空
"\"CHAPTER_URL\" TEXT," + //CHAPTER_URL字段TEXT类型
"\"FINAL_REFRESH_DATA\" INTEGER NOT NULL ," + //FINAL_REFRESH_DATA字段INTEGER类型非空
"\"COVER_URL\" TEXT," + //COVER_URL字段TEXT类型
"\"AUTHOR\" TEXT," + //AUTHOR字段TEXT类型
"\"INTRODUCE\" TEXT," + //INTRODUCE字段TEXT类型
"\"ORIGIN\" TEXT);"); //ORIGIN字段TEXT类型
db.execSQL("CREATE TABLE " + constraint + "\"BOOK_INFO_BEAN\" (" + //
"\"NAME\" TEXT," + // 0: name
"\"TAG\" TEXT," + // 1: tag
"\"NOTE_URL\" TEXT PRIMARY KEY NOT NULL ," + // 2: noteUrl
"\"CHAPTER_URL\" TEXT," + // 3: chapterUrl
"\"FINAL_REFRESH_DATA\" INTEGER NOT NULL ," + // 4: finalRefreshData
"\"COVER_URL\" TEXT," + // 5: coverUrl
"\"AUTHOR\" TEXT," + // 6: author
"\"INTRODUCE\" TEXT," + // 7: introduce
"\"ORIGIN\" TEXT);"); // 8: origin
}
/** Drops the underlying database table.
*
*/
/** Drops the underlying database table. */
public static void dropTable(Database db, boolean ifExists) {
//使用SQL删除表确保表存在时删除
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"BOOK_INFO_BEAN\"";//构造删除表的SQL语句
db.execSQL(sql);//执行删除表的SQL语句
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"BOOK_INFO_BEAN\"";
db.execSQL(sql);
}
@Override
//将BookInfoBean实体对象的值绑定到DatabaseStatement对象中
protected final void bindValues(DatabaseStatement stmt, BookInfoBean entity) {
//清除绑定的值,确保每次绑定时都是干净的
stmt.clearBindings();// 清除之前的绑定值
//绑定name字段到SQL语句的第一个位置
String name = entity.getName();// 获取name属性值
stmt.clearBindings();
String name = entity.getName();
if (name != null) {
//将书名绑定到SQL语句的第一个位置
stmt.bindString(1, name);// 将name绑定到SQL语句的第一个位置
stmt.bindString(1, name);
}
//绑定tag字段到SQL语句的第二个位置
String tag = entity.getTag();// 获取tag属性值
String tag = entity.getTag();
if (tag != null) {
//将标签绑定到SQL语句的第二个位置
stmt.bindString(2, tag);// 将tag绑定到SQL语句的第二个位置
stmt.bindString(2, tag);
}
//绑定noteUrl字段到SQL语句的第三个位置
String noteUrl = entity.getNoteUrl(); // 获取noteUrl属性值
String noteUrl = entity.getNoteUrl();
if (noteUrl != null) {
//将书籍URL绑定到SQL语句的第三个位置
stmt.bindString(3, noteUrl);// 将noteUrl绑定到SQL语句的第三个位置
stmt.bindString(3, noteUrl);
}
//绑定chapterUrl字段到SQL语句的第四个位置
String chapterUrl = entity.getChapterUrl();// 获取chapterUrl属性值
String chapterUrl = entity.getChapterUrl();
if (chapterUrl != null) {
//将章节URL绑定到SQL语句的第四个位置
stmt.bindString(4, chapterUrl);// 将chapterUrl绑定到SQL语句的第四个位置
stmt.bindString(4, chapterUrl);
}
//将最后刷新时间long 类型绑定到SQL语句的第五个位置
stmt.bindLong(5, entity.getFinalRefreshData()); // 将finalRefreshData绑定到SQL语句的第五个位置
//绑定coverUrl字段到SQL语句的第六个位置
String coverUrl = entity.getCoverUrl();// 获取coverUrl属性值
stmt.bindLong(5, entity.getFinalRefreshData());
String coverUrl = entity.getCoverUrl();
if (coverUrl != null) {
//将封面图URL绑定到SQL语句的第六个位置
stmt.bindString(6, coverUrl);// 将coverUrl绑定到SQL语句的第六个位置
stmt.bindString(6, coverUrl);
}
//绑定author字段到SQL语句的第七个位置
String author = entity.getAuthor();// 获取author属性值
String author = entity.getAuthor();
if (author != null) {
//将作者绑定到SQL语句的第七个位置
stmt.bindString(7, author);// 将author绑定到SQL语句的第七个位置
stmt.bindString(7, author);
}
//绑定introduce字段到SQL语句的第八个位置
String introduce = entity.getIntroduce();// 获取introduce属性值
String introduce = entity.getIntroduce();
if (introduce != null) {
//将简介绑定到SQL语句的第八个位置
stmt.bindString(8, introduce);// 将introduce绑定到SQL语句的第八个位置
stmt.bindString(8, introduce);
}
//绑定origin字段到SQL语句的第九个位置
String origin = entity.getOrigin();// 获取origin属性值
String origin = entity.getOrigin();
if (origin != null) {
//将来源绑定到SQL语句的第九个位置
stmt.bindString(9, origin);// 将origin绑定到SQL语句的第九个位置
stmt.bindString(9, origin);
}
}
@Override
//将BookInfoBean实体对象的值绑定到SQLiteStatement对象中与上一个方法功能类似只是对象不同
protected final void bindValues(SQLiteStatement stmt, BookInfoBean entity) {
//清除绑定的值,确保每次绑定时都是干净的
stmt.clearBindings();
//绑定name字段值
String name = entity.getName();// 获取name属性值
String name = entity.getName();
if (name != null) {
stmt.bindString(1, name);// 将name绑定到SQL语句的第一个位置
stmt.bindString(1, name);
}
//绑定tag字段值
String tag = entity.getTag();// 获取tag属性值
String tag = entity.getTag();
if (tag != null) {
stmt.bindString(2, tag);// 将tag绑定到SQL语句的第二个位置
stmt.bindString(2, tag);
}
//绑定noteUrl字段值
String noteUrl = entity.getNoteUrl();// 获取noteUrl属性值
String noteUrl = entity.getNoteUrl();
if (noteUrl != null) {
stmt.bindString(3, noteUrl);// 将noteUrl绑定到SQL语句的第三个位置
stmt.bindString(3, noteUrl);
}
//绑定chapterUrl字段值
String chapterUrl = entity.getChapterUrl();// 获取chapterUrl属性值
String chapterUrl = entity.getChapterUrl();
if (chapterUrl != null) {
stmt.bindString(4, chapterUrl);// 将chapterUrl绑定到SQL语句的第四个位置
stmt.bindString(4, chapterUrl);
}
//绑定finalRefreshData字段值
stmt.bindLong(5, entity.getFinalRefreshData());// 将finalRefreshData绑定到SQL语句的第五个位置
//绑定coverUrl字段值
String coverUrl = entity.getCoverUrl();// 获取coverUrl属性值
stmt.bindLong(5, entity.getFinalRefreshData());
String coverUrl = entity.getCoverUrl();
if (coverUrl != null) {
stmt.bindString(6, coverUrl);// 将coverUrl绑定到SQL语句的第六个位置
stmt.bindString(6, coverUrl);
}
//绑定author字段值
String author = entity.getAuthor();// 获取author属性值
String author = entity.getAuthor();
if (author != null) {
stmt.bindString(7, author);// 将author绑定到SQL语句的第七个位置
stmt.bindString(7, author);
}
//绑定introduce字段值
String introduce = entity.getIntroduce();// 获取introduce属性值
String introduce = entity.getIntroduce();
if (introduce != null) {
stmt.bindString(8, introduce);// 将introduce绑定到SQL语句的第八个参数
stmt.bindString(8, introduce);
}
//绑定origin字段值
String origin = entity.getOrigin();// 获取来源属性值
String origin = entity.getOrigin();
if (origin != null) {
stmt.bindString(9, origin);// 将来源绑定到SQL语句的第九个参数
stmt.bindString(9, origin);
}
}
@Override
//从Cursor中读取主键
public String readKey(Cursor cursor, int offset) {
//主键在第三列(offset + 2)如果为空返回null否则返回字符串
return cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2);// 读取主键noteUrl字段
}
return cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2);
}
@Override
//从Cursor中读取BookInfoBean实体
public BookInfoBean readEntity(Cursor cursor, int offset) {
//创建BookInfoBean对象
BookInfoBean entity = new BookInfoBean( //
cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0), // name
cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1), // tag
cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // noteUrl
cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // chapterUrl
cursor.getLong(offset + 4), // finalRefreshData
cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5), // coverUrl
cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6), // author
cursor.isNull(offset + 7) ? null : cursor.getString(offset + 7), // introduce
cursor.isNull(offset + 8) ? null : cursor.getString(offset + 8) // origin
cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0), // name
cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1), // tag
cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // noteUrl
cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // chapterUrl
cursor.getLong(offset + 4), // finalRefreshData
cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5), // coverUrl
cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6), // author
cursor.isNull(offset + 7) ? null : cursor.getString(offset + 7), // introduce
cursor.isNull(offset + 8) ? null : cursor.getString(offset + 8) // origin
);
//返回创建好的BookInfoBean对象
return entity;// 返回读取到的实体对象
return entity;
}
@Override
//将Cursor中的数据读取到已存在的BookInfoBean实体中
public void readEntity(Cursor cursor, BookInfoBean entity, int offset) {
entity.setName(cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0));//设置name
entity.setTag(cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1));//设置tag
entity.setNoteUrl(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2));//设置noteUrl
entity.setChapterUrl(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3));//设置chapterUrl
entity.setFinalRefreshData(cursor.getLong(offset + 4));//设置finalRefreshData
entity.setCoverUrl(cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5));//设置coverUrl
entity.setAuthor(cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6));//设置author
entity.setIntroduce(cursor.isNull(offset + 7) ? null : cursor.getString(offset + 7));//设置introduce
entity.setOrigin(cursor.isNull(offset + 8) ? null : cursor.getString(offset + 8));//设置origin
}
entity.setName(cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0));
entity.setTag(cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1));
entity.setNoteUrl(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2));
entity.setChapterUrl(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3));
entity.setFinalRefreshData(cursor.getLong(offset + 4));
entity.setCoverUrl(cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5));
entity.setAuthor(cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6));
entity.setIntroduce(cursor.isNull(offset + 7) ? null : cursor.getString(offset + 7));
entity.setOrigin(cursor.isNull(offset + 8) ? null : cursor.getString(offset + 8));
}
@Override
//插入数据后更新主键
protected final String updateKeyAfterInsert(BookInfoBean entity, long rowId) {
//使用noteUrl作为主键
return entity.getNoteUrl();// 返回用于更新的主键
return entity.getNoteUrl();
}
@Override
//获取主键
public String getKey(BookInfoBean entity) {
if(entity != null) {
//返回noteUrl作为主键
return entity.getNoteUrl();// 返回实体的主键noteUrl
return entity.getNoteUrl();
} else {
return null;// 如果实体为空返回null
return null;
}
}
@Override
//是否可更新实体
protected final boolean isEntityUpdateable() {
//可更新
return true;// 表示该实体可更新
return true;
}
}

@ -1,174 +1,141 @@
// 包声明声明该类位于com.monke.monkeybook.dao包下
package com.monke.monkeybook.dao;
import android.database.Cursor; // 导入Cursor类用于从数据库游标中读取数据
import android.database.sqlite.SQLiteStatement; // 导入SQLiteStatement类用于执行预编译的SQL语句
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import org.greenrobot.greendao.AbstractDao; // 导入AbstractDao类GreenDao框架中用于数据库操作的抽象基类
import org.greenrobot.greendao.Property; // 导入Property类用于定义数据库表字段的属性
import org.greenrobot.greendao.internal.DaoConfig; // 导入DaoConfig类用于配置Dao对象的配置信息
import org.greenrobot.greendao.database.Database; // 导入Database类GreenDao框架中用于数据库操作的类
import org.greenrobot.greendao.database.DatabaseStatement; // 导入DatabaseStatement类用于执行SQL语句
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.Property;
import org.greenrobot.greendao.internal.DaoConfig;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.DatabaseStatement;
// 导入BookShelfBean类这是数据库操作的对象
import com.monke.monkeybook.bean.BookShelfBean;
// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
/**
/**
* DAO for table "BOOK_SHELF_BEAN".
*/
// BookShelfBeanDao类继承AbstractDao操作BookShelfBean对象主键类型为String
*/
public class BookShelfBeanDao extends AbstractDao<BookShelfBean, String> {
// 数据库表名常量
public static final String TABLENAME = "BOOK_SHELF_BEAN";
/**
* Properties of entity BookShelfBean.<br/>
* Can be used for QueryBuilder and for referencing column names.
*/
*/
public static class Properties {
public final static Property NoteUrl = new Property(0, String.class, "noteUrl", true, "NOTE_URL"); // 属性0noteUrlString类型主键数据库列名NOTE_URL
public final static Property DurChapter = new Property(1, int.class, "durChapter", false, "DUR_CHAPTER"); // 属性1durChapterint类型非主键数据库列名DUR_CHAPTER
public final static Property DurChapterPage = new Property(2, int.class, "durChapterPage", false, "DUR_CHAPTER_PAGE"); // 属性2durChapterPageint类型非主键数据库列名DUR_CHAPTER_PAGE
public final static Property FinalDate = new Property(3, long.class, "finalDate", false, "FINAL_DATE"); // 属性3finalDatelong类型非主键数据库列名FINAL_DATE
public final static Property Tag = new Property(4, String.class, "tag", false, "TAG"); // 属性4tagString类型非主键数据库列名TAG
public final static Property NoteUrl = new Property(0, String.class, "noteUrl", true, "NOTE_URL");
public final static Property DurChapter = new Property(1, int.class, "durChapter", false, "DUR_CHAPTER");
public final static Property DurChapterPage = new Property(2, int.class, "durChapterPage", false, "DUR_CHAPTER_PAGE");
public final static Property FinalDate = new Property(3, long.class, "finalDate", false, "FINAL_DATE");
public final static Property Tag = new Property(4, String.class, "tag", false, "TAG");
};
// 构造函数使用DaoConfig配置初始化Dao
public BookShelfBeanDao(DaoConfig config) {
super(config);// 调用父类构造函数进行初始化
super(config);
}
// 构造函数使用DaoConfig和DaoSession配置初始化Dao
public BookShelfBeanDao(DaoConfig config, DaoSession daoSession) {
super(config, daoSession);// 调用父类构造函数进行初始化
super(config, daoSession);
}
/** Creates the underlying database table.
*
*/
// 创建BOOK_SHELF_BEAN表的方法
/** Creates the underlying database table. */
public static void createTable(Database db, boolean ifNotExists) {
// 根据ifNotExists判断是否需要添加"IF NOT EXISTS"条件
String constraint = ifNotExists? "IF NOT EXISTS ": "";
// 执行SQL语句创建表
db.execSQL("CREATE TABLE " + constraint + "\"BOOK_SHELF_BEAN\" (" + //
"\"NOTE_URL\" TEXT PRIMARY KEY NOT NULL ," + //NOTE_URL字段TEXT类型主键非空
"\"DUR_CHAPTER\" INTEGER NOT NULL ," + // DUR_CHAPTER字段INTEGER类型非空
"\"DUR_CHAPTER_PAGE\" INTEGER NOT NULL ," + // DUR_CHAPTER_PAGE字段INTEGER类型非空
"\"FINAL_DATE\" INTEGER NOT NULL ," + // FINAL_DATE字段INTEGER类型非空
"\"TAG\" TEXT);"); // TAG字段TEXT类型
"\"NOTE_URL\" TEXT PRIMARY KEY NOT NULL ," + // 0: noteUrl
"\"DUR_CHAPTER\" INTEGER NOT NULL ," + // 1: durChapter
"\"DUR_CHAPTER_PAGE\" INTEGER NOT NULL ," + // 2: durChapterPage
"\"FINAL_DATE\" INTEGER NOT NULL ," + // 3: finalDate
"\"TAG\" TEXT);"); // 4: tag
}
/** Drops the underlying database table.
*
*/
// 删除BOOK_SHELF_BEAN表的方法
/** Drops the underlying database table. */
public static void dropTable(Database db, boolean ifExists) {
// 构造删除表的SQL语句
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"BOOK_SHELF_BEAN\"";// 如果指定ifExists则添加条件
// 执行SQL语句删除表
db.execSQL(sql);// 执行删除表的SQL语句
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"BOOK_SHELF_BEAN\"";
db.execSQL(sql);
}
@Override
// 将BookShelfBean实体对象的值绑定到DatabaseStatement语句中
protected final void bindValues(DatabaseStatement stmt, BookShelfBean entity) {
// 清除之前的绑定
stmt.clearBindings();// 清除绑定,以保证每次都是干净的
String noteUrl = entity.getNoteUrl(); // 获取noteUrl属性值
stmt.clearBindings();
String noteUrl = entity.getNoteUrl();
if (noteUrl != null) {
// 绑定noteUrl值到第一个参数位置
stmt.bindString(1, noteUrl);// 将noteUrl绑定到SQL语句的第一个位置
stmt.bindString(1, noteUrl);
}
stmt.bindLong(2, entity.getDurChapter()); // 绑定durChapter值到第二个参数位置
stmt.bindLong(3, entity.getDurChapterPage()); // 绑定durChapterPage值到第三个参数位置
stmt.bindLong(4, entity.getFinalDate()); // 绑定finalDate值到第四个参数位置
String tag = entity.getTag();// 获取tag属性值
stmt.bindLong(2, entity.getDurChapter());
stmt.bindLong(3, entity.getDurChapterPage());
stmt.bindLong(4, entity.getFinalDate());
String tag = entity.getTag();
if (tag != null) {
stmt.bindString(5, tag); // 绑定tag值到第五个参数位置
stmt.bindString(5, tag);
}
}
@Override
// 将BookShelfBean实体对象的值绑定到SQLiteStatement语句中与上一个方法功能类似只是对象不同
protected final void bindValues(SQLiteStatement stmt, BookShelfBean entity) {
// 清除之前的绑定
stmt.clearBindings();// 清除绑定,以保证每次都是干净的
String noteUrl = entity.getNoteUrl();// 获取noteUrl属性值
stmt.clearBindings();
String noteUrl = entity.getNoteUrl();
if (noteUrl != null) {
// 绑定noteUrl值到第一个参数位置
stmt.bindString(1, noteUrl);// 将noteUrl绑定到SQL语句的第一个位置
stmt.bindString(1, noteUrl);
}
stmt.bindLong(2, entity.getDurChapter()); // 绑定durChapter值到第二个参数位置
stmt.bindLong(3, entity.getDurChapterPage()); // 绑定durChapterPage值到第三个参数位置
stmt.bindLong(4, entity.getFinalDate()); // 绑定finalDate值到第四个参数位置
String tag = entity.getTag();// 获取tag属性值
stmt.bindLong(2, entity.getDurChapter());
stmt.bindLong(3, entity.getDurChapterPage());
stmt.bindLong(4, entity.getFinalDate());
String tag = entity.getTag();
if (tag != null) {
// 绑定tag值到第五个参数位置
stmt.bindString(5, tag);// 将tag绑定到SQL语句的第五个位置
stmt.bindString(5, tag);
}
}
@Override
// 从游标中读取主键
public String readKey(Cursor cursor, int offset) {
// 从偏移量offset+0的位置读取主键值如果为空返回null否则返回字符串
return cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0);// noteUrl是主键
}
return cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0);
}
@Override
// 从游标中读取BookShelfBean实体
public BookShelfBean readEntity(Cursor cursor, int offset) {
// 创建BookShelfBean对象
BookShelfBean entity = new BookShelfBean( //
cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0), // noteUrl
cursor.getInt(offset + 1), // durChapter
cursor.getInt(offset + 2), // durChapterPage
cursor.getLong(offset + 3), // finalDate
cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4) // tag
cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0), // noteUrl
cursor.getInt(offset + 1), // durChapter
cursor.getInt(offset + 2), // durChapterPage
cursor.getLong(offset + 3), // finalDate
cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4) // tag
);
// 返回创建好的BookShelfBean对象
return entity;// 返回读取到的实体对象
return entity;
}
@Override
// 将游标中的数据读取到已存在的BookShelfBean实体中
public void readEntity(Cursor cursor, BookShelfBean entity, int offset) {
entity.setNoteUrl(cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0)); // 设置noteUrl
entity.setDurChapter(cursor.getInt(offset + 1)); // 设置durChapter
entity.setDurChapterPage(cursor.getInt(offset + 2)); // 设置durChapterPage
entity.setFinalDate(cursor.getLong(offset + 3)); // 设置finalDate
entity.setTag(cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4)); // 设置tag
}
entity.setNoteUrl(cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0));
entity.setDurChapter(cursor.getInt(offset + 1));
entity.setDurChapterPage(cursor.getInt(offset + 2));
entity.setFinalDate(cursor.getLong(offset + 3));
entity.setTag(cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4));
}
@Override
// 插入数据后更新主键
protected final String updateKeyAfterInsert(BookShelfBean entity, long rowId) {
// 返回noteUrl作为主键
return entity.getNoteUrl();// 用于更新的主键值
return entity.getNoteUrl();
}
@Override
// 获取主键
public String getKey(BookShelfBean entity) {
if(entity != null) {
// 返回noteUrl作为主键
return entity.getNoteUrl();// 返回实体的主键值
return entity.getNoteUrl();
} else {
return null;// 如果实体为空则返回null
return null;
}
}
@Override
// 判断实体是否可更新
protected final boolean isEntityUpdateable() {
// 返回true表示实体可更新
return true;// 该实体类型支持更新
return true;
}
}

@ -1,213 +1,171 @@
// 包声明
package com.monke.monkeybook.dao;
import android.database.Cursor; // 导入Cursor类用于从数据库游标中读取数据
import android.database.sqlite.SQLiteStatement; // 导入SQLiteStatement类用于执行预编译的SQL语句
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import org.greenrobot.greendao.AbstractDao; // 导入AbstractDao类GreenDao框架中用于数据库操作的抽象基类
import org.greenrobot.greendao.Property; // 导入Property类用于定义数据库表字段的属性
import org.greenrobot.greendao.internal.DaoConfig; // 导入DaoConfig类用于配置Dao对象的配置信息
import org.greenrobot.greendao.database.Database; // 导入Database类GreenDao框架中用于数据库操作的类
import org.greenrobot.greendao.database.DatabaseStatement; // 导入DatabaseStatement类用于执行SQL语句
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.Property;
import org.greenrobot.greendao.internal.DaoConfig;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.DatabaseStatement;
// 导入ChapterListBean类这是数据库操作的对象
import com.monke.monkeybook.bean.ChapterListBean;// 导入ChapterListBean实体类
import com.monke.monkeybook.bean.ChapterListBean;
// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
/**
/**
* DAO for table "CHAPTER_LIST_BEAN".
* "CHAPTER_LIST_BEAN"DAO
*/
// ChapterListBeanDao类继承AbstractDao操作ChapterListBean对象主键类型为String
*/
public class ChapterListBeanDao extends AbstractDao<ChapterListBean, String> {
// 数据库表名常量
public static final String TABLENAME = "CHAPTER_LIST_BEAN";// 定义常量,表示数据库表的名称
public static final String TABLENAME = "CHAPTER_LIST_BEAN";
/**
* Properties of entity ChapterListBean.<br/>
* Can be used for QueryBuilder and for referencing column names.
*/
*/
public static class Properties {
// 定义实体类属性映射,指向数据库表中的字段
public final static Property NoteUrl = new Property(0, String.class, "noteUrl", false, "NOTE_URL"); // 属性0noteUrlString类型非主键数据库列名NOTE_URL
public final static Property DurChapterIndex = new Property(1, int.class, "durChapterIndex", false, "DUR_CHAPTER_INDEX"); // 属性1durChapterIndexint类型非主键数据库列名DUR_CHAPTER_INDEX
public final static Property DurChapterUrl = new Property(2, String.class, "durChapterUrl", true, "DUR_CHAPTER_URL"); // 属性2durChapterUrlString类型主键数据库列名DUR_CHAPTER_URL
public final static Property DurChapterName = new Property(3, String.class, "durChapterName", false, "DUR_CHAPTER_NAME"); // 属性3durChapterNameString类型非主键数据库列名DUR_CHAPTER_NAME
public final static Property Tag = new Property(4, String.class, "tag", false, "TAG"); // 属性4tagString类型非主键数据库列名TAG
public final static Property HasCache = new Property(5, Boolean.class, "hasCache", false, "HAS_CACHE"); // 属性5hasCacheBoolean类型非主键数据库列名HAS_CACHE
public final static Property NoteUrl = new Property(0, String.class, "noteUrl", false, "NOTE_URL");
public final static Property DurChapterIndex = new Property(1, int.class, "durChapterIndex", false, "DUR_CHAPTER_INDEX");
public final static Property DurChapterUrl = new Property(2, String.class, "durChapterUrl", true, "DUR_CHAPTER_URL");
public final static Property DurChapterName = new Property(3, String.class, "durChapterName", false, "DUR_CHAPTER_NAME");
public final static Property Tag = new Property(4, String.class, "tag", false, "TAG");
public final static Property HasCache = new Property(5, Boolean.class, "hasCache", false, "HAS_CACHE");
};
// 构造函数使用DaoConfig配置初始化Dao
public ChapterListBeanDao(DaoConfig config) {
super(config);// 调用父类构造函数初始化DAO对象
super(config);
}
// 构造函数使用DaoConfig和DaoSession配置初始化Dao
public ChapterListBeanDao(DaoConfig config, DaoSession daoSession) {
super(config, daoSession);// 调用父类构造函数初始化DAO对象
super(config, daoSession);
}
/** Creates the underlying database table. */
// 创建CHAPTER_LIST_BEAN表的方法
public static void createTable(Database db, boolean ifNotExists) {
// 根据ifNotExists判断是否需要添加"IF NOT EXISTS"条件
String constraint = ifNotExists? "IF NOT EXISTS ": "";// 如果ifNotExists为真则添加条件
// 执行SQL语句创建表
String constraint = ifNotExists? "IF NOT EXISTS ": "";
db.execSQL("CREATE TABLE " + constraint + "\"CHAPTER_LIST_BEAN\" (" + //
"\"NOTE_URL\" TEXT," + // NOTE_URL字段TEXT类型
"\"DUR_CHAPTER_INDEX\" INTEGER NOT NULL ," + // DUR_CHAPTER_INDEX字段INTEGER类型非空
"\"DUR_CHAPTER_URL\" TEXT PRIMARY KEY NOT NULL ," + // DUR_CHAPTER_URL字段TEXT类型主键非空
"\"DUR_CHAPTER_NAME\" TEXT," + // DUR_CHAPTER_NAME字段TEXT类型
"\"TAG\" TEXT," + // TAG字段TEXT类型
"\"HAS_CACHE\" INTEGER);"); // HAS_CACHE字段INTEGER类型
"\"NOTE_URL\" TEXT," + // 0: noteUrl
"\"DUR_CHAPTER_INDEX\" INTEGER NOT NULL ," + // 1: durChapterIndex
"\"DUR_CHAPTER_URL\" TEXT PRIMARY KEY NOT NULL ," + // 2: durChapterUrl
"\"DUR_CHAPTER_NAME\" TEXT," + // 3: durChapterName
"\"TAG\" TEXT," + // 4: tag
"\"HAS_CACHE\" INTEGER);"); // 5: hasCache
}
/** Drops the underlying database table.
*
*/
// 删除CHAPTER_LIST_BEAN表的方法
/** Drops the underlying database table. */
public static void dropTable(Database db, boolean ifExists) {
// 构造删除表的SQL语句
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"CHAPTER_LIST_BEAN\"";// 如果ifExists为真则加上条件
// 执行SQL语句删除表
db.execSQL(sql);// 执行删除表的SQL语句
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"CHAPTER_LIST_BEAN\"";
db.execSQL(sql);
}
@Override
// 将ChapterListBean实体对象的值绑定到DatabaseStatement语句中
protected final void bindValues(DatabaseStatement stmt, ChapterListBean entity) {
// 清除之前的绑定
stmt.clearBindings();// 确保每次绑定之前清除旧的绑定
String noteUrl = entity.getNoteUrl();// 获取noteUrl属性值
stmt.clearBindings();
String noteUrl = entity.getNoteUrl();
if (noteUrl != null) {
// 绑定noteUrl值到第一个参数位置
stmt.bindString(1, noteUrl);// 将noteUrl绑定到SQL语句的第一个位置
stmt.bindString(1, noteUrl);
}
// 绑定durChapterIndex值到第二个参数位置
stmt.bindLong(2, entity.getDurChapterIndex());// 将durChapterIndex绑定到SQL语句的第二个位置
String durChapterUrl = entity.getDurChapterUrl();// 获取durChapterUrl属性值
stmt.bindLong(2, entity.getDurChapterIndex());
String durChapterUrl = entity.getDurChapterUrl();
if (durChapterUrl != null) {
// 绑定durChapterUrl值到第三个参数位置
stmt.bindString(3, durChapterUrl);// 将durChapterUrl绑定到SQL语句的第三个位置
stmt.bindString(3, durChapterUrl);
}
String durChapterName = entity.getDurChapterName();// 获取durChapterName属性值
String durChapterName = entity.getDurChapterName();
if (durChapterName != null) {
// 绑定durChapterName值到第四个参数位置
stmt.bindString(4, durChapterName);// 将durChapterName绑定到SQL语句的第四个位置
stmt.bindString(4, durChapterName);
}
String tag = entity.getTag();// 获取tag属性值
String tag = entity.getTag();
if (tag != null) {
// 绑定tag值到第五个参数位置
stmt.bindString(5, tag);// 将tag绑定到SQL语句的第五个位置
stmt.bindString(5, tag);
}
Boolean hasCache = entity.getHasCache();// 获取hasCache属性值
Boolean hasCache = entity.getHasCache();
if (hasCache != null) {
// 绑定hasCache值到第六个参数位置true为1false为0
stmt.bindLong(6, hasCache ? 1L: 0L);// 将hasCache绑定到SQL语句的第六个位置布尔值转为整数
stmt.bindLong(6, hasCache ? 1L: 0L);
}
}
@Override
// 将ChapterListBean实体对象的值绑定到SQLiteStatement语句中与上一个方法功能类似只是对象不同
protected final void bindValues(SQLiteStatement stmt, ChapterListBean entity) {
// 清除之前的绑定
stmt.clearBindings();// 确保每次绑定之前清除旧的绑定
String noteUrl = entity.getNoteUrl();// 获取noteUrl属性值
stmt.clearBindings();
String noteUrl = entity.getNoteUrl();
if (noteUrl != null) {
// 绑定noteUrl值到第一个参数位置
stmt.bindString(1, noteUrl);// 将noteUrl绑定到SQL语句的第一个位置
stmt.bindString(1, noteUrl);
}
// 绑定durChapterIndex值到第二个参数位置
stmt.bindLong(2, entity.getDurChapterIndex());// 将durChapterIndex绑定到SQL语句的第二个位置
String durChapterUrl = entity.getDurChapterUrl();// 获取durChapterUrl属性值
stmt.bindLong(2, entity.getDurChapterIndex());
String durChapterUrl = entity.getDurChapterUrl();
if (durChapterUrl != null) {
// 绑定durChapterUrl值到第三个参数位置
stmt.bindString(3, durChapterUrl);// 将durChapterUrl绑定到SQL语句的第三个位置
stmt.bindString(3, durChapterUrl);
}
String durChapterName = entity.getDurChapterName();// 获取durChapterName属性值
String durChapterName = entity.getDurChapterName();
if (durChapterName != null) {
// 绑定durChapterName值到第四个参数位置
stmt.bindString(4, durChapterName);// 将durChapterName绑定到SQL语句的第四个位置
stmt.bindString(4, durChapterName);
}
String tag = entity.getTag();// 获取tag属性值
String tag = entity.getTag();
if (tag != null) {
// 绑定tag值到第五个参数位置
stmt.bindString(5, tag);// 将tag绑定到SQL语句的第五个位置
stmt.bindString(5, tag);
}
Boolean hasCache = entity.getHasCache();// 获取hasCache属性值
Boolean hasCache = entity.getHasCache();
if (hasCache != null) {
// 绑定hasCache值到第六个参数位置true为1false为0
stmt.bindLong(6, hasCache ? 1L: 0L);// 将hasCache绑定到SQL语句的第六个位置布尔值转为整数
stmt.bindLong(6, hasCache ? 1L: 0L);
}
}
@Override
// 从游标中读取主键
public String readKey(Cursor cursor, int offset) {
// 从偏移量offset+2的位置读取主键值如果为空返回null否则返回字符串
return cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2);// 返回durChapterUrl作为主键
}
return cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2);
}
@Override
// 从游标中读取ChapterListBean实体
public ChapterListBean readEntity(Cursor cursor, int offset) {
// 创建ChapterListBean对象
ChapterListBean entity = new ChapterListBean( //
cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0), // noteUrl
cursor.getInt(offset + 1), // durChapterIndex
cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // durChapterUrl
cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // durChapterName
cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4), // tag
cursor.isNull(offset + 5) ? null : cursor.getShort(offset + 5) != 0 // hasCache 将short转换为boolean
cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0), // noteUrl
cursor.getInt(offset + 1), // durChapterIndex
cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // durChapterUrl
cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // durChapterName
cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4), // tag
cursor.isNull(offset + 5) ? null : cursor.getShort(offset + 5) != 0 // hasCache
);
// 返回创建好的ChapterListBean对象
return entity;// 返回读取到的实体对象
return entity;
}
@Override
// 将游标中的数据读取到已存在的ChapterListBean实体中
public void readEntity(Cursor cursor, ChapterListBean entity, int offset) {
entity.setNoteUrl(cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0)); // noteUrl
entity.setDurChapterIndex(cursor.getInt(offset + 1)); // durChapterIndex
entity.setDurChapterUrl(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2)); // durChapterUrl
entity.setDurChapterName(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3)); // durChapterName
entity.setTag(cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4)); // tag
entity.setHasCache(cursor.isNull(offset + 5) ? null : cursor.getShort(offset + 5) != 0); // hasCache 将short转换为boolean
}
entity.setNoteUrl(cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0));
entity.setDurChapterIndex(cursor.getInt(offset + 1));
entity.setDurChapterUrl(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2));
entity.setDurChapterName(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3));
entity.setTag(cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4));
entity.setHasCache(cursor.isNull(offset + 5) ? null : cursor.getShort(offset + 5) != 0);
}
@Override
// 插入数据后更新主键
protected final String updateKeyAfterInsert(ChapterListBean entity, long rowId) {
// 返回durChapterUrl作为主键
return entity.getDurChapterUrl();// 用于更新的主键值
return entity.getDurChapterUrl();
}
@Override
// 获取主键
public String getKey(ChapterListBean entity) {
if(entity != null) {
// 返回durChapterUrl作为主键
return entity.getDurChapterUrl();// 返回实体的主键值
return entity.getDurChapterUrl();
} else {
return null; // 实体为空返回null
return null;
}
}
@Override
// 判断实体是否可更新
protected final boolean isEntityUpdateable() {
// 返回true表示实体可更新
return true;// 该实体类型支持更新
return true;
}
}

@ -1,144 +1,110 @@
// 包名声明
package com.monke.monkeybook.dao;
import android.content.Context; // 导入Context类用于获取应用程序上下文
import android.database.sqlite.SQLiteDatabase; // 导入SQLiteDatabase类用于操作数据库
import android.database.sqlite.SQLiteDatabase.CursorFactory; // 导入CursorFactory类用于创建游标工厂
import android.util.Log; // 导入Log类用于输出日志信息
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.util.Log;
import org.greenrobot.greendao.AbstractDaoMaster; // 导入AbstractDaoMaster类GreenDao框架中用于管理DAO的抽象基类
import org.greenrobot.greendao.database.StandardDatabase; // 导入StandardDatabase类GreenDao框架中基于SQLite的数据库实现
import org.greenrobot.greendao.database.Database; // 导入Database类GreenDao框架中用于数据库操作的接口
import org.greenrobot.greendao.database.DatabaseOpenHelper; // 导入DatabaseOpenHelper类GreenDao框架中用于打开和管理数据库的帮助类
import org.greenrobot.greendao.identityscope.IdentityScopeType; // 导入IdentityScopeType类GreenDao框架中用于指定标识符作用域类型的枚举
import org.greenrobot.greendao.AbstractDaoMaster;
import org.greenrobot.greendao.database.StandardDatabase;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.DatabaseOpenHelper;
import org.greenrobot.greendao.identityscope.IdentityScopeType;
// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
/**
* Master of DAO (schema version 1): knows all DAOs.
*/
// DaoMaster类继承AbstractDaoMaster是所有DAO的管理者
public class DaoMaster extends AbstractDaoMaster {
// 数据库模式版本号
public static final int SCHEMA_VERSION = 1;// 定义常量,表示数据库的版本号
public static final int SCHEMA_VERSION = 1;
/** Creates underlying database table using DAOs. */
// 创建所有数据库表的方法
public static void createAllTables(Database db, boolean ifNotExists) {
BookContentBeanDao.createTable(db, ifNotExists); // 创建BookContentBean表
BookInfoBeanDao.createTable(db, ifNotExists); // 创建BookInfoBean表
BookShelfBeanDao.createTable(db, ifNotExists); // 创建BookShelfBean表
ChapterListBeanDao.createTable(db, ifNotExists); // 创建ChapterListBean表
DownloadChapterBeanDao.createTable(db, ifNotExists); // 创建DownloadChapterBean表
SearchHistoryBeanDao.createTable(db, ifNotExists); // 创建SearchHistoryBean表
BookContentBeanDao.createTable(db, ifNotExists);
BookInfoBeanDao.createTable(db, ifNotExists);
BookShelfBeanDao.createTable(db, ifNotExists);
ChapterListBeanDao.createTable(db, ifNotExists);
DownloadChapterBeanDao.createTable(db, ifNotExists);
SearchHistoryBeanDao.createTable(db, ifNotExists);
}
/** Drops underlying database table using DAOs. */
// 删除所有数据库表的方法
public static void dropAllTables(Database db, boolean ifExists) {
BookContentBeanDao.dropTable(db, ifExists); // 删除BookContentBean表
BookInfoBeanDao.dropTable(db, ifExists); // 删除BookInfoBean表
BookShelfBeanDao.dropTable(db, ifExists); // 删除BookShelfBean表
ChapterListBeanDao.dropTable(db, ifExists); // 删除ChapterListBean表
DownloadChapterBeanDao.dropTable(db, ifExists); // 删除DownloadChapterBean表
SearchHistoryBeanDao.dropTable(db, ifExists); // 删除SearchHistoryBean表
BookContentBeanDao.dropTable(db, ifExists);
BookInfoBeanDao.dropTable(db, ifExists);
BookShelfBeanDao.dropTable(db, ifExists);
ChapterListBeanDao.dropTable(db, ifExists);
DownloadChapterBeanDao.dropTable(db, ifExists);
SearchHistoryBeanDao.dropTable(db, ifExists);
}
/**
* WARNING: Drops all table on Upgrade! Use only during development.
* Convenience method using a {@link DevOpenHelper}.
*/
// 创建一个开发模式的DaoSession
public static DaoSession newDevSession(Context context, String name) {
// 获取可写的数据库
Database db = new DevOpenHelper(context, name).getWritableDb();
// 创建DaoMaster实例
DaoMaster daoMaster = new DaoMaster(db);
// 创建DaoSession实例并返回
return daoMaster.newSession();// 创建并返回DaoSession
return daoMaster.newSession();
}
// 构造函数使用SQLiteDatabase对象初始化DaoMaster
public DaoMaster(SQLiteDatabase db) {
// 使用StandardDatabase包装SQLiteDatabase
this(new StandardDatabase(db));// 创建StandardDatabase对象并调用另一个构造函数
this(new StandardDatabase(db));
}
// 构造函数使用Database对象初始化DaoMaster
public DaoMaster(Database db) {
// 调用父类的构造函数
super(db, SCHEMA_VERSION);
// 注册所有DAO类供后续使用
registerDaoClass(BookContentBeanDao.class); // 注册BookContentBeanDao
registerDaoClass(BookInfoBeanDao.class); // 注册BookInfoBeanDao
registerDaoClass(BookShelfBeanDao.class); // 注册BookShelfBeanDao
registerDaoClass(ChapterListBeanDao.class); // 注册ChapterListBeanDao
registerDaoClass(DownloadChapterBeanDao.class); // 注册DownloadChapterBeanDao
registerDaoClass(SearchHistoryBeanDao.class); // 注册SearchHistoryBeanDao
registerDaoClass(BookContentBeanDao.class);
registerDaoClass(BookInfoBeanDao.class);
registerDaoClass(BookShelfBeanDao.class);
registerDaoClass(ChapterListBeanDao.class);
registerDaoClass(DownloadChapterBeanDao.class);
registerDaoClass(SearchHistoryBeanDao.class);
}
// 创建一个新的DaoSession使用默认的IdentityScopeType.Session
public DaoSession newSession() {
// 创建DaoSession实例使用Session作用域
return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);// 返回新的DaoSession实例
return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
}
// 创建一个新的DaoSession使用指定的IdentityScopeType
public DaoSession newSession(IdentityScopeType type) {
// 创建DaoSession实例使用指定的作用域类型
return new DaoSession(db, type, daoConfigMap);// 返回新的DaoSession实例
return new DaoSession(db, type, daoConfigMap);
}
/**
* Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
*/
// OpenHelper抽象类继承DatabaseOpenHelper用于创建和打开数据库
public static abstract class OpenHelper extends DatabaseOpenHelper {
// 构造函数,指定上下文和数据库名称
public OpenHelper(Context context, String name) {
// 调用父类的构造函数
super(context, name, SCHEMA_VERSION);/
super(context, name, SCHEMA_VERSION);
}
// 构造函数,指定上下文、数据库名称和游标工厂
public OpenHelper(Context context, String name, CursorFactory factory) {
// 调用父类的构造函数
super(context, name, factory, SCHEMA_VERSION);// 设置上下文、数据库名称、游标工厂和版本号
super(context, name, factory, SCHEMA_VERSION);
}
@Override
// 创建数据库时调用
public void onCreate(Database db) {
// 输出日志信息,记录数据库创建事件
Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
// 创建所有数据库表
createAllTables(db, false);// 调用创建所有表的方法
createAllTables(db, false);
}
}
/** WARNING: Drops all table on Upgrade! Use only during development. */
// DevOpenHelper类继承OpenHelper用于开发阶段的数据库升级
public static class DevOpenHelper extends OpenHelper {
// 构造函数,指定上下文和数据库名称
public DevOpenHelper(Context context, String name) {
// 调用父类的构造函数
super(context, name); // 设置上下文和数据库名称
super(context, name);
}
// 构造函数,指定上下文、数据库名称和游标工厂
public DevOpenHelper(Context context, String name, CursorFactory factory) {
// 调用父类的构造函数
super(context, name, factory);// 设置上下文、数据库名称和游标工厂
super(context, name, factory);
}
@Override
// 数据库升级时调用
public void onUpgrade(Database db, int oldVersion, int newVersion) {
// 输出日志信息
Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
// 删除所有数据库表
dropAllTables(db, true);
// 重新创建数据库表
onCreate(db);// 调用创建表的方法
onCreate(db);
}
}

@ -1,121 +1,118 @@
// 包名声明
package com.monke.monkeybook.dao;
// 导入Map接口用于存储键值对
import java.util.Map;
import org.greenrobot.greendao.AbstractDao; // 导入AbstractDao类GreenDao框架中数据库操作的抽象基类
import org.greenrobot.greendao.AbstractDaoSession; // 导入AbstractDaoSession类GreenDao框架中DAO会话的抽象基类
import org.greenrobot.greendao.database.Database; // 导入Database类GreenDao框架中数据库操作的接口
import org.greenrobot.greendao.identityscope.IdentityScopeType; // 导入IdentityScopeType类GreenDao框架中用于指定标识符作用域类型的枚举
import org.greenrobot.greendao.internal.DaoConfig; // 导入DaoConfig类GreenDao框架中用于配置Dao对象的配置信息
import com.monke.monkeybook.bean.BookContentBean; // 导入BookContentBean类书籍内容Bean
import com.monke.monkeybook.bean.BookInfoBean; // 导入BookInfoBean类书籍信息Bean
import com.monke.monkeybook.bean.BookShelfBean; // 导入BookShelfBean类书架Bean
import com.monke.monkeybook.bean.ChapterListBean; // 导入ChapterListBean类章节列表Bean
import com.monke.monkeybook.bean.DownloadChapterBean; // 导入DownloadChapterBean类下载章节Bean
import com.monke.monkeybook.bean.SearchHistoryBean; // 导入SearchHistoryBean类搜索历史Bean
import com.monke.monkeybook.dao.BookContentBeanDao; // 导入BookContentBeanDao类
import com.monke.monkeybook.dao.BookInfoBeanDao; // 导入BookInfoBeanDao类
import com.monke.monkeybook.dao.BookShelfBeanDao; // 导入BookShelfBeanDao类
import com.monke.monkeybook.dao.ChapterListBeanDao; // 导入ChapterListBeanDao类
import com.monke.monkeybook.dao.DownloadChapterBeanDao; // 导入DownloadChapterBeanDao类
import com.monke.monkeybook.dao.SearchHistoryBeanDao; // 导入SearchHistoryBeanDao类
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.AbstractDaoSession;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.identityscope.IdentityScopeType;
import org.greenrobot.greendao.internal.DaoConfig;
import com.monke.monkeybook.bean.BookContentBean;
import com.monke.monkeybook.bean.BookInfoBean;
import com.monke.monkeybook.bean.BookShelfBean;
import com.monke.monkeybook.bean.ChapterListBean;
import com.monke.monkeybook.bean.DownloadChapterBean;
import com.monke.monkeybook.bean.SearchHistoryBean;
import com.monke.monkeybook.dao.BookContentBeanDao;
import com.monke.monkeybook.dao.BookInfoBeanDao;
import com.monke.monkeybook.dao.BookShelfBeanDao;
import com.monke.monkeybook.dao.ChapterListBeanDao;
import com.monke.monkeybook.dao.DownloadChapterBeanDao;
import com.monke.monkeybook.dao.SearchHistoryBeanDao;
// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
/**
* {@inheritDoc}
*
*
* @see org.greenrobot.greendao.AbstractDaoSession
*/
// DaoSession类继承AbstractDaoSession管理数据库会话
public class DaoSession extends AbstractDaoSession {
private final DaoConfig bookContentBeanDaoConfig; // BookContentBeanDao的配置信息
private final DaoConfig bookInfoBeanDaoConfig; // BookInfoBeanDao的配置信息
private final DaoConfig bookShelfBeanDaoConfig; // BookShelfBeanDao的配置信息
private final DaoConfig chapterListBeanDaoConfig; // ChapterListBeanDao的配置信息
private final DaoConfig downloadChapterBeanDaoConfig; // DownloadChapterBeanDao的配置信息
private final DaoConfig searchHistoryBeanDaoConfig; // SearchHistoryBeanDao的配置信息
private final DaoConfig bookContentBeanDaoConfig;
private final DaoConfig bookInfoBeanDaoConfig;
private final DaoConfig bookShelfBeanDaoConfig;
private final DaoConfig chapterListBeanDaoConfig;
private final DaoConfig downloadChapterBeanDaoConfig;
private final DaoConfig searchHistoryBeanDaoConfig;
private final BookContentBeanDao bookContentBeanDao; // BookContentBeanDao实例
private final BookInfoBeanDao bookInfoBeanDao; // BookInfoBeanDao实例
private final BookShelfBeanDao bookShelfBeanDao; // BookShelfBeanDao实例
private final ChapterListBeanDao chapterListBeanDao; // ChapterListBeanDao实例
private final DownloadChapterBeanDao downloadChapterBeanDao; // DownloadChapterBeanDao实例
private final SearchHistoryBeanDao searchHistoryBeanDao; // SearchHistoryBeanDao实例
private final BookContentBeanDao bookContentBeanDao;
private final BookInfoBeanDao bookInfoBeanDao;
private final BookShelfBeanDao bookShelfBeanDao;
private final ChapterListBeanDao chapterListBeanDao;
private final DownloadChapterBeanDao downloadChapterBeanDao;
private final SearchHistoryBeanDao searchHistoryBeanDao;
public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig>
daoConfigMap) {// DaoSession的构造函数传入Database、IdentityScopeType和DaoConfig Map
// 调用父类的构造函数
daoConfigMap) {
super(db);
bookContentBeanDaoConfig = daoConfigMap.get(BookContentBeanDao.class).clone(); // 从map中获取BookContentBeanDao的配置信息并克隆
bookContentBeanDaoConfig.initIdentityScope(type); // 初始化BookContentBeanDao的标识符作用域
bookContentBeanDaoConfig = daoConfigMap.get(BookContentBeanDao.class).clone();
bookContentBeanDaoConfig.initIdentityScope(type);
bookInfoBeanDaoConfig = daoConfigMap.get(BookInfoBeanDao.class).clone(); // 从map中获取BookInfoBeanDao的配置信息并克隆
bookInfoBeanDaoConfig.initIdentityScope(type); // 初始化BookInfoBeanDao的标识符作用域
bookInfoBeanDaoConfig = daoConfigMap.get(BookInfoBeanDao.class).clone();
bookInfoBeanDaoConfig.initIdentityScope(type);
bookShelfBeanDaoConfig = daoConfigMap.get(BookShelfBeanDao.class).clone(); // 从map中获取BookShelfBeanDao的配置信息并克隆
bookShelfBeanDaoConfig.initIdentityScope(type); // 初始化BookShelfBeanDao的标识符作用域
bookShelfBeanDaoConfig = daoConfigMap.get(BookShelfBeanDao.class).clone();
bookShelfBeanDaoConfig.initIdentityScope(type);
chapterListBeanDaoConfig = daoConfigMap.get(ChapterListBeanDao.class).clone(); // 从map中获取ChapterListBeanDao的配置信息并克隆
chapterListBeanDaoConfig.initIdentityScope(type); // 初始化ChapterListBeanDao的标识符作用域
chapterListBeanDaoConfig = daoConfigMap.get(ChapterListBeanDao.class).clone();
chapterListBeanDaoConfig.initIdentityScope(type);
downloadChapterBeanDaoConfig = daoConfigMap.get(DownloadChapterBeanDao.class).clone(); // 从map中获取DownloadChapterBeanDao的配置信息并克隆
downloadChapterBeanDaoConfig.initIdentityScope(type); // 初始化DownloadChapterBeanDao的标识符作用域
downloadChapterBeanDaoConfig = daoConfigMap.get(DownloadChapterBeanDao.class).clone();
downloadChapterBeanDaoConfig.initIdentityScope(type);
searchHistoryBeanDaoConfig = daoConfigMap.get(SearchHistoryBeanDao.class).clone(); // 从map中获取SearchHistoryBeanDao的配置信息并克隆
searchHistoryBeanDaoConfig.initIdentityScope(type); // 初始化SearchHistoryBeanDao的标识符作用域
searchHistoryBeanDaoConfig = daoConfigMap.get(SearchHistoryBeanDao.class).clone();
searchHistoryBeanDaoConfig.initIdentityScope(type);
bookContentBeanDao = new BookContentBeanDao(bookContentBeanDaoConfig, this); // 创建BookContentBeanDao实例
bookInfoBeanDao = new BookInfoBeanDao(bookInfoBeanDaoConfig, this); // 创建BookInfoBeanDao实例
bookShelfBeanDao = new BookShelfBeanDao(bookShelfBeanDaoConfig, this); // 创建BookShelfBeanDao实例
chapterListBeanDao = new ChapterListBeanDao(chapterListBeanDaoConfig, this); // 创建ChapterListBeanDao实例
downloadChapterBeanDao = new DownloadChapterBeanDao(downloadChapterBeanDaoConfig, this); // 创建DownloadChapterBeanDao实例
searchHistoryBeanDao = new SearchHistoryBeanDao(searchHistoryBeanDaoConfig, this); // 创建SearchHistoryBeanDao实例
bookContentBeanDao = new BookContentBeanDao(bookContentBeanDaoConfig, this);
bookInfoBeanDao = new BookInfoBeanDao(bookInfoBeanDaoConfig, this);
bookShelfBeanDao = new BookShelfBeanDao(bookShelfBeanDaoConfig, this);
chapterListBeanDao = new ChapterListBeanDao(chapterListBeanDaoConfig, this);
downloadChapterBeanDao = new DownloadChapterBeanDao(downloadChapterBeanDaoConfig, this);
searchHistoryBeanDao = new SearchHistoryBeanDao(searchHistoryBeanDaoConfig, this);
registerDao(BookContentBean.class, bookContentBeanDao); // 注册BookContentBeanDao
registerDao(BookInfoBean.class, bookInfoBeanDao); // 注册BookInfoBeanDao
registerDao(BookShelfBean.class, bookShelfBeanDao); // 注册BookShelfBeanDao
registerDao(ChapterListBean.class, chapterListBeanDao); // 注册ChapterListBeanDao
registerDao(DownloadChapterBean.class, downloadChapterBeanDao); // 注册DownloadChapterBeanDao
registerDao(SearchHistoryBean.class, searchHistoryBeanDao); // 注册SearchHistoryBeanDao
registerDao(BookContentBean.class, bookContentBeanDao);
registerDao(BookInfoBean.class, bookInfoBeanDao);
registerDao(BookShelfBean.class, bookShelfBeanDao);
registerDao(ChapterListBean.class, chapterListBeanDao);
registerDao(DownloadChapterBean.class, downloadChapterBeanDao);
registerDao(SearchHistoryBean.class, searchHistoryBeanDao);
}
public void clear() {// 清除所有DAO的标识符作用域
bookContentBeanDaoConfig.getIdentityScope().clear(); // 清除BookContentBeanDao的标识符作用域
bookInfoBeanDaoConfig.getIdentityScope().clear(); // 清除BookInfoBeanDao的标识符作用域
bookShelfBeanDaoConfig.getIdentityScope().clear(); // 清除BookShelfBeanDao的标识符作用域
chapterListBeanDaoConfig.getIdentityScope().clear(); // 清除ChapterListBeanDao的标识符作用域
downloadChapterBeanDaoConfig.getIdentityScope().clear(); // 清除DownloadChapterBeanDao的标识符作用域
searchHistoryBeanDaoConfig.getIdentityScope().clear(); // 清除SearchHistoryBeanDao的标识符作用域
public void clear() {
bookContentBeanDaoConfig.getIdentityScope().clear();
bookInfoBeanDaoConfig.getIdentityScope().clear();
bookShelfBeanDaoConfig.getIdentityScope().clear();
chapterListBeanDaoConfig.getIdentityScope().clear();
downloadChapterBeanDaoConfig.getIdentityScope().clear();
searchHistoryBeanDaoConfig.getIdentityScope().clear();
}
public BookContentBeanDao getBookContentBeanDao() {// 获取BookContentBeanDao实例
return bookContentBeanDao;// 返回BookContentBeanDao实例
public BookContentBeanDao getBookContentBeanDao() {
return bookContentBeanDao;
}
public BookInfoBeanDao getBookInfoBeanDao() {// 获取BookInfoBeanDao实例
return bookInfoBeanDao;// 返回BookInfoBeanDao实例
public BookInfoBeanDao getBookInfoBeanDao() {
return bookInfoBeanDao;
}
public BookShelfBeanDao getBookShelfBeanDao() {// 获取BookShelfBeanDao实例
return bookShelfBeanDao;// 返回BookShelfBeanDao实例
public BookShelfBeanDao getBookShelfBeanDao() {
return bookShelfBeanDao;
}
public ChapterListBeanDao getChapterListBeanDao() {// 获取ChapterListBeanDao实例
return chapterListBeanDao;// 返回ChapterListBeanDao实例
public ChapterListBeanDao getChapterListBeanDao() {
return chapterListBeanDao;
}
public DownloadChapterBeanDao getDownloadChapterBeanDao() {// 获取DownloadChapterBeanDao实例
return downloadChapterBeanDao;// 返回DownloadChapterBeanDao实例
public DownloadChapterBeanDao getDownloadChapterBeanDao() {
return downloadChapterBeanDao;
}
public SearchHistoryBeanDao getSearchHistoryBeanDao() {// 获取SearchHistoryBeanDao实例
return searchHistoryBeanDao;// 返回SearchHistoryBeanDao实例
public SearchHistoryBeanDao getSearchHistoryBeanDao() {
return searchHistoryBeanDao;
}
}

@ -1,48 +1,41 @@
// 包名声明
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.dao;
// 导入SQLiteDatabase类用于操作数据库
import android.database.sqlite.SQLiteDatabase;
// 导入MApplication类用于获取应用上下文
import com.monke.monkeybook.MApplication;
public class DbHelper {// DbHelper类用于管理GreenDao数据库连接
private DaoMaster.DevOpenHelper mHelper; // DaoMaster.DevOpenHelper实例用于创建和管理数据库
private SQLiteDatabase db; // SQLiteDatabase实例用于数据库操作
private DaoMaster mDaoMaster; // DaoMaster实例用于管理DAO
private DaoSession mDaoSession; // DaoSession实例用于数据库会话
public class DbHelper {
private DaoMaster.DevOpenHelper mHelper;
private SQLiteDatabase db;
private DaoMaster mDaoMaster;
private DaoSession mDaoSession;
private DbHelper(){// 私有构造方法,采用单例模式
// 创建DaoMaster.DevOpenHelper实例指定数据库名称为"monkebook_db"游标工厂为null
private DbHelper(){
mHelper = new DaoMaster.DevOpenHelper(MApplication.getInstance(), "monkebook_db", null);
// 获取可写的SQLiteDatabase实例
db = mHelper.getWritableDatabase();
// 注意:该数据库连接属于 DaoMaster所以多个 Session 指的是相同的数据库连接。
mDaoMaster = new DaoMaster(db);// 创建DaoMaster实例使用获取的SQLiteDatabase实例
mDaoSession = mDaoMaster.newSession();// 创建DaoSession实例
mDaoMaster = new DaoMaster(db);
mDaoSession = mDaoMaster.newSession();
}
// DbHelper的单例实例
private static DbHelper instance;
public static DbHelper getInstance(){// 获取DbHelper单例实例的方法
if(null == instance){// 判断单例实例是否为空
synchronized (DbHelper.class){// 同步块,保证线程安全
if(null == instance){// 再次判断单例实例是否为空,避免重复创建
instance = new DbHelper();// 创建DbHelper实例
public static DbHelper getInstance(){
if(null == instance){
synchronized (DbHelper.class){
if(null == instance){
instance = new DbHelper();
}
}
}
// 返回单例实例
return instance;
}
public DaoSession getmDaoSession() {// 获取DaoSession实例的方法
// 返回DaoSession实例
public DaoSession getmDaoSession() {
return mDaoSession;
}
public SQLiteDatabase getDb() { // 获取SQLiteDatabase实例的方法
// 返回SQLiteDatabase实例
public SQLiteDatabase getDb() {
return db;
}
}

@ -1,241 +1,185 @@
// 包名声明
package com.monke.monkeybook.dao;
import android.database.Cursor; // 导入Cursor类用于读取数据库查询结果
import android.database.sqlite.SQLiteStatement; // 导入SQLiteStatement类用于执行SQL语句
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import org.greenrobot.greendao.AbstractDao; // 导入AbstractDao类GreenDao框架中数据库操作的抽象基类
import org.greenrobot.greendao.Property; // 导入Property类GreenDao框架中数据库字段的属性类
import org.greenrobot.greendao.internal.DaoConfig; // 导入DaoConfig类GreenDao框架中用于配置Dao对象的配置信息
import org.greenrobot.greendao.database.Database; // 导入Database类GreenDao框架中数据库操作的接口
import org.greenrobot.greendao.database.DatabaseStatement; // 导入DatabaseStatement类GreenDao框架中用于执行SQL语句的接口
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.Property;
import org.greenrobot.greendao.internal.DaoConfig;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.DatabaseStatement;
// 导入DownloadChapterBean类
import com.monke.monkeybook.bean.DownloadChapterBean;
// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
/**
/**
* DAO for table "DOWNLOAD_CHAPTER_BEAN".
*/
public class DownloadChapterBeanDao extends AbstractDao<DownloadChapterBean, String> { // DownloadChapterBeanDao类继承AbstractDao主键类型为String
// 表名常量,表示数据库表的名称
*/
public class DownloadChapterBeanDao extends AbstractDao<DownloadChapterBean, String> {
public static final String TABLENAME = "DOWNLOAD_CHAPTER_BEAN";
/**
* Properties of entity DownloadChapterBean.<br/>
* Can be used for QueryBuilder and for referencing column names.
*/
public static class Properties {// 属性类定义DownloadChapterBean实体类的属性
public final static Property NoteUrl = new Property(0, String.class, "noteUrl", false, "NOTE_URL");// noteUrl属性
public final static Property DurChapterIndex = new Property(1, int.class, "durChapterIndex", false, "DUR_CHAPTER_INDEX");// durChapterIndex属性
public final static Property DurChapterUrl = new Property(2, String.class, "durChapterUrl", true, "DUR_CHAPTER_URL");// durChapterUrl属性主键
public final static Property DurChapterName = new Property(3, String.class, "durChapterName", false, "DUR_CHAPTER_NAME");// durChapterName属性
public final static Property Tag = new Property(4, String.class, "tag", false, "TAG");// tag属性
public final static Property BookName = new Property(5, String.class, "bookName", false, "BOOK_NAME");// bookName属性
public final static Property CoverUrl = new Property(6, String.class, "coverUrl", false, "COVER_URL");// coverUrl属性
*/
public static class Properties {
public final static Property NoteUrl = new Property(0, String.class, "noteUrl", false, "NOTE_URL");
public final static Property DurChapterIndex = new Property(1, int.class, "durChapterIndex", false, "DUR_CHAPTER_INDEX");
public final static Property DurChapterUrl = new Property(2, String.class, "durChapterUrl", true, "DUR_CHAPTER_URL");
public final static Property DurChapterName = new Property(3, String.class, "durChapterName", false, "DUR_CHAPTER_NAME");
public final static Property Tag = new Property(4, String.class, "tag", false, "TAG");
public final static Property BookName = new Property(5, String.class, "bookName", false, "BOOK_NAME");
public final static Property CoverUrl = new Property(6, String.class, "coverUrl", false, "COVER_URL");
};
public DownloadChapterBeanDao(DaoConfig config) {// 构造函数传入DaoConfig配置初始化父类
super(config);// 调用父类构造函数
public DownloadChapterBeanDao(DaoConfig config) {
super(config);
}
public DownloadChapterBeanDao(DaoConfig config, DaoSession daoSession) {// 传入DaoConfig和DaoSession对象初始化父类
super(config, daoSession);// 调用父类构造函数
public DownloadChapterBeanDao(DaoConfig config, DaoSession daoSession) {
super(config, daoSession);
}
/** Creates the underlying database table. */
public static void createTable(Database db, boolean ifNotExists) {// 创建表的方法传入Database对象和是否忽略表已存在的标志
// 判断是否忽略表已存在构造SQL语句的一部分
public static void createTable(Database db, boolean ifNotExists) {
String constraint = ifNotExists? "IF NOT EXISTS ": "";
db.execSQL("CREATE TABLE " + constraint + "\"DOWNLOAD_CHAPTER_BEAN\" (" + //执行创建表的SQL语句
"\"NOTE_URL\" TEXT," + // 0: noteUrl.表中字段NOTE_URL数据类型为TEXT
"\"DUR_CHAPTER_INDEX\" INTEGER NOT NULL ," + // 1: durChapterIndex.DUR_CHAPTER_INDEX字段整型非空
"\"DUR_CHAPTER_URL\" TEXT PRIMARY KEY NOT NULL ," + // 2: durChapterUrl.DUR_CHAPTER_URL字段文本类型主键非空
"\"DUR_CHAPTER_NAME\" TEXT," + // 3: durChapterName.DUR_CHAPTER_NAME字段文本类型
"\"TAG\" TEXT," + // 4: tag.TAG字段文本类型
"\"BOOK_NAME\" TEXT," + // 5: bookName.BOOK_NAME字段文本类型
"\"COVER_URL\" TEXT);"); // 6: coverUrl.COVER_URL字段文本类型
db.execSQL("CREATE TABLE " + constraint + "\"DOWNLOAD_CHAPTER_BEAN\" (" + //
"\"NOTE_URL\" TEXT," + // 0: noteUrl
"\"DUR_CHAPTER_INDEX\" INTEGER NOT NULL ," + // 1: durChapterIndex
"\"DUR_CHAPTER_URL\" TEXT PRIMARY KEY NOT NULL ," + // 2: durChapterUrl
"\"DUR_CHAPTER_NAME\" TEXT," + // 3: durChapterName
"\"TAG\" TEXT," + // 4: tag
"\"BOOK_NAME\" TEXT," + // 5: bookName
"\"COVER_URL\" TEXT);"); // 6: coverUrl
}
/** Drops the underlying database table.
*
*/
public static void dropTable(Database db, boolean ifExists) {// 删除表的方法传入Database对象和是否忽略表不存在的标志
// 构造删除表的SQL语句
/** Drops the underlying database table. */
public static void dropTable(Database db, boolean ifExists) {
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"DOWNLOAD_CHAPTER_BEAN\"";
// 执行删除表的SQL语句
db.execSQL(sql);
}
@Override
// 将实体对象绑定到DatabaseStatement语句中
protected final void bindValues(DatabaseStatement stmt, DownloadChapterBean entity) {
// 清空绑定
stmt.clearBindings();
// 获取noteUrl值
String noteUrl = entity.getNoteUrl();
// 判断noteUrl是否为空
if (noteUrl != null) {
// 绑定noteUrl值到第一个参数位置
stmt.bindString(1, noteUrl);
}
// 绑定durChapterIndex值到第二个参数位置
stmt.bindLong(2, entity.getDurChapterIndex());
// 获取durChapterUrl值
String durChapterUrl = entity.getDurChapterUrl();
// 判断durChapterUrl是否为空
if (durChapterUrl != null) {
// 绑定durChapterUrl值到第三个参数位置
stmt.bindString(3, durChapterUrl);
}
// 获取durChapterName值
String durChapterName = entity.getDurChapterName();
// 判断durChapterName是否为空
if (durChapterName != null) {
// 绑定durChapterName值到第四个参数位置
stmt.bindString(4, durChapterName);
}
// 获取tag值
String tag = entity.getTag();
// 判断tag是否为空
if (tag != null) {
// 绑定tag值到第五个参数位置
stmt.bindString(5, tag);
}
// 获取bookName值
String bookName = entity.getBookName();
// 判断bookName是否为空
if (bookName != null) {
// 绑定bookName值到第六个参数位置
stmt.bindString(6, bookName);
}
// 获取coverUrl值
String coverUrl = entity.getCoverUrl();
// 判断coverUrl是否为空
if (coverUrl != null) {
// 绑定coverUrl值到第七个参数位置
stmt.bindString(7, coverUrl);
}
}
@Override
// 将实体对象绑定到SQLiteStatement语句中.与上面的方法基本一致只是使用的Statement类型不同
protected final void bindValues(SQLiteStatement stmt, DownloadChapterBean entity) {
// 清空绑定
stmt.clearBindings();
// 获取noteUrl值
String noteUrl = entity.getNoteUrl();
// 判断noteUrl是否为空
if (noteUrl != null) {
stmt.bindString(1, noteUrl);// 绑定noteUrl值到第一个参数位置
stmt.bindString(1, noteUrl);
}
stmt.bindLong(2, entity.getDurChapterIndex());// 绑定durChapterIndex值到第二个参数位置
// 获取durChapterUrl值
stmt.bindLong(2, entity.getDurChapterIndex());
String durChapterUrl = entity.getDurChapterUrl();
// 判断durChapterUrl是否为空
if (durChapterUrl != null) {
stmt.bindString(3, durChapterUrl);// 绑定durChapterUrl值到第三个参数位置
stmt.bindString(3, durChapterUrl);
}
// 获取durChapterName值
String durChapterName = entity.getDurChapterName();
// 判断durChapterName是否为空
if (durChapterName != null) {
stmt.bindString(4, durChapterName);// 绑定durChapterName值到第四个参数位置
stmt.bindString(4, durChapterName);
}
// 获取tag值
String tag = entity.getTag();
// 判断tag是否为空
if (tag != null) {
stmt.bindString(5, tag); // 绑定tag值到第五个参数位置
stmt.bindString(5, tag);
}
// 获取bookName值
String bookName = entity.getBookName();
// 判断bookName是否为空
if (bookName != null) {
stmt.bindString(6, bookName);// 绑定bookName值到第六个参数位置
stmt.bindString(6, bookName);
}
// 获取coverUrl值
String coverUrl = entity.getCoverUrl();
// 判断coverUrl是否为空
if (coverUrl != null) {
stmt.bindString(7, coverUrl);// 绑定coverUrl值到第七个参数位置
stmt.bindString(7, coverUrl);
}
}
@Override
// 从游标读取主键
public String readKey(Cursor cursor, int offset) {
// 读取第三列索引为2如果为空返回null否则返回字符串
return cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2);
}
}
@Override
// 从游标读取实体对象
public DownloadChapterBean readEntity(Cursor cursor, int offset) {
// 创建DownloadChapterBean对象
DownloadChapterBean entity = new DownloadChapterBean( //
cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0), // noteUrl
cursor.getInt(offset + 1), // durChapterIndex
cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // durChapterUrl
cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // durChapterName
cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4), // tag
cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5), // bookName
cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6) // coverUrl
cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0), // noteUrl
cursor.getInt(offset + 1), // durChapterIndex
cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // durChapterUrl
cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // durChapterName
cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4), // tag
cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5), // bookName
cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6) // coverUrl
);
// 返回创建的DownloadChapterBean对象
return entity;
}
@Override
// 将游标数据读取到实体对象中
public void readEntity(Cursor cursor, DownloadChapterBean entity, int offset) {
entity.setNoteUrl(cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0));// 设置noteUrl
entity.setDurChapterIndex(cursor.getInt(offset + 1));// 设置durChapterIndex
entity.setDurChapterUrl(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2));// 设置durChapterUrl
entity.setDurChapterName(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3));// 设置durChapterName
entity.setTag(cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4));// 设置tag
entity.setBookName(cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5));// 设置bookName
entity.setCoverUrl(cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6));// 设置coverUrl
}
entity.setNoteUrl(cursor.isNull(offset + 0) ? null : cursor.getString(offset + 0));
entity.setDurChapterIndex(cursor.getInt(offset + 1));
entity.setDurChapterUrl(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2));
entity.setDurChapterName(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3));
entity.setTag(cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4));
entity.setBookName(cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5));
entity.setCoverUrl(cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6));
}
@Override
// 插入后更新主键
protected final String updateKeyAfterInsert(DownloadChapterBean entity, long rowId) {
// 返回durChapterUrl作为主键
return entity.getDurChapterUrl();
}
@Override
// 获取主键
public String getKey(DownloadChapterBean entity) {
// 判断实体对象是否为空
if(entity != null) {
// 返回durChapterUrl作为主键
return entity.getDurChapterUrl();
} else {
// 实体对象为空返回null
return null;
}
}
@Override
// 判断实体是否可更新
protected final boolean isEntityUpdateable() {
// 返回true表示实体可更新
return true;
}
}

@ -1,171 +1,136 @@
// 包名声明
package com.monke.monkeybook.dao;
import android.database.Cursor;// 用于从数据库查询结果中读取数据
import android.database.sqlite.SQLiteStatement;// 用于从数据库查询结果中读取数据
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import org.greenrobot.greendao.AbstractDao;// GreenDao框架提供的抽象DAO类
import org.greenrobot.greendao.Property;// GreenDao框架提供的属性类用于定义数据库表的字段属性
import org.greenrobot.greendao.internal.DaoConfig;// GreenDao框架提供的DAO配置类
import org.greenrobot.greendao.database.Database;// GreenDao框架提供的数据库操作接口
import org.greenrobot.greendao.database.DatabaseStatement;// GreenDao框架提供的数据库语句执行接口
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.Property;
import org.greenrobot.greendao.internal.DaoConfig;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.DatabaseStatement;
import com.monke.monkeybook.bean.SearchHistoryBean;// 搜索历史Bean类
import com.monke.monkeybook.bean.SearchHistoryBean;
// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
/**
/**
* DAO for table "SEARCH_HISTORY_BEAN".
*/
public class SearchHistoryBeanDao extends AbstractDao<SearchHistoryBean, Long> {// 继承AbstractDao主键类型为Long
// 数据库表名
*/
public class SearchHistoryBeanDao extends AbstractDao<SearchHistoryBean, Long> {
public static final String TABLENAME = "SEARCH_HISTORY_BEAN";
/**
* Properties of entity SearchHistoryBean.<br/>
* Can be used for QueryBuilder and for referencing column names.
*/
*/
public static class Properties {
// 定义SearchHistoryBean实体类的属性
public final static Property Id = new Property(0, Long.class, "id", true, "_id");// id属性主键自增
public final static Property Type = new Property(1, int.class, "type", false, "TYPE");// type属性整型
public final static Property Content = new Property(2, String.class, "content", false, "CONTENT");// content属性字符串
public final static Property Date = new Property(3, long.class, "date", false, "DATE");// date属性长整型
public final static Property Id = new Property(0, Long.class, "id", true, "_id");
public final static Property Type = new Property(1, int.class, "type", false, "TYPE");
public final static Property Content = new Property(2, String.class, "content", false, "CONTENT");
public final static Property Date = new Property(3, long.class, "date", false, "DATE");
};
public SearchHistoryBeanDao(DaoConfig config) {// 构造函数传入DAO配置
// 调用父类构造函数
public SearchHistoryBeanDao(DaoConfig config) {
super(config);
}
public SearchHistoryBeanDao(DaoConfig config, DaoSession daoSession) {// 构造函数传入DAO配置和DAO会话
// 调用父类构造函数
public SearchHistoryBeanDao(DaoConfig config, DaoSession daoSession) {
super(config, daoSession);
}
/** Creates the underlying database table.
*
*/
public static void createTable(Database db, boolean ifNotExists) {// 创建表的静态方法
// 判断是否需要检查表是否存在构建SQL语句
/** Creates the underlying database table. */
public static void createTable(Database db, boolean ifNotExists) {
String constraint = ifNotExists? "IF NOT EXISTS ": "";
// 执行创建表语句
db.execSQL("CREATE TABLE " + constraint + "\"SEARCH_HISTORY_BEAN\" (" + //
"\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id _id列主键自增
"\"TYPE\" INTEGER NOT NULL ," + // 1: type TYPE列整型非空
"\"CONTENT\" TEXT," + // 2: content CONTENT列文本类型
"\"DATE\" INTEGER NOT NULL );"); // 3: date DATE列整型非空
"\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id
"\"TYPE\" INTEGER NOT NULL ," + // 1: type
"\"CONTENT\" TEXT," + // 2: content
"\"DATE\" INTEGER NOT NULL );"); // 3: date
}
/** Drops the underlying database table.
*
*/
public static void dropTable(Database db, boolean ifExists) {// 删除表的静态方法
// 构建删除表语句
/** Drops the underlying database table. */
public static void dropTable(Database db, boolean ifExists) {
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"SEARCH_HISTORY_BEAN\"";
// 执行删除表语句
db.execSQL(sql);
}
@Override
// 将实体对象绑定到DatabaseStatement语句
protected final void bindValues(DatabaseStatement stmt, SearchHistoryBean entity) {
// 清除之前的绑定
stmt.clearBindings();
// 获取实体对象的id
Long id = entity.getId();
// 判断id是否为空
if (id != null) {
stmt.bindLong(1, id);// 绑定id到第一个参数
stmt.bindLong(1, id);
}
stmt.bindLong(2, entity.getType());// 绑定type到第二个参数
// 获取实体对象的content
stmt.bindLong(2, entity.getType());
String content = entity.getContent();
// 判断content是否为空
if (content != null) {
// 绑定content到第三个参数
stmt.bindString(3, content);
}
stmt.bindLong(4, entity.getDate());// 绑定date到第四个参数
stmt.bindLong(4, entity.getDate());
}
@Override// 将实体对象绑定到SQLiteStatement语句, 和上面方法类似只是Statement类型不同
@Override
protected final void bindValues(SQLiteStatement stmt, SearchHistoryBean entity) {
// 清除之前的绑定
stmt.clearBindings();
// 获取实体对象的id
Long id = entity.getId();
// 判断id是否为空
if (id != null) {
stmt.bindLong(1, id);// 绑定id到第一个参数
stmt.bindLong(1, id);
}
stmt.bindLong(2, entity.getType());// 绑定type到第二个参数
// 获取实体对象的content
stmt.bindLong(2, entity.getType());
String content = entity.getContent();
// 判断content是否为空
if (content != null) {
stmt.bindString(3, content);// 绑定content到第三个参数
stmt.bindString(3, content);
}
stmt.bindLong(4, entity.getDate());// 绑定date到第四个参数
stmt.bindLong(4, entity.getDate());
}
@Override
// 从Cursor读取主键
public Long readKey(Cursor cursor, int offset) {
// 从Cursor的第offset+0列读取Long类型的主键如果为空则返回null
return cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0);
}
}
@Override
// 从Cursor读取实体对象
public SearchHistoryBean readEntity(Cursor cursor, int offset) {
SearchHistoryBean entity = new SearchHistoryBean( //
cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id
cursor.getInt(offset + 1), // type
cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // content
cursor.getLong(offset + 3) // date
cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id
cursor.getInt(offset + 1), // type
cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // content
cursor.getLong(offset + 3) // date
);
// 返回创建的SearchHistoryBean对象
return entity;
}
@Override
// 将Cursor数据读入到实体对象中
public void readEntity(Cursor cursor, SearchHistoryBean entity, int offset) {
entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0));// 设置id
entity.setType(cursor.getInt(offset + 1));// 设置type
entity.setContent(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2));// 设置content
entity.setDate(cursor.getLong(offset + 3));// 设置date
}
entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0));
entity.setType(cursor.getInt(offset + 1));
entity.setContent(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2));
entity.setDate(cursor.getLong(offset + 3));
}
@Override
// 插入数据后更新主键
protected final Long updateKeyAfterInsert(SearchHistoryBean entity, long rowId) {
// 设置主键为rowId
entity.setId(rowId);
// 返回rowId
return rowId;
}
@Override
// 获取主键
public Long getKey(SearchHistoryBean entity) {
// 判断实体对象是否为空
if(entity != null) {
return entity.getId();// 返回实体对象的id
return entity.getId();
} else {
return null;// 返回null
return null;
}
}
@Override
// 判断实体是否可更新
protected final boolean isEntityUpdateable() {
return true;// 返回true表示实体可更新
return true;
}
}

@ -1,11 +1,9 @@
// 包名声明
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.listener;
import com.monke.monkeybook.bean.BookShelfBean;// 导入BookShelfBean类
import com.monke.monkeybook.bean.BookShelfBean;
public interface OnGetChapterListListener {// 定义一个接口,用于监听获取章节列表的结果
public void success(BookShelfBean bookShelfBean); // 获取章节列表成功时的回调方法参数为BookShelfBean对象
// 表示获取章节列表操作成功并传入包含章节信息的BookShelfBean对象。
public void error();// 获取章节列表失败时的回调方法
// 表示获取章节列表操作失败
public interface OnGetChapterListListener {
public void success(BookShelfBean bookShelfBean);
public void error();
}

@ -8,47 +8,24 @@ import com.monke.monkeybook.listener.OnGetChapterListListener;
import java.util.List;
import io.reactivex.Observable;
// 定义了一个名为IEasouBookModel的接口用于规范与书籍相关的各种数据获取操作的方法签名
// 该接口可能会有不同的实现类来具体处理对应的业务逻辑,例如与不同数据源交互获取书籍信息等
public interface IEasouBookModel {
/**
*
*
* @param content
* @param page
* @param rankKind
* @return ObservableList<SearchBookBean>
* Observable
*
*/
Observable<List<SearchBookBean>> searchBook(String content, int page, int rankKind);
/**
*
*
* @param bookShelfBean BookShelfBean
*
* @return ObservableBookShelfBean
* Observable
*
*/
Observable<BookShelfBean> getBookInfo(final BookShelfBean bookShelfBean);
/**
*
*
* @param bookShelfBean BookShelfBean
* @param getChapterListListener
*
*
*/
void getChapterList(final BookShelfBean bookShelfBean, OnGetChapterListListener getChapterListListener);
/**
*
*
* @param durChapterUrl URLURL
* @param durChapterIndex 便
* @return ObservableBookContentBeanBean
* Observable
*
*/
Observable<BookContentBean> getBookContent(final String durChapterUrl, final int durChapterIndex);
}
}

@ -3,42 +3,21 @@ package com.monke.monkeybook.model;
import com.monke.monkeybook.bean.LibraryBean;
import com.monke.monkeybook.bean.SearchBookBean;
import //
com.monke.monkeybook.cache.ACache;
import com.monke.monkeybook.cache.ACache;
import java.util.List;
import io.reactivex.Observable;
// IGxwztvBookModel接口继承自IStationBookModel接口意味着它会继承IStationBookModel中定义的方法
// 同时在此接口中又额外定义了一些与特定业务可能和gxwztv相关相关的书籍数据获取等操作的方法规范
public interface IGxwztvBookModel extends IStationBookModel {
/**
*
*
* @param url URL
* @param page 便
* @return ObservableList<SearchBookBean>
* Observable
*/
Observable<List<SearchBookBean>> getKindBook(String url, int page);
/**
*
*
* @param aCache ACache
* 便
* @return ObservableLibraryBeanLibraryBean
* Observable
*
*/
Observable<LibraryBean> getLibraryData(ACache aCache);
/**
*
*
* @param data
* LibraryBean
* @return ObservableLibraryBeanLibraryBean
* Observable便
*
*/
Observable<LibraryBean> analyLibraryData(String data);
}
}

@ -2,21 +2,10 @@
package com.monke.monkeybook.model;
import com.monke.monkeybook.bean.LocBookShelfBean;
// 导入Java中用于操作文件的类意味着该接口相关操作可能涉及对本地文件的处理比如导入本地书籍文件等
import java.io.File;
import io.reactivex.Observable;
// 定义了IImportBookModel接口该接口主要规范了导入书籍相关操作的方法签名
// 具体的导入书籍逻辑可由实现该接口的类来完成
public interface IImportBookModel {
/**
*
*
* @param book Filetxtepub
*
* @return ObservableLocBookShelfBean
* Observable
*/
Observable<LocBookShelfBean> importBook(File book);
}
}

@ -8,53 +8,25 @@ import com.monke.monkeybook.listener.OnGetChapterListListener;
import java.util.List;
import io.reactivex.Observable;
// IStationBookModel接口定义了一系列与书籍相关操作的方法规范这些操作可能是围绕某个站点Station展开的
// 比如从特定的网络站点获取书籍数据等,具体实现类会按照这些规范来实现对应的业务逻辑
public interface IStationBookModel {
/**
*
*
* @param content
*
* @param page
* 便
* @return ObservableList<SearchBookBean>
* Observable
*
*/
Observable<List<SearchBookBean>> searchBook(String content, int page);
/**
*
*
* @param bookShelfBean BookShelfBean
*
* BookShelfBean
* @return ObservableBookShelfBean
* Observable
*
*/
Observable<BookShelfBean> getBookInfo(final BookShelfBean bookShelfBean);
/**
*
*
* @param bookShelfBean BookShelfBean
*
* OnGetChapterListListener
* @param getChapterListListener
*
*
*/
void getChapterList(final BookShelfBean bookShelfBean, OnGetChapterListListener getChapterListListener);
/**
*
*
* @param durChapterUrl URLURL
*
* @param durChapterIndex 便使
*
* @return ObservableBookContentBeanBean
* Observable便
*
*/
Observable<BookContentBean> getBookContent(final String durChapterUrl, final int durChapterIndex);
}
}

@ -8,70 +8,28 @@ import com.monke.monkeybook.listener.OnGetChapterListListener;
import java.util.List;
import io.reactivex.Observable;
// IWebBookModel接口定义了一系列与网络书籍相关操作的方法规范
// 主要涉及从网络获取书籍各种信息(如书籍详情、目录、章节内容等)以及搜索不同类型书籍的功能,
// 具体的实现类会按照这些方法签名去实现对应的网络交互逻辑
public interface IWebBookModel {
/**
*
*
* @param bookShelfBean BookShelfBean
*
* BookShelfBean
* @return ObservableBookShelfBean
* Observable
*
*/
Observable<BookShelfBean> getBookInfo(final BookShelfBean bookShelfBean);
/**
*
*
* @param bookShelfBean BookShelfBean
*
* OnGetChapterListListener
*
* @param getChapterListListener
*
*
*
*/
void getChapterList(final BookShelfBean bookShelfBean,OnGetChapterListListener getChapterListListener);
/**
*
*
* @param durChapterUrl URLURL
*
* @param durChapterIndex 便使
*
* @param tag
* @return ObservableBookContentBeanBean
* Observable便
*
*/
Observable<BookContentBean> getBookContent(final String durChapterUrl, final int durChapterIndex, String tag);
/**
*
*
* @param url URL
*
* @param page 便
* 2
* @return ObservableList<SearchBookBean>
* Observable
*
*/
Observable<List<SearchBookBean>> getKindBook(String url,int page);
/**
*
*
* @param content
*
* @param page
* 便3
* @param tag
* @return ObservableList<SearchBookBean>
* Observable
*
*/
Observable<List<SearchBookBean>> searchOtherBook(String content,int page,String tag);
}
}

@ -1,22 +1,7 @@
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.model;
// IWebContentModel接口定义了一个用于分析书籍内容的方法规范
// 具体的实现类需要按照此规范来实现对书籍内容相关的解析操作,
// 可能是从网络获取到的书籍文本内容等进行特定的分析处理。
public interface IWebContentModel {
/**
*
*
* @param s
*
* @param realUrl URL
* URL
* @return
*
* @throws Exception
*
*/
String analyBookcontent(String s,String realUrl) throws Exception;
}

@ -7,48 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
// 实现IWebContentModel接口的类用于处理特定网站的网页内容解析
public class Content17duxsModelImpl implements IWebContentModel {
// 定义一个表示特定网站的标签这里的值是网站的网址
public class Content17duxsModelImpl implements IWebContentModel{
public static final String TAG = "http://www.17duxs.com";
// 获取Content17duxsModelImpl类的单例实例
public static Content17duxsModelImpl getInstance() {
return new Content17duxsModelImpl();
}
// 私有构造方法,按照单例模式的常见思路,防止外部直接实例化该类,不过此处并没有严格限制单例唯一性相关逻辑
private Content17duxsModelImpl() {
}
// 实现IWebContentModel接口中的方法用于解析书籍内容
// 参数s是待解析的网页内容字符串通常是通过网络请求获取到的网页源码等realUrl参数可能是对应的真实网址也许用于一些后续验证或者相关逻辑
// 该方法可能会抛出异常,调用者需要处理相关异常情况
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup解析传入的网页内容字符串得到一个Document对象方便后续对网页DOM结构进行操作
Document doc = Jsoup.parse(s);
// 通过id为"content"获取对应的文本节点列表
List<TextNode> contentEs = doc.getElementById("content").textNodes();
StringBuilder content = new StringBuilder();
// 遍历获取到的文本节点列表
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容并去除前后空白字符trim方法的作用
String temp = contentEs.get(i).text().trim();
// 将文本中的全角空格(&nbsp;对应的字符)和半角空格都替换为空字符串,进行文本内容的格式化清理
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 如果处理后的文本长度大于0说明有有效内容
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 给有效内容添加两个全角空格("\u3000\u3000" 表示两个全角空格)作为缩进格式
content.append("\u3000\u3000" + temp);
// 如果不是最后一个文本节点,添加换行符,用于区分不同的文本节点内容,使解析后的内容格式更合理
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 将处理好的内容以字符串形式返回,最终返回的就是解析并格式化后的书籍内容字符串
return content.toString();
}
}

@ -1,4 +1,4 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.model.impl;
import com.monke.monkeybook.model.IWebContentModel;
@ -8,48 +8,33 @@ import org.jsoup.nodes.Element;
import org.jsoup.nodes.TextNode;
import java.util.List;
// Content17kModelImpl类实现了IWebContentModel接口用于处理来自17k网站相关内容的解析逻辑
public class Content17kModelImpl implements IWebContentModel {
// 定义一个表示17k网站的标签字符串可能用于标识来源等用途
public static final String TAG = "http://www.17k.com";
// 采用单例模式获取Content17kModelImpl类的唯一实例
public static Content17kModelImpl getInstance() {
return new Content17kModelImpl();
}
// 将构造函数私有化保证外部不能通过new关键字随意创建实例符合单例模式的要求
private Content17kModelImpl() {
}
// 实现IWebContentModel接口中的analyBookcontent方法用于解析书籍内容
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup解析传入的字符串通常是网页内容的HTML字符串构建成Document对象方便后续对HTML文档结构进行操作
Document doc = Jsoup.parse(s);
// 通过id获取HTML文档中id为"chapterContentWapper"的元素,这个元素大概率包含了书籍章节的具体内容
Element all = doc.getElementById("chapterContentWapper");
// 获取该元素下所有子元素中的第一个元素包含的文本节点列表此处假设这个结构符合17k网站内容的布局特点
List<TextNode> contentEs = all.getAllElements().get(0).textNodes();
StringBuilder content = new StringBuilder();
// 遍历文本节点列表
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容,并去除首尾空格
String temp = contentEs.get(i).text().trim();
// 替换掉文本中的不间断空格(" ")以及普通空格,进一步清理文本内容
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 如果清理后的文本长度大于0说明有实际内容
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 给文本添加两个全角空格作为缩进格式("\u3000\u3000"),使其在展示时有一定的排版效果
content.append("\u3000\u3000" + temp);
// 如果不是最后一个文本节点,添加换行符,使不同部分的文本内容分行显示
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 将处理好的文本内容以字符串形式返回
return content.toString();
}
}
}

@ -1,21 +1,15 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.model.impl;
import com.monke.monkeybook.model.IWebContentModel;
import org.jsoup.Jsoup;
// 导入Jsoup库中的Document类用于表示解析后的HTML文档结构
import org.jsoup.nodes.Document;
// 导入Jsoup库中的TextNode类用于表示文本节点方便对HTML中的文本内容进行操作
import org.jsoup.nodes.TextNode;
import java.util.List;
// Content3dllcModelImpl类实现了IWebContentModel接口意味着它需要按照该接口定义的规范来实现相应的方法
public class Content3dllcModelImpl implements IWebContentModel {
// 定义一个静态的常量字符串TAG用于标识相关的网址或者某种特定的标记
// 可能在后续的代码逻辑中用于判断来源或者做相关的分类等用途
public class Content3dllcModelImpl implements IWebContentModel{
public static final String TAG = "http://www.3dllc.cc";
// 它用于获取Content3dllcModelImpl类的实例方便外部代码调用获取该类对象进行相关操作
public static Content3dllcModelImpl getInstance() {
return new Content3dllcModelImpl();
}
@ -23,41 +17,21 @@ public class Content3dllcModelImpl implements IWebContentModel {
private Content3dllcModelImpl() {
}
// 实现IWebContentModel接口中定义的analyBookcontent方法用于分析书籍内容。
// 参数s表示要解析的包含书籍内容的HTML字符串
// realUrl参数表示该内容对应的真实网址
// 该方法声明抛出Exception异常意味着在解析过程中如果出现了比如HTML格式不正确、找不到指定元素等问题时会将异常抛给调用者处理
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup的parse方法将传入的HTML字符串解析成一个Document对象
// 这样后续就可以方便地通过Document对象提供的方法来查找、操作HTML文档中的各种元素和内容
Document doc = Jsoup.parse(s);
// 通过Document对象的.getElementById方法根据元素的id属性值这里是"content")来获取对应的元素下的所有文本节点,
// 推测这里的"content"元素可能就是包含了书籍正文内容的HTML元素获取到的是一个TextNode类型的列表方便后续逐个处理这些文本节点内容
List<TextNode> contentEs = doc.getElementById("content").textNodes();
// 创建一个可变的字符串构建器对象,用于逐步构建最终的书籍内容字符串,相比直接使用普通字符串拼接,这样效率更高,尤其在大量字符串拼接操作时
StringBuilder content = new StringBuilder();
// 遍历获取到的文本节点列表
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容,并去除两端的空白字符(空格、制表符等),得到一个相对比较“纯净”的文本内容字符串
String temp = contentEs.get(i).text().trim();
// 将文本内容中的不间断空格(&nbsp;对应的字符)替换为空字符串,同时把普通空格也替换为空字符串,进一步清理文本内容,
// 可能是为了保证最终书籍内容格式的一致性或者符合特定的解析要求
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 如果清理后的文本内容长度大于0说明该文本节点包含有效的文本信息需要将其添加到最终的书籍内容字符串构建器中
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在文本内容前添加两个全角空格(\u3000表示全角空格可能是为了符合某种排版或者显示格式要求比如缩进等
// 然后添加到content字符串构建器中
content.append("\u3000\u3000" + temp);
// 如果当前不是最后一个文本节点,就在后面添加一个换行符(\r\n在Windows系统下常见的换行表示方式
// 使得最终的书籍内容文本在显示时不同文本节点的内容能分行显示,更符合阅读和排版习惯
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 将构建好的包含书籍内容的字符串构建器对象转换为普通字符串并返回,这个字符串就是经过解析、清理和格式化后的书籍正文内容
return content.toString();
}
}
}

@ -1,4 +1,4 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.model.impl;
import com.monke.monkeybook.model.IWebContentModel;
@ -7,48 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
// Content44pqModelImpl类实现了IWebContentModel接口主要用于处理来自http://www.44pq.net网站相关书籍内容的解析操作
public class Content44pqModelImpl implements IWebContentModel {
// 定义一个表示网站来源的标签字符串这里代表的是http://www.44pq.net可能用于后续区分不同来源等情况
public class Content44pqModelImpl implements IWebContentModel{
public static final String TAG = "http://www.44pq.net";
// 使用单例模式获取Content44pqModelImpl类的唯一实例确保在程序中只有一个该类的实例存在
public static Content44pqModelImpl getInstance() {
return new Content44pqModelImpl();
}
// 将构造函数私有化防止外部代码通过new关键字随意创建该类的多个实例符合单例模式的设计要求
private Content44pqModelImpl() {
}
// 重写IWebContentModel接口中的analyBookcontent方法用于解析书籍内容
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库的parse方法将传入的字符串通常是对应网页的HTML内容字符串解析为Document对象
// 这样就可以方便地按照HTML文档结构来操作和提取信息了
Document doc = Jsoup.parse(s);
// 通过id获取Document对象中id为"content"的元素包含的所有文本节点,这里假设该"content"元素包含了书籍章节的实际文本内容,
// 具体依赖于http://www.44pq.net网站页面的HTML结构布局
List<TextNode> contentEs = doc.getElementById("content").textNodes();
StringBuilder content = new StringBuilder();
// 遍历获取到的文本节点列表
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容,并去除其首尾的空白字符,使文本更加规整
String temp = contentEs.get(i).text().trim();
// 替换掉文本中的不间断空格(" ")以及普通空格,进一步清理文本,去除不必要的空白格式
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 如果经过清理后文本的长度大于0说明该文本节点包含有实际有效的内容
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 给有效的文本内容添加两个全角空格("\u3000\u3000")作为缩进,以便在最终展示文本时具有更好的排版效果
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是列表中的最后一个节点,就在后面添加换行符("\r\n"),使不同部分的文本内容分行显示,增强可读性
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 将处理好的包含书籍章节内容的文本以字符串形式返回,以便在其他地方使用该解析后的内容
return content.toString();
}
}
}

@ -1,4 +1,4 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.model.impl;
import com.monke.monkeybook.model.IWebContentModel;
@ -7,59 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
// Content630bookCCModelImpl类实现了IWebContentModel接口主要负责对来自网址为http://www.630book.cc的书籍内容进行解析处理
public class Content630bookCCModelImpl implements IWebContentModel {
// 定义一个公共静态的常量字符串TAG其值为"http://www.630book.cc",用于标识该类所针对处理内容的来源网站。
// 在整个程序架构中,这个标识可能会用于区分不同来源网站的相关逻辑,例如根据不同来源做特定的格式调整或者数据存储等操作
public class Content630bookCCModelImpl implements IWebContentModel{
public static final String TAG = "http://www.630book.cc";
// 这是一个静态方法用于获取Content630bookCCModelImpl类的实例采用的是单例模式。
// 通过这种方式确保在整个程序运行过程中,该类只有一个实例存在,有助于节省内存资源,避免因多次创建实例可能导致的资源浪费和数据不一致等问题
public static Content630bookCCModelImpl getInstance() {
return new Content630bookCCModelImpl();
}
// 将构造函数私有化这是实现单例模式的关键步骤之一。私有化构造函数后外部类无法直接通过new关键字来创建该类的实例
// 只能通过上面定义的getInstance方法来获取唯一的实例以此保证单例模式的正确实现
private Content630bookCCModelImpl() {
}
// 重写了IWebContentModel接口中定义的analyBookcontent方法此方法是整个类的核心功能所在用于解析书籍内容。
// 它接收两个参数一个是字符串s通常代表从网页获取到的包含书籍章节内容的HTML格式的字符串另一个是realUrl可能表示该内容对应的真实网址
// 虽然在当前代码中暂时未看到对realUrl参数的进一步使用但在更复杂的场景下可以基于此参数做更多与网址相关的逻辑处理
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的参数s即包含书籍内容的HTML字符串解析为Document对象。
// Document对象是Jsoup对HTML文档结构的一种表示形式通过它可以方便地按照HTML的文档结构如标签、元素、属性等来查找、提取和操作相应的内容
Document doc = Jsoup.parse(s);
// 通过Document对象的getElementById方法根据元素的id属性查找id为"content"的元素,并获取该元素所包含的所有文本节点。
// 这里假设在http://www.630book.cc网站的页面结构中id为"content"的元素存放着需要解析的书籍章节的实际文本内容,
// 如果网站页面结构发生变化,这个假设可能就不成立了,相应的代码逻辑可能需要调整
List<TextNode> contentEs = doc.getElementById("content").textNodes();
StringBuilder content = new StringBuilder();
// 开始遍历获取到的文本节点列表contentEs对其中的每一个文本节点进行处理
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容并调用trim方法去除文本前后的空白字符包括空格、制表符、换行符等使得获取到的文本更加干净、规范
// 避免多余的空白字符影响后续对文本内容的处理以及最终的展示效果
String temp = contentEs.get(i).text().trim();
// 使用replaceAll方法替换文本中的不间断空格" ")以及普通空格,进一步清理文本内容,确保文本格式的一致性和简洁性,
// 这样在后续拼接和展示文本时不会出现因空格格式不一致而导致的排版问题
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过清理后的文本长度是否大于0如果大于0说明该文本节点包含有实际有意义的文本内容需要将其添加到最终的解析结果中进行拼接处理
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 给有效的文本内容添加两个全角空格("\u3000\u3000")作为缩进,使解析后的文本在展示时(比如在阅读软件或者文本编辑工具中)具有更好的排版效果,
// 更符合一般阅读的视觉习惯,方便读者阅读和区分不同段落的内容
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是列表中的最后一个节点,也就是后面还有其他文本节点需要处理,就在当前文本后面添加换行符("\r\n"
// 这样可以让不同部分的文本内容分行显示,清晰地分隔开各段文本,使整个书籍内容的展示更加有条理,易于阅读和理解
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 将经过上述一系列处理、拼接后的文本内容以字符串形式返回,这个返回的字符串就是最终解析好的书籍章节内容,
// 可以被其他模块(比如显示模块、存储模块等)获取并进一步使用,以实现完整的书籍阅读或者数据处理等功能
return content.toString();
}
}
}

@ -1,4 +1,4 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.model.impl;
import com.monke.monkeybook.model.IWebContentModel;
@ -7,54 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
// Content92zwModelImpl类实现了IWebContentModel接口其主要目的是处理来自网址为http://www.92zw.la相关书籍内容的解析工作
public class Content92zwModelImpl implements IWebContentModel {
// 定义一个静态的常量字符串TAG用于标识该类所针对处理内容的来源网站这里是http://www.92zw.la
// 或许在后续的代码中可用于区分不同网站来源的相关逻辑处理等情况
public class Content92zwModelImpl implements IWebContentModel{
public static final String TAG = "http://www.92zw.la";
// 采用单例模式通过此静态方法获取Content92zwModelImpl类的唯一实例。单例模式可以保证在整个应用程序运行期间
// 该类只有一个实例存在,避免重复创建实例造成资源浪费等问题
public static Content92zwModelImpl getInstance() {
return new Content92zwModelImpl();
}
// 将构造函数私有化这是实现单例模式的常见做法外部代码无法直接通过new关键字来创建该类的多个实例
// 只能通过上面的getInstance方法获取唯一实例
private Content92zwModelImpl() {
}
// 重写IWebContentModel接口中的analyBookcontent方法该方法用于对书籍内容进行具体的解析操作
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库的parse方法将传入的参数s通常是从网页获取到的包含书籍内容的HTML字符串解析为Document对象。
// Document对象可以看作是对整个HTML文档结构的一种抽象表示方便后续基于文档结构去查找和提取相关元素及内容
Document doc = Jsoup.parse(s);
// 通过id属性查找Document对象中id为"contents"的元素并获取该元素包含的所有文本节点。这里假设id为"contents"的元素
// 存放着需要解析的书籍章节的实际文本内容这是基于http://www.92zw.la网站页面HTML结构的特定设计若网站结构改变此处可能需调整
List<TextNode> contentEs = doc.getElementById("contents").textNodes();
StringBuilder content = new StringBuilder();
// 遍历获取到的文本节点列表contentEs对每个文本节点进行相应处理
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容并调用trim方法去除文本前后的空白字符空格、制表符、换行符等使得获取到的文本更干净纯粹
String temp = contentEs.get(i).text().trim();
// 替换文本中的不间断空格(" ")以及普通空格,进一步清理文本内容,去除多余的空白,避免影响后续文本展示的格式和可读性
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过清理后的文本长度是否大于0如果大于0说明该文本节点包含有实际有意义的内容需要进行后续处理和拼接
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 给有效的文本内容添加两个全角空格("\u3000\u3000")作为缩进,使解析后的文本在展示时(比如在文本编辑器或者阅读软件中)
// 具有更好的排版效果,看起来更加整齐美观,符合一般阅读的格式习惯
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是列表中的最后一个节点,也就是后面还有其他文本节点需要处理,就在当前文本后面添加换行符("\r\n"
// 这样可以让不同部分的文本内容分行显示,更清晰地分隔开各段文本内容,方便阅读和查看
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 将经过上述一系列处理、拼接后的文本内容以字符串形式返回,这个返回的字符串就是解析好的书籍章节内容,
// 可以供其他模块进一步使用,比如显示在阅读界面上等
return content.toString();
}
}
}

@ -1,4 +1,4 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.model.impl;
import com.monke.monkeybook.model.IWebContentModel;
@ -7,63 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
// ContentAszwModelImpl类实现了IWebContentModel接口其主要职责是对来自网址为http://www.aszw.org的书籍内容进行解析处理操作
public class ContentAszwModelImpl implements IWebContentModel {
// 定义一个公共静态的常量字符串TAG用于标识该类所处理内容对应的网站来源此处为"http://www.aszw.org"。
// 在整个程序的更宏观层面,这个标识可能会在诸如区分不同网站内容、针对不同来源做差异化处理(比如不同的存储策略、格式转换规则等)等场景中发挥作用
public class ContentAszwModelImpl implements IWebContentModel{
public static final String TAG = "http://www.aszw.org";
// 这是一个静态方法遵循单例模式的设计思路用于获取ContentAszwModelImpl类的唯一实例。
// 单例模式确保在程序运行期间,该类只会存在一个实例对象,这样做有助于避免因频繁创建实例而导致的内存资源浪费,同时保证了数据的一致性等,
// 适用于像这种对特定网站内容解析的功能模块,因为通常不需要多个实例来完成同样的解析任务
public static ContentAszwModelImpl getInstance() {
return new ContentAszwModelImpl();
}
// 将构造函数私有化这是实现单例模式的重要手段之一。通过私有化构造函数外部代码就无法直接通过new关键字来创建该类的多个实例了
// 只能按照既定的单例模式规则通过上面定义的getInstance方法获取唯一可用的实例对象
private ContentAszwModelImpl() {
}
// 重写IWebContentModel接口中定义的analyBookcontent方法此方法是整个类实现书籍内容解析功能的核心所在。
// 它接收两个参数:
// 参数s通常是一个包含了从网页上获取到的书籍章节内容的HTML格式字符串也就是要进行解析处理的原始文本数据
// 参数realUrl表示该内容对应的真实网址不过在当前代码中暂时没有看到针对realUrl的具体使用逻辑在更复杂的应用场景下它可以用于例如验证网址合法性、
// 根据网址不同进行额外的处理或者记录等操作
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的功能强大的parse方法将传入的参数s也就是前面提到的包含书籍内容的HTML字符串解析成一个Document对象。
// Document对象在Jsoup框架里代表着对整个HTML文档结构的一种抽象表示借助它可以方便地按照HTML文档固有的结构像通过标签、元素的id或class属性等方式
// 去查找、提取以及操作相应的内容,为后续解析书籍内容奠定基础
Document doc = Jsoup.parse(s);
// 通过Document对象提供的getElementById方法依据元素的id属性去查找id为"contents"的元素,并获取这个元素所包含的所有文本节点。
// 这里基于对http://www.aszw.org网站页面结构的一种假设即认为id为"contents"的元素里面存放着需要解析提取的书籍章节的实际文本内容。
// 倘若该网站后续对页面结构进行了更改,导致这个元素的位置、作用或者内容存储方式发生变化,那么此处的代码逻辑可能就需要相应地进行调整了
List<TextNode> contentEs = doc.getElementById("contents").textNodes();
StringBuilder content = new StringBuilder();
// 开启一个循环对获取到的文本节点列表contentEs进行遍历依次处理其中的每一个文本节点目的是将它们整理、拼接成最终期望的书籍内容文本格式
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前正在处理的文本节点的文本内容并调用trim方法去除该文本前后可能存在的空白字符例如空格、制表符、换行符等
// 这样做是为了让获取到的文本更加纯粹、规范,避免那些多余的空白字符在后续的文本处理和展示过程中造成不必要的干扰,比如影响排版效果等
String temp = contentEs.get(i).text().trim();
// 利用replaceAll方法来替换文本中的不间断空格" ")以及普通空格,进一步对文本内容进行清理,使得文本的格式更加统一、简洁,
// 确保在后续将各个文本节点的内容拼接起来后,文本整体的展示效果更加整齐、美观,不会因为空格格式不一致等问题而显得杂乱无章
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述清理操作后的文本长度是否大于0如果大于0就说明这个文本节点包含了实际有意义的内容值得添加到最终解析好的书籍内容当中去
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 给有效的文本内容添加两个全角空格("\u3000\u3000")作为缩进,这么做是为了让解析后的书籍内容在展示时(比如在阅读类应用程序或者文本编辑工具里呈现时)
// 具备更好的排版效果,更符合人们日常阅读书籍时对于段落格式、缩进等方面的视觉习惯,方便读者清晰地阅读和区分不同部分的内容
content.append("\u3000\u3000" + temp);
// 如果当前正在处理的文本节点不是列表中的最后一个节点,也就是后面还有其他文本节点需要继续处理和拼接,那么就在当前文本内容后面添加一个换行符("\r\n"
// 这样可以让不同部分的文本内容分行显示,使得整个书籍内容在展示上更加有条理、层次分明,更易于阅读者理解和浏览
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 将经过前面一系列处理、拼接操作后得到的文本内容以字符串的形式返回,这个返回的字符串就是最终解析完成的、符合预期格式的书籍章节内容,
// 它可以被程序中的其他模块(例如用于展示书籍内容的界面模块、负责将内容存储起来的存储模块等)获取并进一步加以利用,以实现完整的书籍阅读或者相关的数据处理等功能
return content.toString();
}
}
}

@ -1,4 +1,4 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.model.impl;
import com.monke.monkeybook.model.IWebContentModel;
@ -7,63 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
// ContentBaishukuModelImpl类实现了IWebContentModel接口其主要功能是针对来自网址为http://baishuku.com的书籍内容进行解析处理。
public class ContentBaishukuModelImpl implements IWebContentModel {
// 定义一个公共静态的常量字符串TAG其值为"http://baishuku.com",用于标识该类所负责处理内容对应的网站来源。
// 在整个程序架构中,这个标识有可能会用于区分不同网站的内容,例如根据不同来源网站做差异化的逻辑处理,像内容格式调整、存储方式的不同选择等操作。
public class ContentBaishukuModelImpl implements IWebContentModel{
public static final String TAG = "http://baishuku.com";
// 这是一个静态方法采用单例模式来获取ContentBaishukuModelImpl类的唯一实例。
// 单例模式确保在程序运行期间,该类只会存在一个实例对象,避免了因多次创建实例而可能造成的内存资源浪费,同时保证了在处理相关内容时数据的一致性等情况。
// 对于这种针对特定网站内容解析的功能模块来说,通常不需要多个实例来完成同样的解析工作,所以使用单例模式较为合适。
public static ContentBaishukuModelImpl getInstance() {
return new ContentBaishukuModelImpl();
}
// 将构造函数私有化这是实现单例模式的关键步骤之一。通过把构造函数设置为私有外部代码就无法直接通过new关键字来创建该类的多个实例了
// 只能按照单例模式规定的方式也就是通过上面定义的getInstance方法来获取唯一可用的实例对象以此保证单例模式的正确实现。
private ContentBaishukuModelImpl() {
}
// 重写了IWebContentModel接口中定义的analyBookcontent方法此方法是整个类实现解析书籍内容这一核心功能的关键所在。
// 它接收两个参数:
// 参数s是一个字符串通常代表从http://baishuku.com网站获取到的包含书籍章节内容的HTML格式的字符串也就是要进行解析处理的原始文本数据
// 参数realUrl表示该内容对应的真实网址不过在当前代码中暂时未看到针对realUrl参数有进一步的使用逻辑在更复杂的应用场景下
// 可以基于这个参数做一些额外的操作,比如验证网址的合法性、根据网址不同做不同的处理策略等。
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的参数s即包含书籍内容的HTML字符串解析为一个Document对象。
// Document对象在Jsoup框架里是对HTML文档结构的一种抽象表示通过它可以方便地按照HTML文档的结构特点例如通过元素的id、class属性等方式
// 去查找、提取和操作相应的内容,这为后续解析书籍内容打下了基础。
Document doc = Jsoup.parse(s);
// 通过Document对象提供的getElementById方法按照元素的id属性查找id为"content"的元素,并获取这个元素包含的所有文本节点。
// 这里基于对http://baishuku.com网站页面结构的一种假设即认为id为"content"的元素存放着需要解析的书籍章节的实际文本内容。
// 如果该网站后续对页面结构进行了改变,导致这个元素的相关情况(如位置、内容存储方式等)发生变化,那么此处的代码逻辑可能就需要相应地调整了。
List<TextNode> contentEs = doc.getElementById("content").textNodes();
StringBuilder content = new StringBuilder();
// 开始循环遍历获取到的文本节点列表contentEs目的是对其中的每一个文本节点进行处理最终将它们整理、拼接成符合要求的书籍内容文本格式。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前正在处理的文本节点的文本内容并调用trim方法去除文本前后可能存在的空白字符比如空格、制表符、换行符等
// 这样做可以让获取到的文本更加纯净、规范,避免多余的空白字符对后续文本处理以及最终展示效果产生不良影响,例如造成排版混乱等问题。
String temp = contentEs.get(i).text().trim();
// 使用replaceAll方法替换文本中的不间断空格" ")以及普通空格,进一步对文本内容进行清理,使得文本格式更加统一、简洁,
// 保证在后续拼接各个文本节点内容后,整个书籍内容的展示效果更加整齐、美观,不会出现因空格格式不一致而显得杂乱的情况。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述清理操作后的文本长度是否大于0如果大于0则说明这个文本节点包含了实际有意义的内容需要将其添加到最终解析好的书籍内容当中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 给有效的文本内容添加两个全角空格("\u3000\u3000")作为缩进,这样做可以让解析后的书籍内容在展示时(比如在阅读应用程序或者文本编辑工具中呈现时)
// 具有更好的排版效果,更符合人们日常阅读书籍时对段落格式、缩进等方面的视觉习惯,便于读者清晰地阅读和区分不同部分的内容。
content.append("\u3000\u3000" + temp);
// 如果当前正在处理的文本节点不是列表中的最后一个节点,也就是后面还有其他文本节点需要继续处理和拼接,
// 那么就在当前文本内容后面添加一个换行符("\r\n"),使得不同部分的文本内容能够分行显示,让整个书籍内容在展示上更有条理、层次分明,更易于阅读者理解和浏览。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 将经过前面一系列处理、拼接操作后得到的文本内容以字符串的形式返回,这个返回的字符串就是最终解析完成的、符合预期格式的书籍章节内容,
// 它可以被程序中的其他模块(例如用于展示书籍内容的界面模块、负责存储书籍内容的存储模块等)获取并进一步加以利用,以实现完整的书籍阅读或者相关的数据处理等功能。
return content.toString();
}
}
}

@ -1,4 +1,4 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.model.impl;
import com.monke.monkeybook.model.IWebContentModel;
@ -7,70 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
// ContentBxwx9ModelImpl类实现了IWebContentModel接口其主要用途是对来自网址为http://www.bxwx9.org的书籍内容进行解析处理操作。
public class ContentBxwx9ModelImpl implements IWebContentModel {
// 定义一个公共静态的常量字符串TAG其值被设定为"http://www.bxwx9.org"该TAG用于标识此解析类所对应的内容来源网站。
// 在整个程序的更大架构中,这个标识可以用于区分不同来源网站的内容,比如针对不同网站的内容采取不同的后续处理逻辑,像存储格式的调整、展示样式的适配等情况。
public class ContentBxwx9ModelImpl implements IWebContentModel{
public static final String TAG = "http://www.bxwx9.org";
// 这是一个遵循单例模式设计的静态方法用于获取ContentBxwx9ModelImpl类的唯一实例。
// 单例模式确保在程序运行期间,此类只会存在一个实例对象,这样做能够避免因重复创建实例而可能导致的内存资源浪费,同时保障在处理相关书籍内容时数据的一致性等方面的要求。
// 对于专门针对特定网站书籍内容解析的功能模块而言,通常一个实例就足以完成相应的解析任务,所以采用单例模式是比较合适的选择。
public static ContentBxwx9ModelImpl getInstance() {
return new ContentBxwx9ModelImpl();
}
// 将构造函数私有化这是实现单例模式的重要手段之一。通过把构造函数设置为私有属性外部代码就无法直接通过使用new关键字来创建该类的多个实例了。
// 外部只能按照单例模式所规定的方式也就是通过调用上面定义的getInstance方法来获取唯一可用的实例对象以此保证单例模式能够正确地得以实现。
private ContentBxwx9ModelImpl() {
}
// 重写了IWebContentModel接口中定义的analyBookcontent方法该方法是整个ContentBxwx9ModelImpl类实现解析书籍内容这一核心功能的关键所在。
// 它接收两个参数:
// 参数s是一个字符串类型的变量通常表示从http://www.bxwx9.org网站获取到的包含书籍章节内容的HTML格式的字符串也就是后续要进行解析处理的原始文本数据
// 参数realUrl同样是字符串类型它代表该内容对应的真实网址不过在当前代码的现有逻辑中暂时没有看到针对这个realUrl参数有进一步的具体使用情况
// 但在更为复杂的应用场景下,这个参数可以被用于诸如验证网址的合法性、根据不同网址执行不同的解析策略或者记录相关网址信息等操作。
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的功能强大的parse方法将传入的参数s即包含书籍内容的HTML字符串解析成为一个Document对象。
// 在Jsoup框架里Document对象是对HTML文档结构进行抽象表示的一种重要形式借助它能够方便地依据HTML文档自身的结构特点例如通过元素的id、class属性等方式
// 去查找、提取以及操作对应的内容,这为后续深入解析书籍内容搭建了良好的基础。
Document doc = Jsoup.parse(s);
// 通过Document对象提供的便捷方法getElementById按照元素的id属性去查找id为"content"的元素,并获取这个元素所包含的所有文本节点。
// 此处基于对http://www.bxwx9.org网站页面结构的一种既有假设即认为id为"content"的这个元素存放着需要解析的书籍章节的实际文本内容。
// 然而,如果该网站后续对自身的页面结构进行了调整修改,导致这个元素在页面中的位置、其所存储内容的方式等方面发生了变化,
// 那么此处的代码逻辑就很可能需要相应地做出适应性的调整改变了。
List<TextNode> contentEs = doc.getElementById("content").textNodes();
StringBuilder content = new StringBuilder();
// 开启一个循环对获取到的文本节点列表contentEs进行逐一的遍历操作目的在于对其中每一个文本节点都按照既定的规则进行处理
// 最终将它们合理地整理、拼接成为符合预期格式的书籍内容文本。
for (int i = 0; i < contentEs.size(); i++) {
// 首先获取当前正在处理的文本节点所包含的文本内容然后调用trim方法去除该文本前后可能存在的空白字符这些空白字符可能包含空格、制表符、换行符等等
// 通过这样的操作可以使得获取到的文本更加纯净、规范,从而避免多余的空白字符对后续文本的处理流程以及最终的展示效果产生不良的影响,
// 例如可能会造成文本排版出现混乱无序的情况等。
String temp = contentEs.get(i).text().trim();
// 接着使用Java中的replaceAll方法来对文本中的不间断空格" ")以及普通空格进行替换操作,进一步对文本内容进行清理优化,
// 以此让文本的格式变得更加统一、简洁,进而保证在后续把各个文本节点的内容拼接起来之后,整个书籍内容在展示的时候能够更加整齐、美观,
// 不会由于空格格式不一致等问题而显得杂乱无章、影响阅读体验。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 对经过上述一系列清理操作后的文本进行长度判断如果其长度大于0那就意味着这个文本节点中包含了实际有意义的内容
// 这样的文本内容是需要被添加到最终解析好的书籍内容当中去的。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 针对有实际意义的文本内容,给它添加两个全角空格("\u3000\u3000")作为缩进,这么做的好处是可以让解析后的书籍内容在展示时(比如在阅读类的应用程序或者文本编辑工具中呈现时),
// 具备更好的排版效果,更加贴合人们日常阅读书籍时对于段落格式、缩进等方面的视觉习惯,方便读者能够更加清晰地阅读和区分不同部分的文本内容。
content.append("\u3000\u3000" + temp);
// 如果当前正在处理的文本节点并非是列表中的最后一个节点,也就是说后面还有其他的文本节点需要继续进行处理和拼接操作,
// 那么就在当前文本内容的后面添加一个换行符("\r\n"),通过这样的方式可以使得不同部分的文本内容能够分行显示,
// 让整个书籍内容在展示方面更加有条理、层次分明,更有助于阅读者去理解和浏览其中的内容。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 经过前面循环中对各个文本节点的处理、拼接等一系列操作之后,将最终得到的文本内容以字符串的形式返回。
// 这个返回的字符串就是经过完整解析、并且符合预期格式要求的书籍章节内容了,它可以被程序中的其他相关模块(例如用于展示书籍内容的界面模块、
// 负责将书籍内容进行存储的存储模块等)获取并进一步加以利用,以此来实现完整的书籍阅读或者其他相关的数据处理等功能。
return content.toString();
}
}
}

@ -1,4 +1,4 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.model.impl;
import com.monke.monkeybook.ErrorAnalyContentManager;
@ -8,75 +8,37 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
// ContentCommendModelImpl类从命名来看可能是用于通用的内容推荐相关模型实现但从代码功能上更像是内容解析相关可能命名不太准确需结合更完整的业务场景判断
public class ContentCommendModelImpl {
// 采用单例模式通过此静态方法获取ContentCommendModelImpl类的唯一实例。
// 单例模式可以保证在整个应用程序运行期间,该类只有一个实例存在,避免重复创建实例造成资源浪费等问题,适用于一些全局唯一的功能模块场景。
public static ContentCommendModelImpl getInstance() {
return new ContentCommendModelImpl();
}
// 将构造函数私有化这是实现单例模式的常见做法外部代码无法直接通过new关键字来创建该类的多个实例
// 只能通过上面的getInstance方法获取唯一实例以此确保类实例的唯一性和对其创建过程的控制。
private ContentCommendModelImpl() {
}
// analyBookcontent方法用于解析书籍内容并将解析结果设置到BookContentBean对象中返回同时处理可能出现的解析异常情况。
// 它接收三个参数:
// - bookContentBean一个BookContentBean类型的对象可能用于承载解析后的书籍内容以及相关状态信息等最终也会将解析结果设置到这个对象中返回。
// - s一个字符串通常代表从网页获取到的包含书籍章节内容的HTML格式的字符串也就是要进行解析处理的原始文本数据。
// - realUrl表示该内容对应的真实网址可能在记录错误信息、判断网站支持情况等方面会用到这个参数。
public BookContentBean analyBookcontent(BookContentBean bookContentBean, String s, String realUrl) throws Exception {
// 调用ErrorAnalyContentManager的单例实例的writeNewErrorUrl方法将当前尝试解析的真实网址记录下来
// 可能用于后续统计哪些网址出现过解析问题或者进行错误分析等相关操作具体功能取决于ErrorAnalyContentManager类的实现逻辑。
ErrorAnalyContentManager.getInstance().writeNewErrorUrl(realUrl);
try {
// 使用Jsoup库的parse方法将传入的参数s即包含书籍内容的HTML字符串解析为Document对象。
// Document对象可以看作是对整个HTML文档结构的一种抽象表示方便后续基于文档结构去查找和提取相关元素及内容
// 例如通过元素的id、class等属性查找特定元素。
try{
Document doc = Jsoup.parse(s);
// 通过id属性查找Document对象中id为"content"的元素,并获取该元素包含的所有文本节点。
// 这里假设id为"content"的元素存放着需要解析的书籍章节的实际文本内容不过这依赖于对应网页的HTML结构设计
// 若网页结构改变,此处获取内容的逻辑可能会失效,需要相应调整。
List<TextNode> contentEs = doc.getElementById("content").textNodes();
StringBuilder content = new StringBuilder();
// 遍历获取到的文本节点列表contentEs对每个文本节点进行相应处理目的是整理并拼接出最终的书籍内容文本。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容并调用trim方法去除文本前后的空白字符空格、制表符、换行符等使得获取到的文本更干净纯粹
// 避免多余空白字符影响后续文本处理和展示效果,比如排版等方面。
String temp = contentEs.get(i).text().trim();
// 替换文本中的不间断空格(" ")以及普通空格,进一步清理文本内容,去除不必要的空白,使文本格式更统一,便于后续拼接展示。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过清理后的文本长度是否大于0如果大于0说明该文本节点包含有实际有意义的内容需要进行后续处理和拼接。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 给有效的文本内容添加两个全角空格("\u3000\u3000")作为缩进,使解析后的文本在展示时(比如在文本编辑器或者阅读软件中)
// 具有更好的排版效果,看起来更加整齐美观,符合一般阅读的格式习惯。
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是列表中的最后一个节点,也就是后面还有其他文本节点需要处理,就在当前文本后面添加换行符("\r\n"
// 这样可以让不同部分的文本内容分行显示,更清晰地分隔开各段文本内容,方便阅读和查看。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 将处理好的包含书籍章节内容的文本设置到bookContentBean对象的durCapterContent属性中
// 这样通过这个对象就可以将解析后的内容传递到其他需要使用的地方了。
bookContentBean.setDurCapterContent(content.toString());
} catch (Exception e) {
// 如果在解析过程中出现异常,先打印异常的堆栈信息,方便进行调试和排查问题,了解异常发生的具体位置和原因。
}catch (Exception e){
e.printStackTrace();
// 设置bookContentBean对象的durCapterContent属性为一段提示信息告知用户当前站点暂时不支持解析
// 并提供了反馈的QQ联系方式以及承诺解决问题的时效等信息用于向用户说明情况。
bookContentBean.setDurCapterContent(realUrl.substring(0, realUrl.indexOf('/', 8)) + "站点暂时不支持解析请反馈给Monke QQ:1105075896,半小时内解决,超级效率的程序员");
// 同时将bookContentBean对象的right属性设置为false表示解析出现了问题该内容可能是不正确或者不完全的
// 其他使用这个对象的地方可以根据这个属性来判断解析结果的有效性。
bookContentBean.setRight(false);
}
// 最后将处理后的bookContentBean对象返回这个对象中包含了解析后的书籍内容以及相关状态信息
// 可供其他模块进一步使用,比如展示给用户或者进行后续的业务逻辑处理等。
return bookContentBean;
}
}
}

@ -1,4 +1,4 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.model.impl;
import com.monke.monkeybook.model.IWebContentModel;
@ -7,73 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
// ContentDhzwModelImpl类实现了IWebContentModel接口其主要功能是针对来自网址为http://www.dhzw.org的书籍内容进行解析处理以提取出可用的文本内容。
public class ContentDhzwModelImpl implements IWebContentModel {
// 定义一个公共静态的常量字符串TAG其值设定为"http://www.dhzw.org",用于标识该类所处理内容对应的网站来源。
// 在整个程序架构中,这个标识可用于区分不同网站的内容,例如后续根据不同来源网站采取不同的处理逻辑,像针对特定网站内容进行格式转换、存储策略调整等操作。
public class ContentDhzwModelImpl implements IWebContentModel{
public static final String TAG = "http://www.dhzw.org";
// 这是一个静态方法采用单例模式来获取ContentDhzwModelImpl类的唯一实例。
// 单例模式能确保在程序运行期间,该类只会存在一个实例对象,避免了因多次创建实例而可能引发的内存资源浪费问题,同时保证了在解析相关书籍内容时数据的一致性等情况。
// 对于这种专门针对特定网站内容解析的功能模块而言,通常只需一个实例就能完成相应的解析任务,所以单例模式是比较合适的设计选择。
public static ContentDhzwModelImpl getInstance() {
return new ContentDhzwModelImpl();
}
// 将构造函数私有化这是实现单例模式的关键步骤之一。通过把构造函数设置为私有外部代码就无法直接通过new关键字来创建该类的多个实例了
// 只能按照单例模式规定的方式也就是通过上面定义的getInstance方法来获取唯一可用的实例对象以此保障单例模式的正确实现。
private ContentDhzwModelImpl() {
}
// 重写IWebContentModel接口中定义的analyBookcontent方法此方法是整个类实现解析书籍内容这一核心功能的关键所在。
// 它接收两个参数:
// 参数s是一个字符串通常代表从http://www.dhzw.org网站获取到的包含书籍章节内容的HTML格式的字符串也就是要进行解析处理的原始文本数据
// 参数realUrl表示该内容对应的真实网址不过在当前代码的现有逻辑中暂时未看到针对realUrl参数有进一步的具体使用情况
// 但在更为复杂的应用场景下,该参数可用于验证网址合法性、根据网址不同执行不同的解析策略或者记录相关网址信息等操作。
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的参数s即包含书籍内容的HTML字符串解析为一个Document对象。
// 在Jsoup框架里Document对象是对HTML文档结构进行抽象表示的一种重要形式借助它能够方便地依据HTML文档自身的结构特点例如通过元素的id、class属性等方式
// 去查找、提取以及操作对应的内容,这为后续深入解析书籍内容搭建了良好的基础。
Document doc = Jsoup.parse(s);
// 通过Document对象提供的便捷方法getElementById按照元素的id属性去查找id为"BookText"的元素,并获取这个元素所包含的所有文本节点。
// 此处基于对http://www.dhzw.org网站页面结构的一种既有假设即认为id为"BookText"的这个元素存放着需要解析的书籍章节的实际文本内容。
// 倘若该网站后续对自身的页面结构进行了调整修改,导致这个元素在页面中的位置、其所存储内容的方式等方面发生了变化,
// 那么此处的代码逻辑就很可能需要相应地做出适应性的调整改变了。
List<TextNode> contentEs = doc.getElementById("BookText").textNodes();
StringBuilder content = new StringBuilder();
// 开启一个循环对获取到的文本节点列表contentEs进行逐一的遍历操作目的在于对其中每一个文本节点都按照既定的规则进行处理
// 最终将它们合理地整理、拼接成为符合预期格式的书籍内容文本。
for (int i = 0; i < contentEs.size(); i++) {
// 首先获取当前正在处理的文本节点所包含的文本内容然后调用trim方法去除该文本前后可能存在的空白字符这些空白字符可能包含空格、制表符、换行符等等
// 通过这样的操作可以使得获取到的文本更加纯净、规范,从而避免多余的空白字符对后续文本的处理流程以及最终的展示效果产生不良的影响,
// 例如可能会造成文本排版出现混乱无序的情况等。
String temp = contentEs.get(i).text().trim();
// 接着使用Java中的replaceAll方法来对文本中的不间断空格" ")以及普通空格进行替换操作,进一步对文本内容进行清理优化,
// 以此让文本的格式变得更加统一、简洁,进而保证在后续把各个文本节点的内容拼接起来之后,整个书籍内容在展示的时候能够更加整齐、美观,
// 不会由于空格格式不一致等问题而显得杂乱无章、影响阅读体验。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 额外的处理:将文本中的全角空格(“ ”)也进行替换去除,进一步清理文本内容,确保文本格式更加规范,此处同样是基于对网站内容格式特点的一种处理需求假设。
temp = temp.replaceAll(" ", "");
// 对经过上述一系列清理操作后的文本进行长度判断如果其长度大于0那就意味着这个文本节点中包含了实际有意义的内容
// 这样的文本内容是需要被添加到最终解析好的书籍内容当中去的。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 针对有实际意义的文本内容,给它添加两个全角空格("\u3000\u3000")作为缩进,这么做的好处是可以让解析后的书籍内容在展示时(比如在阅读类的应用程序或者文本编辑工具中呈现时),
// 具备更好的排版效果,更加贴合人们日常阅读书籍时对于段落格式、缩进等方面的视觉习惯,方便读者能够更加清晰地阅读和区分不同部分的文本内容。
// 这里注意在添加缩进时再次获取了当前文本节点处理后的文本内容调用了一次text().trim().replaceAll(" ","")),可能代码存在一些重复逻辑,可考虑优化。
content.append("\u3000\u3000" + contentEs.get(i).text().trim().replaceAll(" ", ""));
// 如果当前正在处理的文本节点并非是列表中的最后一个节点,也就是说后面还有其他的文本节点需要继续进行处理和拼接操作,
// 那么就在当前文本内容的后面添加一个换行符("\r\n"),通过这样的方式可以使得不同部分的文本内容能够分行显示,
// 让整个书籍内容在展示方面更加有条理、层次分明,更有助于阅读者去理解和浏览其中的内容。
content.append("\u3000\u3000" + contentEs.get(i).text().trim().replaceAll(" ",""));
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 经过前面循环中对各个文本节点的处理、拼接等一系列操作之后,将最终得到的文本内容以字符串的形式返回。
// 这个返回的字符串就是经过完整解析、并且符合预期格式要求的书籍章节内容了,它可以被程序中的其他相关模块(例如用于展示书籍内容的界面模块、
// 负责将书籍内容进行存储的存储模块等)获取并进一步加以利用,以此来实现完整的书籍阅读或者其他相关的数据处理等功能。
return content.toString();
}
}
}

@ -1,4 +1,4 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.model.impl;
import com.monke.monkeybook.model.IWebContentModel;
@ -7,70 +7,32 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
// ContentEasouModelImpl类实现了IWebContentModel接口其主要职责是对来自网址为http://book.easou.com的书籍内容进行解析处理操作。
public class ContentEasouModelImpl implements IWebContentModel {
// 定义一个公共静态的常量字符串TAG其值被设定为"http://book.easou.com"该TAG用于标识此解析类所对应的内容来源网站。
// 在整个程序的更大架构中,这个标识可以用于区分不同来源网站的内容,比如针对不同网站的内容采取不同的后续处理逻辑,像存储格式的调整、展示样式的适配等情况。
public static final String TAG = "http://book.easou.com";
// 这是一个遵循单例模式设计的静态方法用于获取ContentEasouModelImpl类的唯一实例。
// 单例模式确保在程序运行期间,此类只会存在一个实例对象,这样做能够避免因重复创建实例而可能导致的内存资源浪费,同时保障在处理相关书籍内容时数据的一致性等方面的要求。
// 对于专门针对特定网站书籍内容解析的功能模块而言,通常一个实例就足以完成相应的解析任务,所以采用单例模式是比较合适的选择。
public static ContentEasouModelImpl getInstance() {
public static ContentEasouModelImpl getInstance(){
return new ContentEasouModelImpl();
}
// 将构造函数私有化这是实现单例模式的重要手段之一。通过把构造函数设置为私有属性外部代码就无法直接通过使用new关键字来创建该类的多个实例了。
// 外部只能按照单例模式所规定的方式也就是通过调用上面定义的getInstance方法来获取唯一可用的实例对象以此保证单例模式能够正确地得以实现。
private ContentEasouModelImpl() {
private ContentEasouModelImpl(){
}
// 重写了IWebContentModel接口中定义的analyBookcontent方法该方法是整个ContentEasouModelImpl类实现解析书籍内容这一核心功能的关键所在。
// 它接收两个参数:
// 参数s是一个字符串类型的变量通常表示从http://book.easou.com网站获取到的包含书籍章节内容的HTML格式的字符串也就是后续要进行解析处理的原始文本数据
// 参数realUrl同样是字符串类型它代表该内容对应的真实网址不过在当前代码的现有逻辑中暂时没有看到针对这个realUrl参数有进一步的具体使用情况
// 但在更为复杂的应用场景下,这个参数可以被用于诸如验证网址的合法性、根据不同网址执行不同的解析策略或者记录相关网址信息等操作。
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的功能强大的parse方法将传入的参数s即包含书籍内容的HTML字符串解析成为一个Document对象。
// 在Jsoup框架里Document对象是对HTML文档结构进行抽象表示的一种重要形式借助它能够方便地依据HTML文档自身的结构特点例如通过元素的id、class属性等方式
// 去查找、提取以及操作对应的内容,这为后续深入解析书籍内容搭建了良好的基础。
public String analyBookcontent(String s, String realUrl) throws Exception{
Document doc = Jsoup.parse(s);
// 通过Document对象提供的便捷方法getElementsByClass按照元素的class属性去查找class为"show"的元素并获取查找到的第一个该类元素通过调用get(0)
// 然后获取这个元素所包含的所有文本节点。这里基于对http://book.easou.com网站页面结构的一种既有假设即认为class为"show"的这个元素(第一个匹配的)存放着需要解析的书籍章节的实际文本内容。
// 然而,如果该网站后续对自身的页面结构进行了调整修改,导致这个元素在页面中的位置、其所存储内容的方式等方面发生了变化,
// 那么此处的代码逻辑就很可能需要相应地做出适应性的调整改变了。
List<TextNode> contentEs = doc.getElementsByClass("show").get(0).textNodes();
StringBuilder content = new StringBuilder();
// 开启一个循环对获取到的文本节点列表contentEs进行逐一的遍历操作目的在于对其中每一个文本节点都按照既定的规则进行处理
// 最终将它们合理地整理、拼接成为符合预期格式的书籍内容文本。
for (int i = 0; i < contentEs.size(); i++) {
// 首先获取当前正在处理的文本节点所包含的文本内容然后调用trim方法去除该文本前后可能存在的空白字符这些空白字符可能包含空格、制表符、换行符等等
// 通过这样的操作可以使得获取到的文本更加纯净、规范,从而避免多余的空白字符对后续文本的处理流程以及最终的展示效果产生不良的影响,
// 例如可能会造成文本排版出现混乱无序的情况等。
String temp = contentEs.get(i).text().trim();
// 接着使用Java中的replaceAll方法来对文本中的不间断空格" ")以及普通空格进行替换操作,进一步对文本内容进行清理优化,
// 以此让文本的格式变得更加统一、简洁,进而保证在后续把各个文本节点的内容拼接起来之后,整个书籍内容在展示的时候能够更加整齐、美观,
// 不会由于空格格式不一致等问题而显得杂乱无章、影响阅读体验。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 对经过上述一系列清理操作后的文本进行长度判断如果其长度大于0那就意味着这个文本节点中包含了实际有意义的内容
// 这样的文本内容是需要被添加到最终解析好的书籍内容当中去的。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 针对有实际意义的文本内容,给它添加两个全角空格("\u3000\u3000")作为缩进,这么做的好处是可以让解析后的书籍内容在展示时(比如在阅读类的应用程序或者文本编辑工具中呈现时),
// 具备更好的排版效果,更加贴合人们日常阅读书籍时对于段落格式、缩进等方面的视觉习惯,方便读者能够更加清晰地阅读和区分不同部分的文本内容。
content.append("\u3000\u3000" + temp);
// 如果当前正在处理的文本节点并非是列表中的最后一个节点,也就是说后面还有其他的文本节点需要继续进行处理和拼接操作,
// 那么就在当前文本内容的后面添加一个换行符("\r\n"),通过这样的方式可以使得不同部分的文本内容能够分行显示,
// 让整个书籍内容在展示方面更加有条理、层次分明,更有助于阅读者去理解和浏览其中的内容。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 经过前面循环中对各个文本节点的处理、拼接等一系列操作之后,将最终得到的文本内容以字符串的形式返回。
// 这个返回的字符串就是经过完整解析、并且符合预期格式要求的书籍章节内容了,它可以被程序中的其他相关模块(例如用于展示书籍内容的界面模块、
// 负责将书籍内容进行存储的存储模块等)获取并进一步加以利用,以此来实现完整的书籍阅读或者其他相关的数据处理等功能。
return content.toString();
}
}

@ -8,81 +8,34 @@ import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentFuheishuModelImplIWebContentModelTAGhttp://fuheishu.com
*
*
*/
public class ContentFuheishuModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型所对应的网站地址此处为http://fuheishu.com
public class ContentFuheishuModelImpl implements IWebContentModel{
public static final String TAG = "http://fuheishu.com";
/**
* ContentFuheishuModelImpl
* 便使
*
* @return ContentFuheishuModelImpl
*/
public static ContentFuheishuModelImpl getInstance() {
public static ContentFuheishuModelImpl getInstance(){
return new ContentFuheishuModelImpl();
}
/**
*
* getInstance
*/
private ContentFuheishuModelImpl() {
private ContentFuheishuModelImpl(){
}
/**
* IWebContentModelanalyBookcontent
* HTML
*
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库的parse方法将传入的原始字符串通常是HTML内容解析为一个Document对象
// 方便后续通过DOM操作来提取需要的书籍内容相关节点等信息。
public String analyBookcontent(String s, String realUrl) throws Exception{
Document doc = Jsoup.parse(s);
// 从realUrl中提取书籍的唯一标识bookId这里通过截取网址字符串的方式获取。
// 先找到从第21个字符之后此处假设网址的特定结构可能需要根据实际网址情况调整出现的第一个'/'的位置,
// 然后从该位置的下一个字符开始,截取到'.html'之前的字符串作为bookId用于后续定位书籍内容所在的HTML元素。
String bookId = realUrl.substring(realUrl.indexOf("/", 21) + 1, realUrl.indexOf(".html"));
// 通过获取到的bookId在解析后的Document对象中查找id为"content" + bookId的元素并获取其包含的所有文本节点TextNode列表。
// 这些文本节点预计包含了实际的书籍内容信息,后续将对这些节点进行处理以提取出完整的书籍内容。
List<TextNode> contentTNs = doc.getElementById("content" + bookId).textNodes();
// 创建一个StringBuilder对象用于高效地拼接处理后的书籍内容字符串避免频繁创建新的字符串对象带来的性能开销。
String bookId = realUrl.substring(realUrl.indexOf("/",21)+1,realUrl.indexOf(".html"));
List<TextNode> contentTNs = doc.getElementById("content"+bookId).textNodes();
StringBuilder stringBuilder = new StringBuilder();
// 遍历获取到的所有文本节点contentTNs对每个节点进行处理。
for (int i = 0; i < contentTNs.size(); i++) {
// 获取当前文本节点的文本内容,并去除两端的空白字符(空格、制表符、换行等),得到较为纯净的文本内容。
for(int i=0;i<contentTNs.size();i++){
String temp = contentTNs.get(i).text().trim();
// 进一步去除文本内容中的不间断空格(&nbsp;对应的Unicode字符这里替换为空字符串以及普通空格
// 使文本内容的格式更加规整,去除多余的空白间隔。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 如果处理后的文本内容长度大于0说明该文本节点包含有效的内容信息将其添加到stringBuilder中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效文本内容前添加两个全角空格(\u3000作为缩进格式使书籍内容排版更美观符合一般的阅读格式要求。
stringBuilder.append("\u3000\u3000" + temp);
// 如果当前不是最后一个文本节点,就在后面添加一个换行符(\r\n实现不同文本节点内容之间的换行
// 让书籍内容在显示时更有条理,便于阅读。
if (i < contentTNs.size() - 1) {
stringBuilder.append("\r\n");
}
}
}
// 将处理好的书籍内容字符串通过StringBuilder的toString方法转换为普通字符串并返回该结果
// 该字符串即为最终解析并格式化后的书籍内容,可以用于后续的展示、存储等操作。
return stringBuilder.toString();
}
}

@ -7,79 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentKewaishuModelImplIWebContentModelTAGhttp://www.kewaishu.org
*
*/
public class ContentKewaishuModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型所对应的网站地址此处为http://www.kewaishu.org
public class ContentKewaishuModelImpl implements IWebContentModel{
public static final String TAG = "http://www.kewaishu.org";
/**
* ContentKewaishuModelImpl
* 便使
*
*
* @return ContentKewaishuModelImpl
*/
public static ContentKewaishuModelImpl getInstance() {
return new ContentKewaishuModelImpl();
}
/**
*
* getInstance
*/
private ContentKewaishuModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
*
*
* @param s HTML
* @param realUrl 便
* @return 使
* @throws Exception 使JsoupHTMLHTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库的parse方法将传入的原始字符串通常为HTML格式的网页内容解析为一个Document对象
// 这样后续就能基于DOM文档对象模型结构方便地查找和操作网页中的各种元素提取我们需要的书籍内容相关部分。
Document doc = Jsoup.parse(s);
// 首先通过id查找网页中id为"content"的元素,然后在这个元素内查找所有的<p>标签元素,
// 这里取第一个<p>标签元素通过get(0)获取并获取其包含的所有文本节点TextNode列表。
// 假设这里认为书籍内容是包含在这个特定的<p>标签元素中的文本节点集合里,通过这样的方式定位到需要解析的文本内容所在位置。
List<TextNode> contentEs = doc.getElementById("content").getElementsByTag("p").get(0).textNodes();
// 创建一个StringBuilder对象用于高效地拼接处理后的书籍内容字符串相比于直接使用字符串相加操作
// StringBuilder可以减少创建多个临时字符串对象带来的性能开销更适合用于频繁的字符串拼接场景。
StringBuilder content = new StringBuilder();
// 遍历获取到的所有文本节点contentEs对每个节点进行处理和拼接操作。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容,并去除两端的空白字符(如空格、制表符、换行等),得到相对纯净、无多余空白的文本内容。
String temp = contentEs.get(i).text().trim();
// 进一步替换文本内容中的不间断空格Unicode编码为“&nbsp;”对应的字符,这里替换为空字符串)以及普通空格,
// 以此进一步清理文本,让文本格式更加规整,避免出现多余的空白间隔影响阅读体验。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 如果处理后的文本内容长度大于0说明该文本节点包含有效的信息将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效文本内容前添加两个全角空格(\u3000作为缩进格式使最终呈现的书籍内容排版更美观符合一般阅读时的段落格式要求。
content.append("\u3000\u3000" + temp);
// 如果当前不是最后一个文本节点,就在添加的文本内容后面添加一个换行符(\r\n
// 实现不同文本节点内容之间的换行,使书籍内容在展示时更有条理,便于阅读和区分不同的段落内容。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 将经过处理和拼接后存储在StringBuilder中的书籍内容字符串转换为普通字符串通过调用toString方法来实现
// 并将最终整理好的书籍内容字符串返回,该字符串可用于后续的展示、存储等操作,比如在阅读软件中显示书籍文本内容等。
return content.toString();
}
}
}

@ -7,78 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentLeduwoModelImplIWebContentModelTAGhttp://leduwo.com
* 便使
*/
public class ContentLeduwoModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型所对应的网站地址这里对应的是http://leduwo.com方便知晓该解析逻辑针对的具体站点。
public class ContentLeduwoModelImpl implements IWebContentModel{
public static final String TAG = "http://leduwo.com";
/**
* ContentLeduwoModelImpl
* 便
*
* @return ContentLeduwoModelImpl
*/
public static ContentLeduwoModelImpl getInstance() {
return new ContentLeduwoModelImpl();
}
/**
*
* getInstance
*/
private ContentLeduwoModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'便
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTMLHTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库的parse方法将传入的原始字符串一般是HTML格式的网页内容解析为一个Document对象。
// 通过创建Document对象后续就能基于DOM文档对象模型的结构方便地在网页内容中查找、定位和操作各种元素从而提取出我们需要的书籍内容相关部分。
Document doc = Jsoup.parse(s);
// 通过id查找网页中id为"content"的元素并获取该元素包含的所有文本节点TextNode列表。
// 这里假设网页中书籍的具体内容是存放在id为"content"的这个元素内的文本节点当中,以此方式来定位要解析的文本内容所在位置。
List<TextNode> contentEs = doc.getElementById("content").textNodes();
// 创建一个StringBuilder对象用于高效地拼接处理后的书籍内容字符串。
// 相比于直接使用字符串相加操作使用StringBuilder可以避免频繁创建新的字符串对象所带来的性能开销特别适合在需要多次进行字符串拼接的场景下使用。
StringBuilder content = new StringBuilder();
// 遍历获取到的所有文本节点contentEs对每个文本节点进行相应的处理和拼接操作。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容,并去除其两端的空白字符(例如空格、制表符、换行等),得到相对纯净、没有多余空白的文本内容。
String temp = contentEs.get(i).text().trim();
// 进一步替换文本内容中的不间断空格Unicode编码对应的“&nbsp;”字符,这里将其替换为空字符串)以及普通空格,
// 以此进一步清理文本内容,使得文本的格式更加规整,避免出现多余的空白间隔影响阅读体验和内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 如果处理后的文本内容长度大于0说明该文本节点包含有效的信息此时将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效文本内容前添加两个全角空格(\u3000作为缩进格式这样能让最终呈现的书籍内容排版更加美观符合一般阅读时对于段落格式的要求。
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是最后一个文本节点,就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同文本节点内容之间的换行,使得书籍内容在展示时更有条理,方便阅读者区分不同的段落内容。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 将经过处理和拼接后存储在StringBuilder中的书籍内容字符串转换为普通字符串通过调用toString方法来实现这一转换操作
// 最后将整理好的书籍内容字符串返回,这个返回的字符串可以用于后续的各种操作,比如在阅读软件中展示书籍的文本内容、将内容存储到本地文件等。
return content.toString();
}
}
}

@ -7,86 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentLewen8ModelImplIWebContentModelTAGhttp://www.lewen8.com
*
*/
public class ContentLewen8ModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于明确标识该类所对应的网站地址这里设定为http://www.lewen8.com
// 方便在整个程序架构中知晓此解析逻辑具体是针对哪个站点的书籍内容进行操作的。
public class ContentLewen8ModelImpl implements IWebContentModel{
public static final String TAG = "http://www.lewen8.com";
/**
* ContentLewen8ModelImpl
* 便
*
*
* @return ContentLewen8ModelImpl
*/
public static ContentLewen8ModelImpl getInstance() {
return new ContentLewen8ModelImpl();
}
/**
*
* getInstance
*/
private ContentLewen8ModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'便
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception JsoupHTMLHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般为HTML格式的网页内容解析成一个Document对象。
// Document对象基于DOM文档对象模型的结构来表示HTML文档通过它可以方便地按照HTML元素的层级关系、标签名称、属性等特征来查找、定位和操作网页中的各种元素
// 进而帮助我们提取出期望的书籍内容相关部分。
Document doc = Jsoup.parse(s);
// 通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"content"的元素,
// 然后获取该元素所包含的所有文本节点TextNode列表。这里隐含的假设是网页中书籍的实际内容是存放在id为"content"这个元素内部的文本节点当中,
// 通过这种方式来确定要解析的文本内容在整个HTML文档中的具体位置。
List<TextNode> contentEs = doc.getElementById("content").textNodes();
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相较于直接使用字符串相加操作使用StringBuilder可以显著减少创建多个临时字符串对象所带来的性能开销
// 尤其适用于像这种需要频繁进行字符串拼接的场景,能提升程序的运行效率。
StringBuilder content = new StringBuilder();
// 开始遍历获取到的所有文本节点contentEs针对每个文本节点依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentEs.size(); i++) {
// 首先获取当前文本节点的文本内容然后调用trim方法去除文本内容两端的空白字符包括空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,以便后续更清晰地处理和展示。
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符在HTML中常用于表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该文本节点包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的常规要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是遍历过程中的最后一个文本节点,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同文本节点内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件系统等。
return content.toString();
}
}
}

@ -7,90 +7,32 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentPbtxtModelImplIWebContentModelTAGhttp://www.pbtxt.com
* 便
*/
public class ContentPbtxtModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型所对应的网站地址此处明确为http://www.pbtxt.com
// 便于在整个程序体系里知晓此解析逻辑是专门针对该站点的书籍内容而设计的。
public class ContentPbtxtModelImpl implements IWebContentModel{
public static final String TAG = "http://www.pbtxt.com";
/**
* ContentPbtxtModelImpl
*
*
*
* @return ContentPbtxtModelImpl
*/
public static ContentPbtxtModelImpl getInstance() {
public static ContentPbtxtModelImpl getInstance(){
return new ContentPbtxtModelImpl();
}
/**
*
* getInstance
*/
private ContentPbtxtModelImpl() {
private ContentPbtxtModelImpl(){
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'HTML
*
* @param s HTML
* @param realUrl HTML
* @return 使
* @throws Exception 使JsoupHTMLHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般是HTML格式的网页内容解析为一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档通过它能够按照HTML元素的标签、属性、层级关系等特征方便地查找、定位和操作网页中的各种元素
// 从而帮助我们准确地提取出与书籍内容相关的部分。
public String analyBookcontent(String s, String realUrl) throws Exception{
Document doc = Jsoup.parse(s);
// 从'realUrl'中提取书籍的唯一标识bookId此处是通过截取网址字符串的方式来获取。
// 先找到从第21个字符之后这里假设网址具有特定的结构实际应用中可能需要根据具体网址的格式特点来调整出现的第一个'/'的位置,
// 然后从该位置的下一个字符开始,截取到'.html'之前的字符串作为bookId后续将利用这个bookId来定位书籍内容所在的具体HTML元素。
String bookId = realUrl.substring(realUrl.indexOf("/", 21) + 1, realUrl.indexOf(".html"));
// 通过获取到的bookId在解析后的Document对象中查找id为"content" + bookId的元素并获取该元素包含的所有文本节点TextNode列表。
// 这里的假设是书籍内容存放在这个特定id的元素内的文本节点当中通过这样的操作来精准定位到需要解析的书籍内容所在位置。
List<TextNode> contentTNs = doc.getElementById("content" + bookId).textNodes();
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相比于直接使用字符串相加操作使用StringBuilder可以避免频繁创建新的字符串对象所带来的性能开销
// 特别适合在需要多次进行字符串拼接的场景下使用,有助于提升程序运行效率。
String bookId = realUrl.substring(realUrl.indexOf("/",21)+1,realUrl.indexOf(".html"));
List<TextNode> contentTNs = doc.getElementById("content"+bookId).textNodes();
StringBuilder stringBuilder = new StringBuilder();
// 开始遍历获取到的所有文本节点contentTNs针对每个文本节点进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentTNs.size(); i++) {
// 获取当前文本节点的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,方便后续进一步处理和展示。
for(int i=0;i<contentTNs.size();i++){
String temp = contentTNs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该文本节点包含有实际有效的信息需要将其添加到stringBuilder这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
stringBuilder.append("\u3000\u3000" + temp);
// 如果当前文本节点不是遍历过程中的最后一个文本节点,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同文本节点内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentTNs.size() - 1) {
stringBuilder.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return stringBuilder.toString();
}
}

@ -7,86 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentPpxsModelImplIWebContentModelTAGhttp://www.ppxs.net
* 便
*/
public class ContentPpxsModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型所对应的网站地址这里明确为http://www.ppxs.net
// 使得在整个程序架构中能够清晰知晓此解析逻辑是专门针对该站点的书籍内容而设置的。
public class ContentPpxsModelImpl implements IWebContentModel{
public static final String TAG = "http://www.ppxs.net";
/**
* ContentPpxsModelImpl
* 便
*
*
* @return ContentPpxsModelImpl
*/
public static ContentPpxsModelImpl getInstance() {
return new ContentPpxsModelImpl();
}
/**
*
* getInstance
*/
private ContentPpxsModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTMLHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串通常为HTML格式的网页内容解析成一个Document对象。
// Document对象基于DOM文档对象模型来表示HTML文档通过它可以依据HTML元素的各种属性如id、class等、标签名称以及层级关系等
// 方便地查找、定位并操作网页中的各种元素,进而帮助我们准确地提取出与书籍内容相关的文本部分。
Document doc = Jsoup.parse(s);
// 通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"booktext"的元素,
// 然后获取该元素包含的所有文本节点TextNode列表。这里假设书籍内容存放在id为"booktext"的这个元素内的文本节点当中,
// 通过这种方式来定位要解析的书籍内容在整个HTML文档中的具体位置。
List<TextNode> contentEs = doc.getElementById("booktext").textNodes();
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 与直接使用字符串相加操作相比使用StringBuilder可以避免频繁创建新的字符串对象所带来的性能开销
// 尤其适用于像这样需要多次进行字符串拼接的场景,有助于提高程序的运行效率。
StringBuilder content = new StringBuilder();
// 开始遍历获取到的所有文本节点contentEs针对每个文本节点依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,以便后续更好地进行处理和展示。
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该文本节点包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使最终呈现的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是遍历过程中的最后一个文本节点,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同文本节点内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -7,86 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentQulaModelImplIWebContentModelTAGhttp://www.qu.la
* 便
*/
public class ContentQulaModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该类所对应的网站地址这里明确指定为http://www.qu.la
// 这样在整个程序架构中就能清楚地知道这个解析类是针对该网站的书籍内容进行处理的。
public class ContentQulaModelImpl implements IWebContentModel{
public static final String TAG = "http://www.qu.la";
/**
* ContentQulaModelImpl
* 便
*
*
* @return ContentQulaModelImpl
*/
public static ContentQulaModelImpl getInstance() {
return new ContentQulaModelImpl();
}
/**
*
* getInstance
*/
private ContentQulaModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般为HTML格式的网页内容解析成一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档通过它可以按照HTML元素的标签、属性、层级关系等特征方便地查找、定位和操作网页中的各种元素
// 从而帮助我们准确地提取出与书籍内容相关的部分。
Document doc = Jsoup.parse(s);
// 通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"content"的元素,
// 然后获取该元素包含的所有文本节点TextNode列表。这里假设网页中书籍的实际内容是存放在id为"content"这个元素内的文本节点当中,
// 通过这种方式来定位要解析的文本内容在整个HTML文档中的具体位置。
List<TextNode> contentEs = doc.getElementById("content").textNodes();
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相较于直接使用字符串相加操作使用StringBuilder可以避免频繁创建新的字符串对象所带来的性能开销
// 特别适合在像这样需要多次进行字符串拼接的场景下使用,有助于提升程序运行效率。
StringBuilder content = new StringBuilder();
// 开始遍历获取到的所有文本节点contentEs针对每个文本节点依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,以便后续更好地进行处理和展示。
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该文本节点包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是遍历过程中的最后一个文本节点,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同文本节点内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -7,88 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentQzreadModelImplIWebContentModelTAGhttp://www.qzread.com
* HTML
*/
public class ContentQzreadModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于明确标识该类所对应的网站地址此处设定为http://www.qzread.com
// 这样在整个程序架构中就能清晰知晓此解析逻辑是专门针对该站点的书籍内容进行操作的。
public class ContentQzreadModelImpl implements IWebContentModel{
public static final String TAG = "http://www.qzread.com";
/**
* ContentQzreadModelImpl
* 便
*
*
* @return ContentQzreadModelImpl
*/
public static ContentQzreadModelImpl getInstance() {
return new ContentQzreadModelImpl();
}
/**
*
* getInstance
*/
private ContentQzreadModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'便
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception JsoupHTMLHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般为HTML格式的网页内容解析成一个Document对象。
// Document对象基于DOM文档对象模型的结构来表示HTML文档通过它可以按照HTML元素的层级关系、标签名称、属性等特征来查找、定位和操作网页中的各种元素
// 进而帮助我们提取出期望的书籍内容相关部分。
Document doc = Jsoup.parse(s);
// 首先通过getElementsByClass方法依据元素的class属性查找网页中class为"txt"的所有元素,
// 然后取其中的第一个元素通过get(0)获取这是因为假设符合该class的元素中第一个包含了我们要解析的书籍内容相关结构。
// 在这个获取到的元素基础上再通过getElementsByTag方法依据标签名称查找其中的第一个<p>标签元素同样通过get(0)获取),
// 这里又假设书籍内容在这个特定的<p>标签元素内部。最后获取该<p>标签元素所包含的所有文本节点TextNode列表
// 通过这样层层查找的方式来确定要解析的文本内容在整个HTML文档中的具体位置。
List<TextNode> contentEs = doc.getElementsByClass("txt").get(0).getElementsByTag("p").get(0).textNodes();
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相较于直接使用字符串相加操作使用StringBuilder可以显著减少创建多个临时字符串对象所带来的性能开销
// 尤其适用于像这种需要频繁进行字符串拼接的场景,能提升程序的运行效率。
StringBuilder content = new StringBuilder();
// 开始遍历获取到的所有文本节点contentEs针对每个文本节点依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentEs.size(); i++) {
// 首先获取当前文本节点的文本内容然后调用trim方法去除文本内容两端的空白字符包括空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,以便后续更清晰地处理和展示。
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符在HTML中常用于表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该文本节点包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是遍历过程中的最后一个文本节点,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同文本节点内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -7,86 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentRanwenaModelImplIWebContentModelTAGhttp://www.ranwena.com
* 便
*/
public class ContentRanwenaModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于明确标识该内容解析类对应的网站地址这里设定为http://www.ranwena.com
// 使得在整个程序架构中能够清楚知晓此解析逻辑是专门针对该站点的书籍内容而设计的。
public class ContentRanwenaModelImpl implements IWebContentModel{
public static final String TAG = "http://www.ranwena.com";
/**
* ContentRanwenaModelImpl
* 便
*
*
* @return ContentRanwenaModelImpl
*/
public static ContentRanwenaModelImpl getInstance() {
return new ContentRanwenaModelImpl();
}
/**
*
* getInstance
*/
private ContentRanwenaModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTMLHTML
* idHTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般为HTML格式的网页内容解析为一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档借助它能够按照HTML元素的各种属性如id、class等、标签名称以及元素间的层级关系等
// 方便地查找、定位并操作网页中的各种元素,从而准确提取出与书籍内容相关的文本部分。
Document doc = Jsoup.parse(s);
// 通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"content"的元素,
// 然后获取该元素所包含的所有文本节点TextNode列表。这里假设书籍内容存放在id为"content"这个元素内的文本节点当中,
// 通过这种方式来定位要解析的书籍内容在整个HTML文档中的具体位置。
List<TextNode> contentEs = doc.getElementById("content").textNodes();
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相比于直接使用字符串相加操作使用StringBuilder能够避免频繁创建新的字符串对象所带来的性能开销
// 特别适用于像这样需要多次进行字符串拼接的场景,有助于提高程序的运行效率。
StringBuilder content = new StringBuilder();
// 开始遍历获取到的所有文本节点contentEs针对每个文本节点依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,方便后续更好地进行处理和展示。
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该文本节点包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使最终呈现的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是遍历过程中的最后一个文本节点,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同文本节点内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -6,83 +6,23 @@ import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
/**
* ContentShulouModelImplIWebContentModelTAGhttp://www.shulou.cc
*
*/
public class ContentShulouModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型所对应的网站地址此处明确为http://www.shulou.cc
// 便于在整个程序体系里知晓此解析逻辑是专门针对该站点的书籍内容而设计的。
public class ContentShulouModelImpl implements IWebContentModel{
public static final String TAG = "http://www.shulou.cc";
/**
* ContentShulouModelImpl
*
*
*
* @return ContentShulouModelImpl
*/
public static ContentShulouModelImpl getInstance() {
public static ContentShulouModelImpl getInstance(){
return new ContentShulouModelImpl();
}
/**
*
* getInstance
*/
private ContentShulouModelImpl() {
private ContentShulouModelImpl(){
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'
*
* @param s HTML
* @param realUrl
* @return HTML使
* @throws Exception 使JsoupHTMLHTML
*
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般是HTML格式的网页内容解析为一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档通过它能够按照HTML元素的标签、属性、层级关系等特征方便地查找、定位和操作网页中的各种元素
// 从而帮助我们准确地提取出与书籍内容相关的部分。
public String analyBookcontent(String s, String realUrl) throws Exception{
Document doc = Jsoup.parse(s);
// 通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"content"的元素。
// 这里假设网页中书籍的实际内容主要存放在这个id为"content"的元素内通过这种方式来定位要解析的书籍内容在整个HTML文档中的大致位置。
Element contentE = doc.getElementById("content");
// 将获取到的包含书籍内容相关信息的Element对象转换为字符串形式方便后续进行一系列的文本替换操作来清理和格式化内容。
String contentString = contentE.toString();
// 以下是一系列的字符串替换操作目的是去除内容字符串中不需要的空格、换行符、HTML中的特殊空格占位符&nbsp;以及一些不必要的HTML标签等
// 让文本内容更加纯净、格式更加规整,更符合阅读展示的要求。
// 去除所有普通空格
contentString = contentString.replaceAll(" ", "");
// 去除所有换行符
contentString = contentString.replaceAll("\n", "");
// 去除HTML中的不间断空格&nbsp;
contentString = contentString.replaceAll("&nbsp;", "");
// 去除开头的 <div id="content"> 标签,因为已经提取出了其内部的内容,这个标签在最终展示的文本中不需要出现。
contentString = contentString.replaceAll("<divid=\"content\">", "");
// 去除结尾的 </div> 标签同理这也是不需要在最终文本内容里呈现的HTML标签部分。
contentString = contentString.replaceAll("</div>", "");
// 去除空的 <p> 标签(<p></p>),这类空标签对书籍内容展示没有实际意义,去除可使内容更简洁。
contentString = contentString.replaceAll("<p></p>", "");
// 将连续的两个 <br> 标签通常用于HTML中表示换行替换为 \r\n\u3000\u3000
// 其中 \r\n 表示换行,\u3000\u3000 表示添加两个全角空格作为缩进,使文本排版更美观,符合阅读习惯。
contentString = contentString.replaceAll("<br><br>", "\r\n\u3000\u3000");
// 在整个处理后的内容字符串开头添加两个全角空格(\u3000\u3000作为缩进进一步优化排版效果使书籍内容看起来更整齐、美观。
contentString = contentString.replaceAll(" ", "").replaceAll("\n", "").replaceAll("&nbsp;", "").replaceAll("<divid=\"content\">", "").replaceAll("</div>", "").replaceAll("<p></p>","").replaceAll("<br><br>", "\r\n\u3000\u3000");
contentString = "\u3000\u3000" + contentString;
// 返回经过上述一系列处理后的书籍内容字符串,该字符串可用于后续在阅读应用等场景中的展示、存储等操作。
return contentString;
}
}
}

@ -7,86 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentSnwx8ModelImplIWebContentModelTAGhttp://www.snwx8.com
*
*/
public class ContentSnwx8ModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型所对应的网站地址这里明确为http://www.snwx8.com
// 便于在整个程序架构中知晓此解析逻辑是专门针对该站点的书籍内容而设计的。
public class ContentSnwx8ModelImpl implements IWebContentModel{
public static final String TAG = "http://www.snwx8.com";
/**
* ContentSnwx8ModelImpl
* 便
*
*
* @return ContentSnwx8ModelImpl
*/
public static ContentSnwx8ModelImpl getInstance() {
return new ContentSnwx8ModelImpl();
}
/**
*
* getInstance
*/
private ContentSnwx8ModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTMLHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般为HTML格式的网页内容解析成一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档通过它可以按照HTML元素的标签、属性、层级关系等特征方便地查找、定位和操作网页中的各种元素
// 从而帮助我们准确地提取出与书籍内容相关的部分。
Document doc = Jsoup.parse(s);
// 通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"BookText"的元素,
// 然后获取该元素包含的所有文本节点TextNode列表。这里假设网页中书籍的实际内容是存放在id为"BookText"这个元素内的文本节点当中,
// 通过这种方式来定位要解析的文本内容在整个HTML文档中的具体位置。
List<TextNode> contentEs = doc.getElementById("BookText").textNodes();
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相较于直接使用字符串相加操作使用StringBuilder可以避免频繁创建新的字符串对象所带来的性能开销
// 特别适合在像这样需要多次进行字符串拼接的场景下使用,有助于提升程序运行效率。
StringBuilder content = new StringBuilder();
// 开始遍历获取到的所有文本节点contentEs针对每个文本节点依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,以便后续更好地进行处理和展示。
for(int i=0;i<contentEs.size();i++){
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该文本节点包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if(temp.length()>0){
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是遍历过程中的最后一个文本节点,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同文本节点内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -7,86 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentSuimengModelImplIWebContentModelTAGhttp://www.suimeng.la
* 便
*/
public class ContentSuimengModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该类所对应的网站地址这里明确指定为http://www.suimeng.la
// 这样在整个程序架构中就能清楚地知道这个解析类是针对该网站的书籍内容进行处理的。
public class ContentSuimengModelImpl implements IWebContentModel{
public static final String TAG = "http://www.suimeng.la";
/**
* ContentSuimengModelImpl
* 便
*
*
* @return ContentSuimengModelImpl
*/
public static ContentSuimengModelImpl getInstance() {
return new ContentSuimengModelImpl();
}
/**
*
* getInstance
*/
private ContentSuimengModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTMLHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般为HTML格式的网页内容解析成一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档通过它可以按照HTML元素的标签、属性、层级关系等特征方便地查找、定位和操作网页中的各种元素
// 从而帮助我们准确地提取出与书籍内容相关的部分。
Document doc = Jsoup.parse(s);
// 通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"ccontent"的元素,
// 然后获取该元素包含的所有文本节点TextNode列表。这里假设网页中书籍的实际内容是存放在id为"ccontent"这个元素内的文本节点当中,
// 通过这种方式来定位要解析的文本内容在整个HTML文档中的具体位置。
List<TextNode> contentEs = doc.getElementById("ccontent").textNodes();
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相较于直接使用字符串相加操作使用StringBuilder可以避免频繁创建新的字符串对象所带来的性能开销
// 特别适合在像这样需要多次进行字符串拼接的场景下使用,有助于提升程序运行效率。
StringBuilder content = new StringBuilder();
// 开始遍历获取到的所有文本节点contentEs针对每个文本节点依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,以便后续更好地进行处理和展示。
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该文本节点包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是遍历过程中的最后一个文本节点,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同文本节点内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -8,85 +8,33 @@ import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentSyzwwModelImplIWebContentModelTAGhttp://www.syzww.net
*
*
*/
public class ContentSyzwwModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型所对应的网站地址此处明确为http://www.syzww.net
// 便于在整个程序体系中知晓此解析逻辑是针对该站点的书籍内容而设计的。
public class ContentSyzwwModelImpl implements IWebContentModel{
public static final String TAG = "http://www.syzww.net";
/**
* ContentSyzwwModelImpl
* 便
*
*
* @return ContentSyzwwModelImpl
*/
public static ContentSyzwwModelImpl getInstance() {
return new ContentSyzwwModelImpl();
}
/**
*
* getInstance
*/
private ContentSyzwwModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTMLHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般是HTML格式的网页内容解析为一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档通过它能够按照HTML元素的标签、属性、层级关系等特征方便地查找、定位和操作网页中的各种元素
// 从而帮助我们准确地提取出与书籍内容相关的部分。
Document doc = Jsoup.parse(s);
// 通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"content"的元素,
// 然后获取该元素包含的所有文本节点TextNode列表。这里假设网页中书籍的实际内容是存放在id为"content"这个元素内的文本节点当中,
// 通过这种方式来定位要解析的文本内容在整个HTML文档中的具体位置。
List<TextNode> contentEs = doc.getElementById("content").textNodes();
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相比于直接使用字符串相加操作使用StringBuilder可以避免频繁创建新的字符串对象所带来的性能开销
// 特别适合在需要多次进行字符串拼接的场景下使用,有助于提升程序运行效率。
StringBuilder content = new StringBuilder();
// 开始遍历获取到的所有文本节点contentEs针对每个文本节点依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,以便后续更好地进行处理和展示。
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该文本节点包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是遍历过程中的最后一个文本节点,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同文本节点内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -7,89 +7,34 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
/**
* ContentVodtwModelImplIWebContentModelTAGhttp://www.vodtw.com
* 便
*/
public class ContentVodtwModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型所对应的网站地址此处明确为http://www.vodtw.com
// 便于在整个程序体系里知晓此解析逻辑是专门针对该站点的书籍内容而设计的。
public static final String TAG = "http://www.vodtw.com";
/**
* ContentVodtwModelImpl
*
*
*
* @return ContentVodtwModelImpl
*/
public static ContentVodtwModelImpl getInstance() {
return new ContentVodtwModelImpl();
}
/**
*
* getInstance
*/
private ContentVodtwModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTMLHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般是HTML格式的网页内容解析为一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档通过它能够按照HTML元素的标签、属性、层级关系等特征方便地查找、定位和操作网页中的各种元素
// 从而帮助我们准确地提取出与书籍内容相关的部分。
Document doc = Jsoup.parse(s);
// 通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"BookText"的元素。
// 这里假设网页中书籍的实际内容主要存放在这个id为"BookText"的元素内通过这种方式来定位要解析的书籍内容在整个HTML文档中的大致位置。
Element contentE = doc.getElementById("BookText");
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相比于直接使用字符串相加操作使用StringBuilder能够避免频繁创建新的字符串对象所带来的性能开销
// 特别适合在像这样需要多次进行字符串拼接的场景,有助于提高程序的运行效率。
StringBuilder content = new StringBuilder();
// 通过获取到的contentE元素使用getElementsByTag方法依据标签名称"p"来查找其内部所有的<p>标签元素,
// 并将这些元素存储在Elements集合中这里假设书籍内容是分布在这些<p>标签内的文本信息,后续将从这些<p>标签元素中提取具体文本内容。
Elements contentEs = contentE.getElementsByTag("p");
// 开始遍历获取到的所有<p>标签元素contentEs针对每个元素依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前<p>标签元素的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,方便后续更好地进行处理和展示。
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该<p>标签元素包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 如果当前<p>标签元素不是遍历过程中的最后一个元素,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同<p>标签元素内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -7,86 +7,31 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import java.util.List;
/**
* ContentWxguanModelImplIWebContentModelTAGhttp://www.wxguan.com
* 便
*/
public class ContentWxguanModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于明确标识该内容解析类对应的网站地址这里设定为http://www.wxguan.com
// 便于在整个程序架构中知晓此解析逻辑是专门针对该站点的书籍内容而设计的。
public class ContentWxguanModelImpl implements IWebContentModel{
public static final String TAG = "http://www.wxguan.com";
/**
* ContentWxguanModelImpl
* 便
*
*
* @return ContentWxguanModelImpl
*/
public static ContentWxguanModelImpl getInstance() {
return new ContentWxguanModelImpl();
}
/**
*
* getInstance
*/
private ContentWxguanModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'便
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTMLHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般为HTML格式的网页内容解析成一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档通过它可以按照HTML元素的标签、属性、层级关系等特征方便地查找、定位和操作网页中的各种元素
// 进而帮助我们准确地提取出与书籍内容相关的部分。
Document doc = Jsoup.parse(s);
// 通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"content"的元素,
// 然后获取该元素包含的所有文本节点TextNode列表。这里假设网页中书籍的实际内容是存放在id为"content"这个元素内的文本节点当中,
// 通过这种方式来定位要解析的文本内容在整个HTML文档中的具体位置。
List<TextNode> contentEs = doc.getElementById("content").textNodes();
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相较于直接使用字符串相加操作使用StringBuilder可以避免频繁创建新的字符串对象所带来的性能开销
// 特别适合在像这样需要多次进行字符串拼接的场景下使用,有助于提升程序运行效率。
StringBuilder content = new StringBuilder();
// 开始遍历获取到的所有文本节点contentEs针对每个文本节点依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前文本节点的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,以便后续更好地进行处理和展示。
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该文本节点包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 如果当前文本节点不是遍历过程中的最后一个文本节点,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同文本节点内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -6,87 +6,32 @@ import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
/**
* ContentXqingdouCCModelImplIWebContentModelTAGhttp://www.xqingdou.cc
* 便
*/
public class ContentXqingdouCCModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型所对应的网站地址此处明确为http://www.xqingdou.cc
// 便于在整个程序体系里知晓此解析逻辑是专门针对该站点的书籍内容而设计的。
public static final String TAG = "http://www.xqingdou.cc";
/**
* ContentXqingdouCCModelImpl
*
*
*
* @return ContentXqingdouCCModelImpl
*/
public static ContentXqingdouCCModelImpl getInstance() {
return new ContentXqingdouCCModelImpl();
}
/**
*
* getInstance
*/
private ContentXqingdouCCModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTMLHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般是HTML格式的网页内容解析为一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档通过它能够按照HTML元素的标签、属性、层级关系等特征方便地查找、定位和操作网页中的各种元素
// 从而帮助我们准确地提取出与书籍内容相关的部分。
public String analyBookcontent(String s, String realUrl) throws Exception{
Document doc = Jsoup.parse(s);
// 首先通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"chapter_content"的元素,
// 这里假设书籍内容所在的区域是在这个id为"chapter_content"的元素内部。
// 然后在此元素基础上再使用getElementsByTag方法依据标签名称"p"来查找其内部所有的<p>标签元素,
// 并将这些<p>标签元素存储在Elements集合中通常会假设书籍的具体文本内容是分布在这些<p>标签当中,后续将从这些元素中提取具体的文本内容。
Elements contentEs = doc.getElementById("chapter_content").getElementsByTag("p");
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相比于直接使用字符串相加操作使用StringBuilder可以避免频繁创建新的字符串对象所带来的性能开销
// 特别适合在像这样需要多次进行字符串拼接的场景下使用,有助于提升程序运行效率。
StringBuilder content = new StringBuilder();
// 开始遍历获取到的所有<p>标签元素contentEs针对每个元素依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前<p>标签元素的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,方便后续更好地进行处理和展示。
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该<p>标签元素包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 如果当前<p>标签元素不是遍历过程中的最后一个元素,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同<p>标签元素内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -6,87 +6,32 @@ import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
/**
* ContentXqingdouModelImplIWebContentModelTAGhttp://www.xqingdou.com
* HTML
*/
public class ContentXqingdouModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型对应的网站地址这里明确指定为http://www.xqingdou.com
// 便于在整个程序架构中知晓此解析逻辑是专门针对该站点书籍内容而设计的。
public static final String TAG = "http://www.xqingdou.com";
/**
* ContentXqingdouModelImpl
* 便
*
*
* @return ContentXqingdouModelImpl
*/
public static ContentXqingdouModelImpl getInstance() {
return new ContentXqingdouModelImpl();
}
/**
*
* getInstance
*/
private ContentXqingdouModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTMLHTML
* ID
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串通常为HTML格式的网页内容解析成一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档借助它可以依据HTML元素的标签、属性、层级关系等特征方便地查找、定位和操作网页中的各种元素
// 从而帮助我们准确地提取出与书籍内容相关的部分。
public String analyBookcontent(String s, String realUrl) throws Exception{
Document doc = Jsoup.parse(s);
// 首先通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"chapter_content"的元素,
// 这里假设书籍内容所在的主要区域是在这个id为"chapter_content"的元素内部,这是定位书籍文本内容大致位置的第一步。
// 接着,在找到的"chapter_content"元素基础上调用getElementsByTag方法依据标签名称"p"来查找该元素内部所有的<p>标签元素,
// 并将这些<p>标签元素存储在Elements集合中通常认为书籍的具体文本内容是分布在这些<p>标签里面的,后续将从这些元素中提取具体文本内容进行整理。
Elements contentEs = doc.getElementById("chapter_content").getElementsByTag("p");
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相较于直接使用字符串相加操作使用StringBuilder能避免频繁创建新的字符串对象所带来的性能开销
// 特别适合在像这样需要多次进行字符串拼接的场景下使用,有助于提升程序运行效率。
StringBuilder content = new StringBuilder();
// 开始遍历获取到的所有<p>标签元素contentEs针对每个元素依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前<p>标签元素的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,以便后续更清晰地处理和展示。
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该<p>标签元素包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 如果当前<p>标签元素不是遍历过程中的最后一个元素,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同<p>标签元素内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -9,125 +9,52 @@ import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;
import java.util.List;
/**
* ContentYb3ModelImplIWebContentModelTAGhttp://www.yb3.cc
*
* 便
*/
public class ContentYb3ModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型所对应的网站地址此处明确为http://www.yb3.cc
// 便于在整个程序体系里知晓此解析逻辑是专门针对该站点的书籍内容而设计的。
public class ContentYb3ModelImpl implements IWebContentModel{
public static final String TAG = "http://www.yb3.cc";
/**
* ContentYb3ModelImpl
*
*
*
* @return ContentYb3ModelImpl
*/
public static ContentYb3ModelImpl getInstance() {
return new ContentYb3ModelImpl();
}
/**
*
* getInstance
*/
private ContentYb3ModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
*
* 's'HTML
* 'realUrl'
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTMLHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般是HTML格式的网页内容解析为一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档通过它能够按照HTML元素的标签、属性、层级关系等特征方便地查找、定位和操作网页中的各种元素
// 从而帮助我们准确地提取出与书籍内容相关的部分。
Document doc = Jsoup.parse(s);
// 通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"content"的元素,
// 这里假设书籍内容所在的主要区域是在这个id为"content"的元素内部,这是定位书籍文本内容大致位置的第一步。
Element contentE = doc.getElementById("content");
// 获取id为"content"的元素下包含的所有文本节点TextNode列表尝试第一种提取书籍内容的方式
// 即直接从该元素下的文本节点中获取文本内容,后续会判断这些文本节点是否符合要求来进行相应处理。
List<TextNode> contentTextNodes = contentE.textNodes();
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相比于直接使用字符串相加操作使用StringBuilder可以避免频繁创建新的字符串对象所带来的性能开销
// 特别适合在像这样需要多次进行字符串拼接的场景下使用,有助于提升程序运行效率。
StringBuilder content = new StringBuilder();
// 首先判断获取到的文本节点列表是否不为空且包含至少一个文本节点,说明可以尝试从这些文本节点中提取内容。
if (contentTextNodes!= null && contentTextNodes.size() > 0) {
// 开始遍历获取到的所有文本节点contentTextNodes针对每个文本节点依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
if(contentTextNodes!=null && contentTextNodes.size()>0){
for (int i = 0; i < contentTextNodes.size(); i++) {
// 获取当前文本节点的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 再判断处理后的文本内容长度是否大于0如果大于0则说明该文本节点包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
if (contentTextNodes.get(i).text().trim().length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + contentTextNodes.get(i).text().trim());
// 如果当前文本节点不是遍历过程中的最后一个文本节点,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同文本节点内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentTextNodes.size() - 1) {
content.append("\r\n");
}
}
}
} else {
// 如果通过第一种方式(直接从文本节点获取内容)没有获取到有效内容,说明网页结构可能不太一样,
// 则尝试另一种方式获取id为"content"的元素的所有子元素children通过遍历这些子元素来提取可能存在的书籍内容文本。
}else{
Elements contentEs = contentE.children();
// 开始遍历这些子元素contentEs尝试从每个子元素中提取和整理书籍内容文本。
for (int i = 0; i < contentEs.size(); i++) {
// 获取当前子元素的文本内容转换为字符串后调用trim方法去除两端空白字符再将其中的全角空格Unicode编码对应的“ ”字符替换为空字符串
// 然后判断处理后的文本内容长度是否大于0如果大于0则说明该子元素可能包含有实际有效的信息需要进一步处理来提取其中的书籍内容文本。
if (contentEs.get(i).text().toString().trim().replaceAll(" ", "").length() > 0) {
// 获取当前子元素下包含的所有文本节点TextNode列表尝试从这些文本节点中提取更细化的书籍内容文本信息。
if (contentEs.get(i).text().toString().trim().replaceAll(" ","").length() > 0) {
List<TextNode> tempTextNodes = contentEs.get(i).textNodes();
// 判断获取到的文本节点列表是否不为空且包含至少一个文本节点,如果是,则进行相应的文本提取和整理操作。
if (tempTextNodes!= null && tempTextNodes.size() > 0) {
// 开始遍历当前子元素下的所有文本节点tempTextNodes针对每个文本节点依次进行相应的处理和拼接操作以构建最终的书籍内容字符串。
if(tempTextNodes!=null && tempTextNodes.size()>0){
for (int j = 0; j < tempTextNodes.size(); j++) {
// 获取当前文本节点的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
String temp = tempTextNodes.get(j).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该文本节点包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 添加一个换行符(\r\n实现不同文本节点内容之间的换行让书籍内容在展示时更有条理。
content.append("\r\n");
}
}
} else {
// 如果当前子元素下没有文本节点(或者文本节点列表为空),但该子元素本身的文本内容经过前面处理后还有效,
// 则直接将其文本内容去除全角空格后添加到content这个StringBuilder对象中并添加一个换行符\r\n实现换行效果。
content.append("\u3000\u3000" + contentEs.get(i).text().trim().replaceAll(" ", ""));
}else{
content.append("\u3000\u3000" + contentEs.get(i).text().trim().replaceAll(" ",""));
content.append("\r\n");
}
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -6,88 +6,31 @@ import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
/**
* ContentZhulangModelImplIWebContentModelTAGhttp://book.zhulang.com
*
*/
public class ContentZhulangModelImpl implements IWebContentModel {
// 定义一个静态常量TAG用于标识该内容解析模型所对应的网站地址此处明确为http://book.zhulang.com
// 便于在整个程序体系里知晓此解析逻辑是专门针对该站点的书籍内容而设计的。
public class ContentZhulangModelImpl implements IWebContentModel{
public static final String TAG = "http://book.zhulang.com";
/**
* ContentZhulangModelImpl
*
*
*
* @return ContentZhulangModelImpl
*/
public static ContentZhulangModelImpl getInstance() {
return new ContentZhulangModelImpl();
}
/**
*
* getInstance
*/
private ContentZhulangModelImpl() {
}
/**
* IWebContentModelanalyBookcontent
* 's'HTML
* 'realUrl'
*
* @param s HTML
* @param realUrl
* @return 使
* @throws Exception 使JsoupHTMLHTML
* HTML
*/
@Override
public String analyBookcontent(String s, String realUrl) throws Exception {
// 使用Jsoup库提供的parse方法将传入的原始字符串一般是HTML格式的网页内容解析为一个Document对象。
// Document对象基于DOM文档对象模型结构来表示HTML文档通过它能够按照HTML元素的标签、属性、层级关系等特征方便地查找、定位和操作网页中的各种元素
// 从而帮助我们准确地提取出与书籍内容相关的部分。
Document doc = Jsoup.parse(s);
// 通过调用Document对象的getElementById方法依据元素的id属性查找网页中id为"read-content"的元素,
// 然后获取该元素的所有子元素children并将这些子元素存储在Elements集合中这里假设书籍内容是分布在这些子元素当中
// 通过后续遍历这些子元素来提取具体的文本内容。
Elements contentEs = doc.getElementById("read-content").children();
// 创建一个StringBuilder对象用于高效地拼接整理后的书籍内容字符串。
// 相比于直接使用字符串相加操作使用StringBuilder可以避免频繁创建新的字符串对象所带来的性能开销
// 特别适合在像这样需要多次进行字符串拼接的场景下使用,有助于提升程序运行效率。
StringBuilder content = new StringBuilder();
// 开始遍历获取到的子元素contentEs不过这里注意起始索引为3结束索引为contentEs.size() - 1
// 说明可能跳过了前面几个特定的元素(可能是网页中一些非书籍正文内容的元素,比如标题、导航栏之类的相关元素),
// 同时也排除了最后一个元素(可能有一些特殊格式或者不属于正文内容的元素),仅对中间认为是书籍正文内容的子元素进行处理。
for (int i = 3; i < contentEs.size() - 1; i++) {
// 获取当前子元素的文本内容然后调用trim方法去除文本内容两端的空白字符包含空格、制表符、换行等
// 得到相对纯净、没有多余空白干扰的文本内容,方便后续更好地进行处理和展示。
for (int i = 3; i < contentEs.size()-1; i++) {
String temp = contentEs.get(i).text().trim();
// 进一步对处理后的文本内容进行清理将其中的不间断空格Unicode编码对应的“&nbsp;”字符常用于HTML中表示空格占位等情况以及普通空格都替换为空字符串
// 这样可以让文本的格式更加规整,避免因多余的空白间隔而影响阅读体验以及内容展示的美观性。
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
// 判断经过上述处理后的文本内容长度是否大于0如果大于0则说明该子元素包含有实际有效的信息需要将其添加到content这个StringBuilder对象中。
temp = temp.replaceAll(" ","").replaceAll(" ","");
if (temp.length() > 0) {
// 在每个有效的文本内容前面添加两个全角空格(\u3000作为缩进格式使得最终呈现出来的书籍内容在排版上更加美观
// 更符合一般阅读时对于段落格式的要求,便于读者区分不同的内容段落。
content.append("\u3000\u3000" + temp);
// 如果当前子元素不是遍历过程中的最后一个子元素,那么就在添加的文本内容后面添加一个换行符(\r\n
// 以此实现不同子元素内容之间的换行,让书籍内容在展示时更有条理,方便读者清晰地阅读和理解。
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
// 最后通过调用StringBuilder对象的toString方法将经过一系列处理和拼接后存储在其中的书籍内容字符串转换为普通字符串格式
// 并将这个整理好的书籍内容字符串返回,该返回值可以用于后续的各种操作,比如在阅读应用程序中展示给用户查看、保存到本地文件等。
return content.toString();
}
}
}

@ -33,61 +33,32 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
/**
* GxwztvBookModelImplMBaseModelImplIGxwztvBookModelTAGhttp://www.gxwztv.com
* HTML
* RxJava
*/
public class GxwztvBookModelImpl extends MBaseModelImpl implements IGxwztvBookModel {
// 定义一个静态常量TAG用于标识该模型所对应的网站地址此处明确为http://www.gxwztv.com
// 便于在整个程序体系里知晓此模型相关的网络操作、数据解析等逻辑是针对该站点而设计的。
public static final String TAG = "http://www.gxwztv.com";
/**
* GxwztvBookModelImpl
*
*
*
* @return GxwztvBookModelImpl
*/
public static GxwztvBookModelImpl getInstance() {
return new GxwztvBookModelImpl();
}
/**
*
*
* Observable
* 便RxJava
*
* @param aCache ACache便访
* @return ObservableLibraryBean
*/
@Override
public Observable<LibraryBean> getLibraryData(final ACache aCache) {
return getRetrofitObject(TAG).create(IGxwztvApi.class).getLibraryData("")
// 使用flatMap操作符将网络请求获取到的字符串数据进行进一步处理先尝试缓存数据再进行解析操作
.flatMap(new Function<String, ObservableSource<LibraryBean>>() {
@Override
public ObservableSource<LibraryBean> apply(String s) throws Exception {
// 如果获取到的数据不为空且长度大于0并且缓存对象不为空则将数据存入缓存缓存的键由LibraryPresenterImpl.LIBRARY_CACHE_KEY指定
if (s!= null && s.length() > 0 && aCache!= null) {
aCache.put(LibraryPresenterImpl.LIBRARY_CACHE_KEY, s);
}
// 调用解析主页数据的方法将获取到的数据传入进行解析并返回解析后的Observable对象
return analyLibraryData(s);
}
});
return getRetrofitObject(TAG).create(IGxwztvApi.class).getLibraryData("").flatMap(new Function<String, ObservableSource<LibraryBean>>() {
@Override
public ObservableSource<LibraryBean> apply(String s) throws Exception {
if (s != null && s.length() > 0 && aCache != null) {
aCache.put(LibraryPresenterImpl.LIBRARY_CACHE_KEY, s);
}
return analyLibraryData(s);
}
});
}
/**
*
* ObservableHTML
* LibraryBeanObservableEmitter
*
* @param data HTML
* @return ObservableLibraryBean
*/
@Override
public Observable<LibraryBean> analyLibraryData(final String data) {
@ -95,35 +66,24 @@ public class GxwztvBookModelImpl extends MBaseModelImpl implements IGxwztvBookMo
@Override
public void subscribe(ObservableEmitter<LibraryBean> e) throws Exception {
LibraryBean result = new LibraryBean();
// 使用Jsoup库将传入的HTML格式字符串数据解析为Document对象以便后续按照DOM结构查找和提取元素及内容
Document doc = Jsoup.parse(data);
// 通过元素的类名获取名为"container"的元素,这里假设主页的主要内容都在这个元素内部,是后续提取各种信息的基础元素
Element contentE = doc.getElementsByClass("container").get(0);
// 解析最新书籍信息
// 获取所有类名为"list-group-item text-nowrap modal-open"的元素,这些元素被认为包含了最新书籍的相关信息
//解析最新书籍
Elements newBookEs = contentE.getElementsByClass("list-group-item text-nowrap modal-open");
List<LibraryNewBookBean> libraryNewBooks = new ArrayList<LibraryNewBookBean>();
for (int i = 0; i < newBookEs.size(); i++) {
// 从每个包含最新书籍信息的元素中获取<a>标签元素,通常<a>标签包含了书籍的链接、书名等关键信息
Element itemE = newBookEs.get(i).getElementsByTag("a").get(0);
// 创建一个LibraryNewBookBean对象将书名、书籍链接等信息封装进去其中链接是在TAG基础上拼接获取到的相对链接
LibraryNewBookBean item = new LibraryNewBookBean(itemE.text(), TAG + itemE.attr("href"), TAG, "gxwztv.com");
libraryNewBooks.add(item);
}
result.setLibraryNewBooks(libraryNewBooks);
// 解析不同分类书籍信息,先初始化一个空的列表用于存放不同分类的书籍列表信息
//////////////////////////////////////////////////////////////////////
List<LibraryKindBookListBean> kindBooks = new ArrayList<LibraryKindBookListBean>();
// 解析男频女频分类书籍信息
// 获取所有类名为"col-xs-12"的元素,这里假设这些元素包含了男频女频相关的分类信息
//解析男频女频
Elements hotEs = contentE.getElementsByClass("col-xs-12");
for (int i = 1; i < hotEs.size(); i++) {
LibraryKindBookListBean kindItem = new LibraryKindBookListBean();
// 获取分类名称,即通过类名为"panel-title"的元素获取其文本内容作为分类名
kindItem.setKindName(hotEs.get(i).getElementsByClass("panel-title").get(0).text());
// 获取该分类下的书籍元素列表,通过先获取类名为"panel-body"的元素,再获取其内部的<li>标签元素来定位书籍信息所在元素
Elements bookEs = hotEs.get(i).getElementsByClass("panel-body").get(0).getElementsByTag("li");
List<SearchBookBean> books = new ArrayList<SearchBookBean>();
@ -131,59 +91,47 @@ public class GxwztvBookModelImpl extends MBaseModelImpl implements IGxwztvBookMo
SearchBookBean searchBookBean = new SearchBookBean();
searchBookBean.setOrigin("gxwztv.com");
searchBookBean.setTag(TAG);
// 获取书籍名称,通过获取<span>标签的文本内容作为书名
searchBookBean.setName(bookEs.get(j).getElementsByTag("span").get(0).text());
// 获取书籍详情链接同样是在TAG基础上拼接相对链接
searchBookBean.setNoteUrl(TAG + bookEs.get(j).getElementsByTag("a").get(0).attr("href"));
// 获取书籍封面链接,通过获取<img>标签的src属性值作为封面链接
searchBookBean.setCoverUrl(bookEs.get(j).getElementsByTag("img").get(0).attr("src"));
books.add(searchBookBean);
}
kindItem.setBooks(books);
kindBooks.add(kindItem);
}
// 解析部分分类推荐书籍信息
// 获取所有类名为"panel panel-info index-category-qk"的元素,这些元素包含了部分分类推荐的相关信息
//解析部分分类推荐
Elements kindEs = contentE.getElementsByClass("panel panel-info index-category-qk");
for (int i = 0; i < kindEs.size(); i++) {
LibraryKindBookListBean kindItem = new LibraryKindBookListBean();
kindItem.setKindName(kindEs.get(i).getElementsByClass("panel-title").get(0).text());
// 获取分类链接同样是在TAG基础上拼接相对链接
kindItem.setKindUrl(TAG + kindEs.get(i).getElementsByClass("listMore").get(0).getElementsByTag("a").get(0).attr("href"));
List<SearchBookBean> books = new ArrayList<SearchBookBean>();
// 获取第一个推荐书籍的元素,这里通过<dl>标签来定位
Element firstBookE = kindEs.get(i).getElementsByTag("dl").get(0);
SearchBookBean firstBook = new SearchBookBean();
firstBook.setTag(TAG);
firstBook.setOrigin("gxwztv.com");
// 获取书籍名称,通过获取<a>标签的文本内容作为书名(这里取第二个<a>标签的文本,可能是根据网页结构来确定的具体书名所在位置)
firstBook.setName(firstBookE.getElementsByTag("a").get(1).text());
// 获取书籍详情链接在TAG基础上拼接相对链接
firstBook.setNoteUrl(TAG + firstBookE.getElementsByTag("a").get(0).attr("href"));
// 获取书籍封面链接,通过获取<a>标签下的<img>标签的src属性值作为封面链接
firstBook.setCoverUrl(firstBookE.getElementsByTag("a").get(0).getElementsByTag("img").get(0).attr("src"));
firstBook.setKind(kindItem.getKindName());
books.add(firstBook);
// 获取其他推荐书籍的元素列表,通过类名为"book_textList"的元素下的<li>标签来定位
Elements otherBookEs = kindEs.get(i).getElementsByClass("book_textList").get(0).getElementsByTag("li");
for (int j = 0; j < otherBookEs.size(); j++) {
SearchBookBean item = new SearchBookBean();
item.setTag(TAG);
item.setOrigin("gxwztv.com");
item.setKind(kindItem.getKindName());
item.setNoteUrl(TAG + otherBookEs.get(j).getElementsByTag("a").get(0).attr("href"));
item.setNoteUrl(TAG+otherBookEs.get(j).getElementsByTag("a").get(0).attr("href"));
item.setName(otherBookEs.get(j).getElementsByTag("a").get(0).text());
books.add(item);
}
kindItem.setBooks(books);
kindBooks.add(kindItem);
}
//////////////
result.setKindBooks(kindBooks);
// 通过ObservableEmitter发射解析好的LibraryBean对象包含了主页上的各种书籍相关信息
e.onNext(result);
e.onComplete();
}
@ -191,58 +139,33 @@ public class GxwztvBookModelImpl extends MBaseModelImpl implements IGxwztvBookMo
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
*
*
* SearchBookBeanObservable
*
* @param content
* @param page
* @return ObservableList<SearchBookBean>
*/
@Override
public Observable<List<SearchBookBean>> searchBook(String content, int page) {
return getRetrofitObject(TAG).create(IGxwztvApi.class).searchBook(content, page)
.flatMap(new Function<String, ObservableSource<List<SearchBookBean>>>() {
@Override
public ObservableSource<List<SearchBookBean>> apply(String s) throws Exception {
return analySearchBook(s);
}
});
return getRetrofitObject(TAG).create(IGxwztvApi.class).searchBook(content, page).flatMap(new Function<String, ObservableSource<List<SearchBookBean>>>() {
@Override
public ObservableSource<List<SearchBookBean>> apply(String s) throws Exception {
return analySearchBook(s);
}
});
}
/**
*
* HTML
* SearchBookBeanObservableEmitter
*
* @param s HTML
* @return ObservableList<SearchBookBean>
*/
public Observable<List<SearchBookBean>> analySearchBook(final String s) {
return Observable.create(new ObservableOnSubscribe<List<SearchBookBean>>() {
@Override
public void subscribe(ObservableEmitter<List<SearchBookBean>> e) throws Exception {
try {
Document doc = Jsoup.parse(s);
// 获取id为"novel-list"的元素下类名为"list-group-item clearfix"的元素列表,这里假设这些元素包含了搜索到的每本书籍的相关信息
Elements booksE = doc.getElementById("novel-list").getElementsByClass("list-group-item clearfix");
if (null!= booksE && booksE.size() >= 2) {
if (null != booksE && booksE.size() >= 2) {
List<SearchBookBean> books = new ArrayList<SearchBookBean>();
for (int i = 1; i < booksE.size(); i++) {
SearchBookBean item = new SearchBookBean();
item.setTag(TAG);
// 获取书籍作者信息,通过类名为"col-xs-2"的元素获取其文本内容作为作者名
item.setAuthor(booksE.get(i).getElementsByClass("col-xs-2").get(0).text());
// 获取书籍分类信息,通过类名为"col-xs-1"的元素获取其文本内容作为分类名
item.setKind(booksE.get(i).getElementsByClass("col-xs-1").get(0).text());
// 获取书籍最后章节信息,通过类名为"col-xs-4"的元素下的<a>标签获取其文本内容作为最后章节名
item.setLastChapter(booksE.get(i).getElementsByClass("col-xs-4").get(0).getElementsByTag("a").get(0).text());
item.setOrigin("gxwztv.com");
// 获取书籍名称,通过类名为"col-xs-3"的元素下的<a>标签获取其文本内容作为书名
item.setName(booksE.get(i).getElementsByClass("col-xs-3").get(0).getElementsByTag("a").get(0).text());
// 获取书籍详情链接在TAG基础上拼接相对链接
item.setNoteUrl(TAG + booksE.get(i).getElementsByClass("col-xs-3").get(0).getElementsByTag("a").get(0).attr("href"));
item.setCoverUrl("noimage");
books.add(item);
@ -260,8 +183,171 @@ public class GxwztvBookModelImpl extends MBaseModelImpl implements IGxwztvBookMo
});
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public Observable<BookShelfBean> getBookInfo(final BookShelfBean bookShelfBean) {
return getRetrofitObject(TAG).create(IGxwztvApi.class).getBookInfo(bookShelfBean.getNoteUrl().replace(TAG, "")).flatMap(new Function<String, ObservableSource<BookShelfBean>>() {
@Override
public ObservableSource<BookShelfBean> apply(String s) throws Exception {
return analyBookInfo(s, bookShelfBean);
}
});
}
private Observable<BookShelfBean> analyBookInfo(final String s, final BookShelfBean bookShelfBean) {
return Observable.create(new ObservableOnSubscribe<BookShelfBean>() {
@Override
public void subscribe(ObservableEmitter<BookShelfBean> e) throws Exception {
bookShelfBean.setTag(TAG);
bookShelfBean.setBookInfoBean(analyBookinfo(s, bookShelfBean.getNoteUrl()));
e.onNext(bookShelfBean);
e.onComplete();
}
});
}
private BookInfoBean analyBookinfo(String s, String novelUrl) {
BookInfoBean bookInfoBean = new BookInfoBean();
bookInfoBean.setNoteUrl(novelUrl); //id
bookInfoBean.setTag(TAG);
Document doc = Jsoup.parse(s);
Element resultE = doc.getElementsByClass("panel panel-warning").get(0);
bookInfoBean.setCoverUrl(resultE.getElementsByClass("panel-body").get(0).getElementsByClass("img-thumbnail").get(0).attr("src"));
bookInfoBean.setName(resultE.getElementsByClass("active").get(0).text());
bookInfoBean.setAuthor(resultE.getElementsByClass("col-xs-12 list-group-item no-border").get(0).getElementsByTag("small").get(0).text());
Element introduceE = resultE.getElementsByClass("panel panel-default mt20").get(0);
String introduce = "";
if (introduceE.getElementById("all") != null) {
introduce = introduceE.getElementById("all").text().replace("[收起]", "");
} else {
introduce = introduceE.getElementById("shot").text();
}
bookInfoBean.setIntroduce("\u3000\u3000" + introduce);
bookInfoBean.setChapterUrl(TAG + resultE.getElementsByClass("list-group-item tac").get(0).getElementsByTag("a").get(0).attr("href"));
bookInfoBean.setOrigin("gxwztv.com");
return bookInfoBean;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public void getChapterList(final BookShelfBean bookShelfBean, final OnGetChapterListListener getChapterListListener) {
getRetrofitObject(TAG).create(IGxwztvApi.class).getChapterList(bookShelfBean.getBookInfoBean().getChapterUrl().replace(TAG, "")).flatMap(new Function<String, ObservableSource<WebChapterBean<BookShelfBean>>>() {
@Override
public ObservableSource<WebChapterBean<BookShelfBean>> apply(String s) throws Exception {
return analyChapterList(s, bookShelfBean);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SimpleObserver<WebChapterBean<BookShelfBean>>() {
@Override
public void onNext(WebChapterBean<BookShelfBean> value) {
if (getChapterListListener != null) {
getChapterListListener.success(value.getData());
}
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
if (getChapterListListener != null) {
getChapterListListener.error();
}
}
});
}
private Observable<WebChapterBean<BookShelfBean>> analyChapterList(final String s, final BookShelfBean bookShelfBean) {
return Observable.create(new ObservableOnSubscribe<WebChapterBean<BookShelfBean>>() {
@Override
public void subscribe(ObservableEmitter<WebChapterBean<BookShelfBean>> e) throws Exception {
bookShelfBean.setTag(TAG);
WebChapterBean<List<ChapterListBean>> temp = analyChapterlist(s, bookShelfBean.getNoteUrl());
bookShelfBean.getBookInfoBean().setChapterlist(temp.getData());
e.onNext(new WebChapterBean<BookShelfBean>(bookShelfBean, temp.getNext()));
e.onComplete();
}
});
}
private WebChapterBean<List<ChapterListBean>> analyChapterlist(String s, String novelUrl) {
Document doc = Jsoup.parse(s);
Elements chapterlist = doc.getElementById("chapters-list").getElementsByTag("a");
List<ChapterListBean> chapterBeans = new ArrayList<ChapterListBean>();
for (int i = 0; i < chapterlist.size(); i++) {
ChapterListBean temp = new ChapterListBean();
temp.setDurChapterUrl(TAG + chapterlist.get(i).attr("href")); //id
temp.setDurChapterIndex(i);
temp.setDurChapterName(chapterlist.get(i).text());
temp.setNoteUrl(novelUrl);
temp.setTag(TAG);
chapterBeans.add(temp);
}
Boolean next = false;
return new WebChapterBean<List<ChapterListBean>>(chapterBeans, next);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public Observable<BookContentBean> getBookContent(final String durChapterUrl, final int durChapterIndex) {
return getRetrofitObject(TAG).create(IGxwztvApi.class).getBookContent(durChapterUrl.replace(TAG, "")).flatMap(new Function<String, ObservableSource<BookContentBean>>() {
@Override
public ObservableSource<BookContentBean> apply(String s) throws Exception {
return analyBookContent(s, durChapterUrl, durChapterIndex);
}
});
}
private Observable<BookContentBean> analyBookContent(final String s, final String durChapterUrl, final int durChapterIndex) {
return Observable.create(new ObservableOnSubscribe<BookContentBean>() {
@Override
public void subscribe(ObservableEmitter<BookContentBean> e) throws Exception {
BookContentBean bookContentBean = new BookContentBean();
bookContentBean.setDurChapterIndex(durChapterIndex);
bookContentBean.setDurChapterUrl(durChapterUrl);
bookContentBean.setTag(TAG);
try {
Document doc = Jsoup.parse(s);
List<TextNode> contentEs = doc.getElementById("txtContent").textNodes();
StringBuilder content = new StringBuilder();
for (int i = 0; i < contentEs.size(); i++) {
String temp = contentEs.get(i).text().trim();
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
if (temp.length() > 0) {
content.append("\u3000\u3000" + temp);
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
bookContentBean.setDurCapterContent(content.toString());
bookContentBean.setRight(true);
} catch (Exception ex) {
ex.printStackTrace();
ErrorAnalyContentManager.getInstance().writeNewErrorUrl(durChapterUrl);
bookContentBean.setDurCapterContent(durChapterUrl.substring(0, durChapterUrl.indexOf('/', 8)) + "站点暂时不支持解析请反馈给Monke QQ:1105075896,半小时内解决,超级效率的程序员");
bookContentBean.setRight(false);
}
e.onNext(bookContentBean);
e.onComplete();
}
});
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
*
* BookShelfBean
/**
*
*/
@Override
public Observable<List<SearchBookBean>> getKindBook(String url, int page) {
url = url + page + ".htm";
return getRetrofitObject(GxwztvBookModelImpl.TAG).create(IGxwztvApi.class).getKindBooks(url.replace(GxwztvBookModelImpl.TAG, "")).flatMap(new Function<String, ObservableSource<List<SearchBookBean>>>() {
@Override
public ObservableSource<List<SearchBookBean>> apply(String s) throws Exception {
return analySearchBook(s);
}
});
}
}

@ -25,68 +25,36 @@ import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
/**
* ImportBookModelImplMBaseModelImplIImportBookModel
* MD5
* RxJavaObservable便
*/
public class ImportBookModelImpl extends MBaseModelImpl implements IImportBookModel {
/**
* ImportBookModelImpl
* 便
*
*
* @return ImportBookModelImpl
*/
public static ImportBookModelImpl getInstance() {
return new ImportBookModelImpl();
}
/**
*
* MD5
* LocBookShelfBeanObservable
*
* @param book File
* @return ObservableLocBookShelfBean
*/
@Override
public Observable<LocBookShelfBean> importBook(final File book) {
return Observable.create(new ObservableOnSubscribe<LocBookShelfBean>() {
@Override
public void subscribe(ObservableEmitter<LocBookShelfBean> e) throws Exception {
// 创建MessageDigest实例用于计算文件的MD5值MD5值可作为书籍文件的唯一标识来判断是否重复导入等情况
MessageDigest md = MessageDigest.getInstance("MD5");
// 创建文件输入流,用于读取书籍文件内容
FileInputStream in = new FileInputStream(book);
byte[] buffer = new byte[2048];
int len;
// 循环读取文件内容每次读取指定长度的数据并将其更新到MessageDigest中用于计算MD5值
while ((len = in.read(buffer, 0, 2048))!= -1) {
while ((len = in.read(buffer, 0, 2048)) != -1) {
md.update(buffer, 0, len);
}
// 关闭文件输入流,释放资源
in.close();
in = null;
// 将计算得到的MD5值转换为十六进制字符串形式方便后续作为标识使用
String md5 = new BigInteger(1, md.digest()).toString(16);
BookShelfBean bookShelfBean = null;
// 从数据库中查询是否已存在具有相同MD5值即相同书籍文件的记录通过构建查询条件并执行查询获取结果列表
List<BookShelfBean> temp = DbHelper.getInstance().getmDaoSession().getBookShelfBeanDao().queryBuilder()
.where(BookShelfBeanDao.Properties.NoteUrl.eq(md5)).build().list();
// 标记书籍是否为新导入的初始化为true表示默认是新书
List<BookShelfBean> temp = DbHelper.getInstance().getmDaoSession().getBookShelfBeanDao().queryBuilder().where(BookShelfBeanDao.Properties.NoteUrl.eq(md5)).build().list();
Boolean isNew = true;
// 如果查询到的结果列表不为空且包含至少一个元素,说明书籍已存在,不是新书
if (temp!= null && temp.size() > 0) {
if (temp!=null && temp.size()>0) {
isNew = false;
bookShelfBean = temp.get(0);
// 从数据库中获取对应的书籍详情信息并设置到bookShelfBean对象中确保书籍相关信息的完整性
bookShelfBean.setBookInfoBean(DbHelper.getInstance().getmDaoSession().getBookInfoBeanDao().queryBuilder()
.where(BookInfoBeanDao.Properties.NoteUrl.eq(bookShelfBean.getNoteUrl())).build().list().get(0));
bookShelfBean.setBookInfoBean(DbHelper.getInstance().getmDaoSession().getBookInfoBeanDao().queryBuilder().where(BookInfoBeanDao.Properties.NoteUrl.eq(bookShelfBean.getNoteUrl())).build().list().get(0));
} else {
// 如果是新书则创建一个新的BookShelfBean对象用于存储书籍的基本信息
bookShelfBean = new BookShelfBean();
bookShelfBean.setFinalDate(System.currentTimeMillis());
bookShelfBean.setDurChapter(0);
@ -94,7 +62,6 @@ public class ImportBookModelImpl extends MBaseModelImpl implements IImportBookMo
bookShelfBean.setTag(BookShelfBean.LOCAL_TAG);
bookShelfBean.setNoteUrl(md5);
// 设置书籍详情信息中的作者、书名等基本信息,书名通过去除文件扩展名来获取,作者暂设为“佚名”
bookShelfBean.getBookInfoBean().setAuthor("佚名");
bookShelfBean.getBookInfoBean().setName(book.getName().replace(".txt", "").replace(".TXT", ""));
bookShelfBean.getBookInfoBean().setFinalRefreshData(System.currentTimeMillis());
@ -102,31 +69,17 @@ public class ImportBookModelImpl extends MBaseModelImpl implements IImportBookMo
bookShelfBean.getBookInfoBean().setNoteUrl(md5);
bookShelfBean.getBookInfoBean().setTag(BookShelfBean.LOCAL_TAG);
// 调用方法保存书籍的章节信息到数据库中
saveChapter(book, md5);
// 将书籍详情信息插入或替换到数据库中(如果已存在相同标识的记录则替换)
DbHelper.getInstance().getmDaoSession().getBookInfoBeanDao().insertOrReplace(bookShelfBean.getBookInfoBean());
// 将书籍基本信息插入或替换到数据库中
DbHelper.getInstance().getmDaoSession().getBookShelfBeanDao().insertOrReplace(bookShelfBean);
}
// 从数据库中获取该书籍的章节列表信息并设置到bookShelfBean对象的对应属性中确保书籍章节信息的完整性
bookShelfBean.getBookInfoBean().setChapterlist(DbHelper.getInstance().getmDaoSession().getChapterListBeanDao().queryBuilder()
.where(ChapterListBeanDao.Properties.NoteUrl.eq(bookShelfBean.getNoteUrl())).orderAsc(ChapterListBeanDao.Properties.DurChapterIndex).build().list());
// 通过ObservableEmitter发射包含是否为新书以及完整书籍信息的LocBookShelfBean对象供订阅者接收处理
e.onNext(new LocBookShelfBean(isNew, bookShelfBean));
bookShelfBean.getBookInfoBean().setChapterlist(DbHelper.getInstance().getmDaoSession().getChapterListBeanDao().queryBuilder().where(ChapterListBeanDao.Properties.NoteUrl.eq(bookShelfBean.getNoteUrl())).orderAsc(ChapterListBeanDao.Properties.DurChapterIndex).build().list());
e.onNext(new LocBookShelfBean(isNew,bookShelfBean));
e.onComplete();
}
});
}
/**
* BookShelfBeanshelfs
* NoteUrl
*
* @param temp BookShelfBean
* @param shelfs List<BookShelfBean>
* @return truefalse
*/
private Boolean isAdded(BookShelfBean temp, List<BookShelfBean> shelfs) {
if (shelfs == null || shelfs.size() == 0) {
return false;
@ -146,68 +99,47 @@ public class ImportBookModelImpl extends MBaseModelImpl implements IImportBookMo
}
}
/**
*
*
* ChapterListBean
*
* @param book File
* @param md5 MD5
* @throws IOException I/O
*/
private void saveChapter(File book, String md5) throws IOException {
// 定义一个正则表达式用于匹配章节标题的模式这里假设章节标题一般以“第X章”开头X表示数字及一些其他可能的字符长度限制在1到7之间后面可跟一些其他内容
String regex = "第.{1,7}章.{0,}";
String encoding;
// 创建文件输入流,用于读取书籍文件内容,为后续检测编码格式和解析内容做准备
FileInputStream fis = new FileInputStream(book);
byte[] buf = new byte[4096];
// 创建UniversalDetector实例用于检测文件的编码格式它可以自动识别多种常见的编码类型
UniversalDetector detector = new UniversalDetector(null);
int nread;
// 循环读取文件内容每次读取一部分数据交给UniversalDetector进行分析直到检测完成或读取完整个文件
while ((nread = fis.read(buf)) > 0 &&!detector.isDone()) {
while ((nread = fis.read(buf)) > 0 && !detector.isDone()) {
detector.handleData(buf, 0, nread);
}
detector.dataEnd();
// 获取检测到的文件编码格式,如果未检测到则默认为"utf-8"编码
encoding = detector.getDetectedCharset();
if (encoding == null || encoding.length() == 0)
encoding = "utf-8";
fis.close();
fis = null;
// 章节页码索引用于记录章节的顺序初始化为0表示从第一章开始
int chapterPageIndex = 0;
String title = null;
StringBuilder contentBuilder = new StringBuilder();
fis = new FileInputStream(book);
// 根据检测到的编码格式创建InputStreamReader用于以正确的编码方式读取文件内容
InputStreamReader inputreader = new InputStreamReader(fis, encoding);
// 创建BufferedReader方便逐行读取文件内容进行解析
BufferedReader buffreader = new BufferedReader(inputreader);
String line;
// 逐行读取书籍文件内容,进行章节信息的解析和提取
while ((line = buffreader.readLine())!= null) {
while ((line = buffreader.readLine()) != null) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(line);
if (m.find()) {
// 如果匹配到章节标题先提取标题前可能存在的内容如果有并添加到章节内容的临时存储字符串中contentBuilder
String temp = line.trim().substring(0, line.trim().indexOf("第"));
if (temp!= null && temp.trim().length() > 0) {
String temp = line.trim().substring(0,line.trim().indexOf("第"));
if(temp!= null && temp.trim().length()>0){
contentBuilder.append(temp);
}
// 如果临时存储的章节内容字符串不为空且去除全角空格等空白字符后仍有内容,则将当前章节内容保存到数据库中,并更新相关索引和清空临时存储内容
if (contentBuilder.toString().length() > 0) {
if (contentBuilder.toString().replaceAll(" ", "").trim().length() > 0) {
if(contentBuilder.toString().replaceAll(" ","").trim().length()>0){
saveDurChapterContent(md5, chapterPageIndex, title, contentBuilder.toString());
chapterPageIndex++;
}
contentBuilder.delete(0, contentBuilder.length());
}
// 将匹配到的章节标题提取出来,作为当前章节的标题
title = line.trim().substring(line.trim().indexOf("第"));
} else {
if (line.trim().length() == 0) {
@ -224,7 +156,6 @@ public class ImportBookModelImpl extends MBaseModelImpl implements IImportBookMo
}
}
}
// 处理文件末尾剩余的章节内容(如果有),同样将其保存到数据库中
if (contentBuilder.length() > 0) {
saveDurChapterContent(md5, chapterPageIndex, title, contentBuilder.toString());
contentBuilder.delete(0, contentBuilder.length());
@ -236,16 +167,6 @@ public class ImportBookModelImpl extends MBaseModelImpl implements IImportBookMo
fis = null;
}
/**
*
* ChapterListBean
*
*
* @param md5 MD5
* @param chapterPageIndex
* @param name
* @param content
*/
private void saveDurChapterContent(String md5, int chapterPageIndex, String name, String content) {
ChapterListBean chapterListBean = new ChapterListBean();
chapterListBean.setNoteUrl(md5);
@ -261,4 +182,4 @@ public class ImportBookModelImpl extends MBaseModelImpl implements IImportBookMo
DbHelper.getInstance().getmDaoSession().getBookContentBeanDao().insertOrReplace(chapterListBean.getBookContentBean());
DbHelper.getInstance().getmDaoSession().getChapterListBeanDao().insertOrReplace(chapterListBean);
}
}
}

@ -28,84 +28,42 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
/**
* LingdiankanshuStationBookModelImplMBaseModelImplIStationBookModel
* TAGhttp://www.lingdiankanshu.co进行交互实现如搜索书籍、获取书籍详情、章节列表以及章节内容等功能。
* HTMLRxJava
*/
public class LingdiankanshuStationBookModelImpl extends MBaseModelImpl implements IStationBookModel {
// 定义一个静态常量TAG用于标识该模型所对应的网站地址此处明确为http://www.lingdiankanshu.co
// 便于在整个程序体系里知晓此模型相关的网络操作、数据解析等逻辑是针对该站点而设计的。
public static final String TAG = "http://www.lingdiankanshu.co";
/**
* LingdiankanshuStationBookModelImpl
*
*
*
* @return LingdiankanshuStationBookModelImpl
*/
public static LingdiankanshuStationBookModelImpl getInstance() {
return new LingdiankanshuStationBookModelImpl();
}
/**
*
* http://zhannei.baidu.com发起搜索书籍的网络请求调用对应的API
* SearchBookBeanObservable
*
*
* @param content
* @param page 1
* @return ObservableList<SearchBookBean>
*/
@Override
public Observable<List<SearchBookBean>> searchBook(String content, int page) {
return getRetrofitObject("http://zhannei.baidu.com").create(ILingdiankanshuApi.class).searchBook(content, page - 1, "16865089933227718744")
.flatMap(new Function<String, ObservableSource<List<SearchBookBean>>>() {
@Override
public ObservableSource<List<SearchBookBean>> apply(String s) throws Exception {
return analySearchBook(s);
}
});
return getRetrofitObject("http://zhannei.baidu.com").create(ILingdiankanshuApi.class).searchBook(content, page - 1, "16865089933227718744").flatMap(new Function<String, ObservableSource<List<SearchBookBean>>>() {
@Override
public ObservableSource<List<SearchBookBean>> apply(String s) throws Exception {
return analySearchBook(s);
}
});
}
/**
*
* HTML
* SearchBookBeanObservableEmitter
*
* @param s HTML
* @return ObservableList<SearchBookBean>
*/
public Observable<List<SearchBookBean>> analySearchBook(final String s) {
return Observable.create(new ObservableOnSubscribe<List<SearchBookBean>>() {
@Override
public void subscribe(ObservableEmitter<List<SearchBookBean>> e) throws Exception {
try {
Document doc = Jsoup.parse(s);
// 获取类名为"result-list"的元素下,类名为"result-item result-game-item"的元素列表,这里假设这些元素包含了每本搜索到的书籍的相关信息
Elements booksE = doc.getElementsByClass("result-list").get(0).getElementsByClass("result-item result-game-item");
if (null!= booksE && booksE.size() > 1) {
if (null != booksE && booksE.size() > 1) {
List<SearchBookBean> books = new ArrayList<SearchBookBean>();
for (int i = 0; i < booksE.size(); i++) {
SearchBookBean item = new SearchBookBean();
item.setTag(TAG);
// 获取书籍作者信息,通过层层定位元素,先找到类名为"result-game-item-info"的元素,再找到其内部类名为"result-game-item-info-tag"的元素,
// 然后获取第二个<span>标签的文本内容作为作者名(这里的元素定位方式是根据网页结构来确定的具体作者名所在位置)
item.setAuthor(booksE.get(i).getElementsByClass("result-game-item-info").get(0).getElementsByClass("result-game-item-info-tag").get(0).getElementsByTag("span").get(1).text());
// 获取书籍分类信息,同样通过层层定位元素,获取对应位置的<span>标签文本内容作为分类名
item.setKind(booksE.get(i).getElementsByClass("result-game-item-info").get(0).getElementsByClass("result-game-item-info-tag").get(1).getElementsByTag("span").get(1).text());
// item.setState(); // 此处代码似乎未完成对书籍状态的设置相关逻辑,可能需要后续补充
// 获取书籍最后章节信息,通过对应元素下的<a>标签获取其文本内容作为最后章节名
// item.setState();
item.setLastChapter(booksE.get(i).getElementsByClass("result-game-item-info").get(0).getElementsByClass("result-game-item-info-tag").get(3).getElementsByTag("a").get(0).text());
item.setOrigin("lingdiankanshu.co");
// 获取书籍名称,通过类名为"result-item-title result-game-item-title"的元素下的<a>标签获取其文本内容作为书名
item.setName(booksE.get(i).getElementsByClass("result-item-title result-game-item-title").get(0).getElementsByTag("a").get(0).text());
// 获取书籍详情链接,通过对应的<a>标签获取其href属性值作为详情链接
item.setNoteUrl(booksE.get(i).getElementsByClass("result-item-title result-game-item-title").get(0).getElementsByTag("a").get(0).attr("href"));
// 获取书籍封面链接,通过<img>标签的src属性值作为封面链接
item.setCoverUrl(booksE.get(i).getElementsByTag("img").get(0).attr("src"));
books.add(item);
}
@ -123,35 +81,16 @@ public class LingdiankanshuStationBookModelImpl extends MBaseModelImpl implement
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
*
* BookShelfBean
* BookShelfBeanObservable
*
* @param bookShelfBean BookShelfBean
* @return ObservableBookShelfBean
*/
@Override
public Observable<BookShelfBean> getBookInfo(final BookShelfBean bookShelfBean) {
return getRetrofitObject(TAG).create(ILingdiankanshuApi.class).getBookInfo(bookShelfBean.getNoteUrl().replace(TAG, ""))
.flatMap(new Function<String, ObservableSource<BookShelfBean>>() {
@Override
public ObservableSource<BookShelfBean> apply(String s) throws Exception {
return analyBookInfo(s, bookShelfBean);
}
});
return getRetrofitObject(TAG).create(ILingdiankanshuApi.class).getBookInfo(bookShelfBean.getNoteUrl().replace(TAG, "")).flatMap(new Function<String, ObservableSource<BookShelfBean>>() {
@Override
public ObservableSource<BookShelfBean> apply(String s) throws Exception {
return analyBookInfo(s, bookShelfBean);
}
});
}
/**
*
* HTML
* BookShelfBeananalyBookinfoObservableEmitter
*
* @param s HTML
* @param bookShelfBean BookShelfBean
* @return ObservableBookShelfBean
*/
private Observable<BookShelfBean> analyBookInfo(final String s, final BookShelfBean bookShelfBean) {
return Observable.create(new ObservableOnSubscribe<BookShelfBean>() {
@Override
@ -164,34 +103,23 @@ public class LingdiankanshuStationBookModelImpl extends MBaseModelImpl implement
});
}
/**
* HTMLBookInfoBean
*
* @param s HTML
* @param novelUrl BookInfoBean
* @return BookInfoBean
*/
private BookInfoBean analyBookinfo(String s, String novelUrl) {
BookInfoBean bookInfoBean = new BookInfoBean();
bookInfoBean.setNoteUrl(novelUrl); //id
bookInfoBean.setTag(TAG);
Document doc = Jsoup.parse(s);
// 获取类名为"box_con"的元素,这里假设书籍详情的主要信息都在这个元素内部,是后续提取各种信息的基础元素
Element resultE = doc.getElementsByClass("box_con").get(0);
// 获取书籍封面链接,通过定位元素找到对应的<img>标签并获取其src属性值作为封面链接
bookInfoBean.setCoverUrl(resultE.getElementById("fmimg").getElementsByTag("img").get(0).attr("src"));
// 获取书籍名称,通过定位元素找到对应的<h1>标签,并获取其文本内容作为书名
bookInfoBean.setName(resultE.getElementById("info").getElementsByTag("h1").get(0).text());
String author = resultE.getElementById("info").getElementsByTag("p").get(0).text().toString().trim();
// 对获取到的作者信息字符串进行处理,去除一些多余的空格、特殊空格以及"作者:"等前缀内容,得到纯净的作者名
author = author.replace(" ", "").replace(" ", "").replace("作者:", "");
author = author.replace(" ", "").replace("  ", "").replace("作者:", "");
bookInfoBean.setAuthor(author);
List<TextNode> contentEs = resultE.getElementById("intro").textNodes();
StringBuilder content = new StringBuilder();
for (int i = 0; i < contentEs.size(); i++) {
String temp = contentEs.get(i).text().trim();
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
if (temp.length() > 0) {
content.append("\u3000\u3000" + temp);
if (i < contentEs.size() - 1) {
@ -207,31 +135,20 @@ public class LingdiankanshuStationBookModelImpl extends MBaseModelImpl implement
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
*
* BookShelfBean
* BookShelfBean
* RxJava线IO线线OnGetChapterListListener
*
* @param bookShelfBean BookShelfBean
* @param getChapterListListener successerror
*/
@Override
public void getChapterList(final BookShelfBean bookShelfBean, final OnGetChapterListListener getChapterListListener) {
getRetrofitObject(TAG).create(ILingdiankanshuApi.class).getChapterList(bookShelfBean.getBookInfoBean().getChapterUrl().replace(TAG, ""))
.flatMap(new Function<String, ObservableSource<WebChapterBean<BookShelfBean>>>() {
@Override
public ObservableSource<WebChapterBean<BookShelfBean>> apply(String s) throws Exception {
return analyChapterList(s, bookShelfBean);
}
})
getRetrofitObject(TAG).create(ILingdiankanshuApi.class).getChapterList(bookShelfBean.getBookInfoBean().getChapterUrl().replace(TAG, "")).flatMap(new Function<String, ObservableSource<WebChapterBean<BookShelfBean>>>() {
@Override
public ObservableSource<WebChapterBean<BookShelfBean>> apply(String s) throws Exception {
return analyChapterList(s, bookShelfBean);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SimpleObserver<WebChapterBean<BookShelfBean>>() {
@Override
public void onNext(WebChapterBean<BookShelfBean> value) {
if (getChapterListListener!= null) {
if (getChapterListListener != null) {
getChapterListListener.success(value.getData());
}
}
@ -239,17 +156,90 @@ public class LingdiankanshuStationBookModelImpl extends MBaseModelImpl implement
@Override
public void onError(Throwable e) {
e.printStackTrace();
if (getChapterListListener!= null) {
if (getChapterListListener != null) {
getChapterListListener.error();
}
}
});
}
/**
*
* HTML
* ChapterListBeanBookShelfBeanObservableEmitter
*
* @param s HTML
* @param bookShelfBean
private Observable<WebChapterBean<BookShelfBean>> analyChapterList(final String s, final BookShelfBean bookShelfBean) {
return Observable.create(new ObservableOnSubscribe<WebChapterBean<BookShelfBean>>() {
@Override
public void subscribe(ObservableEmitter<WebChapterBean<BookShelfBean>> e) throws Exception {
bookShelfBean.setTag(TAG);
WebChapterBean<List<ChapterListBean>> temp = analyChapterlist(s, bookShelfBean.getNoteUrl());
bookShelfBean.getBookInfoBean().setChapterlist(temp.getData());
e.onNext(new WebChapterBean<BookShelfBean>(bookShelfBean, temp.getNext()));
e.onComplete();
}
});
}
private WebChapterBean<List<ChapterListBean>> analyChapterlist(String s, String novelUrl) {
Document doc = Jsoup.parse(s);
Elements chapterlist = doc.getElementById("list").getElementsByTag("dd");
List<ChapterListBean> chapterBeans = new ArrayList<ChapterListBean>();
for (int i = 0; i < chapterlist.size(); i++) {
ChapterListBean temp = new ChapterListBean();
temp.setDurChapterUrl(novelUrl + chapterlist.get(i).getElementsByTag("a").get(0).attr("href")); //id
temp.setDurChapterIndex(i);
temp.setDurChapterName(chapterlist.get(i).getElementsByTag("a").get(0).text());
temp.setNoteUrl(novelUrl);
temp.setTag(TAG);
chapterBeans.add(temp);
}
Boolean next = false;
return new WebChapterBean<List<ChapterListBean>>(chapterBeans, next);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public Observable<BookContentBean> getBookContent(final String durChapterUrl, final int durChapterIndex) {
return getRetrofitObject(TAG).create(ILingdiankanshuApi.class).getBookContent(durChapterUrl.replace(TAG, "")).flatMap(new Function<String, ObservableSource<BookContentBean>>() {
@Override
public ObservableSource<BookContentBean> apply(String s) throws Exception {
return analyBookContent(s, durChapterUrl, durChapterIndex);
}
});
}
private Observable<BookContentBean> analyBookContent(final String s, final String durChapterUrl, final int durChapterIndex) {
return Observable.create(new ObservableOnSubscribe<BookContentBean>() {
@Override
public void subscribe(ObservableEmitter<BookContentBean> e) throws Exception {
BookContentBean bookContentBean = new BookContentBean();
bookContentBean.setDurChapterIndex(durChapterIndex);
bookContentBean.setDurChapterUrl(durChapterUrl);
bookContentBean.setTag(TAG);
try {
Document doc = Jsoup.parse(s);
List<TextNode> contentEs = doc.getElementById("content").textNodes();
StringBuilder content = new StringBuilder();
for (int i = 0; i < contentEs.size(); i++) {
String temp = contentEs.get(i).text().trim();
temp = temp.replaceAll(" ", "").replaceAll(" ", "");
if (temp.length() > 0) {
content.append("\u3000\u3000" + temp);
if (i < contentEs.size() - 1) {
content.append("\r\n");
}
}
}
bookContentBean.setDurCapterContent(content.toString());
bookContentBean.setRight(true);
} catch (Exception ex) {
ex.printStackTrace();
ErrorAnalyContentManager.getInstance().writeNewErrorUrl(durChapterUrl);
bookContentBean.setDurCapterContent(durChapterUrl.substring(0, durChapterUrl.indexOf('/', 8)) + "站点暂时不支持解析请反馈给Monke QQ:1105075896,半小时内解决,超级效率的程序员");
bookContentBean.setRight(false);
}
e.onNext(bookContentBean);
e.onComplete();
}
});
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
}

@ -12,20 +12,8 @@ import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
/**
* WebBookModelImplIWebBookModel
* tag
* 便
*/
public class WebBookModelImpl implements IWebBookModel {
/**
* WebBookModelImpl
* 便
*
*
* @return WebBookModelImpl
*/
public static WebBookModelImpl getInstance() {
return new WebBookModelImpl();
}
@ -34,22 +22,17 @@ public class WebBookModelImpl implements IWebBookModel {
/**
*
* BookShelfBeantag
* GxwztvBookModelImpl.TAG
* LingdiankanshuStationBookModelImpl.TAG
* null
* BookShelfBeanObservable
*
* @param bookShelfBean BookShelfBeantag
* @return ObservableBookShelfBeannull
* return BookShelfBean
*/
@Override
public Observable<BookShelfBean> getBookInfo(BookShelfBean bookShelfBean) {
if (bookShelfBean.getTag().equals(GxwztvBookModelImpl.TAG)) {
if(bookShelfBean.getTag().equals(GxwztvBookModelImpl.TAG)){
return GxwztvBookModelImpl.getInstance().getBookInfo(bookShelfBean);
} else if (bookShelfBean.getTag().equals(LingdiankanshuStationBookModelImpl.TAG)) {
}
else if(bookShelfBean.getTag().equals(LingdiankanshuStationBookModelImpl.TAG)){
return LingdiankanshuStationBookModelImpl.getInstance().getBookInfo(bookShelfBean);
} else {
}
else {
return null;
}
}
@ -58,23 +41,18 @@ public class WebBookModelImpl implements IWebBookModel {
/**
*
* BookShelfBeantag
* GxwztvBookModelImpl.TAG
* LingdiankanshuStationBookModelImpl.TAG
* OnGetChapterListListenerBookShelfBean
*
*
* @param bookShelfBean BookShelfBeantag
* @param getChapterListListener successerror
* return BookShelfBean
*/
@Override
public void getChapterList(final BookShelfBean bookShelfBean, OnGetChapterListListener getChapterListListener) {
if (bookShelfBean.getTag().equals(GxwztvBookModelImpl.TAG)) {
if(bookShelfBean.getTag().equals(GxwztvBookModelImpl.TAG)){
GxwztvBookModelImpl.getInstance().getChapterList(bookShelfBean, getChapterListListener);
} else if (bookShelfBean.getTag().equals(LingdiankanshuStationBookModelImpl.TAG)) {
}
else if(bookShelfBean.getTag().equals(LingdiankanshuStationBookModelImpl.TAG)){
LingdiankanshuStationBookModelImpl.getInstance().getChapterList(bookShelfBean, getChapterListListener);
} else {
if (getChapterListListener!= null)
}
else{
if(getChapterListListener!=null)
getChapterListListener.success(bookShelfBean);
}
}
@ -83,24 +61,16 @@ public class WebBookModelImpl implements IWebBookModel {
/**
*
* durChapterUrltag
* GxwztvBookModelImpl.TAG
* LingdiankanshuStationBookModelImpl.TAG
* ObservableBookContentBean
* BookContentBeanObservable
*
* @param durChapterUrl 便
* @param durChapterIndex
* @param tag
* @return ObservableBookContentBeanObservableBookContentBean
*/
@Override
public Observable<BookContentBean> getBookContent(String durChapterUrl, int durChapterIndex, String tag) {
if (tag.equals(GxwztvBookModelImpl.TAG)) {
if(tag.equals(GxwztvBookModelImpl.TAG)){
return GxwztvBookModelImpl.getInstance().getBookContent(durChapterUrl, durChapterIndex);
} else if (tag.equals(LingdiankanshuStationBookModelImpl.TAG)) {
}
else if(tag.equals(LingdiankanshuStationBookModelImpl.TAG)){
return LingdiankanshuStationBookModelImpl.getInstance().getBookContent(durChapterUrl, durChapterIndex);
} else
}
else
return Observable.create(new ObservableOnSubscribe<BookContentBean>() {
@Override
public void subscribe(ObservableEmitter<BookContentBean> e) throws Exception {
@ -112,24 +82,16 @@ public class WebBookModelImpl implements IWebBookModel {
/**
*
* tag
* GxwztvBookModelImpl.TAG
* LingdiankanshuStationBookModelImpl.TAG
* ObservableSearchBookBean
* SearchBookBeanObservable
*
* @param content
* @param page
* @param tag
* @return ObservableList<SearchBookBean>Observable
*/
@Override
public Observable<List<SearchBookBean>> searchOtherBook(String content, int page, String tag) {
if (tag.equals(GxwztvBookModelImpl.TAG)) {
public Observable<List<SearchBookBean>> searchOtherBook(String content,int page,String tag){
if(tag.equals(GxwztvBookModelImpl.TAG)){
return GxwztvBookModelImpl.getInstance().searchBook(content, page);
} else if (tag.equals(LingdiankanshuStationBookModelImpl.TAG)) {
}
else if(tag.equals(LingdiankanshuStationBookModelImpl.TAG)){
return LingdiankanshuStationBookModelImpl.getInstance().searchBook(content, page);
} else {
}
else{
return Observable.create(new ObservableOnSubscribe<List<SearchBookBean>>() {
@Override
public void subscribe(ObservableEmitter<List<SearchBookBean>> e) throws Exception {
@ -139,19 +101,11 @@ public class WebBookModelImpl implements IWebBookModel {
});
}
}
/**
*
* GxwztvBookModelImpl
* GxwztvBookModelImplSearchBookBeanObservable
*
*
* @param url GxwztvBookModelImpl
* @param page
* @return ObservableList<SearchBookBean>
*/
@Override
public Observable<List<SearchBookBean>> getKindBook(String url, int page) {
return GxwztvBookModelImpl.getInstance().getKindBook(url, page);
public Observable<List<SearchBookBean>> getKindBook(String url,int page) {
return GxwztvBookModelImpl.getInstance().getKindBook(url,page);
}
}
}

@ -1,3 +1,4 @@
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.service;
import android.app.NotificationManager;
@ -11,7 +12,6 @@ import android.os.Looper;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.widget.Toast;
import com.hwangjr.rxbus.RxBus;
import com.hwangjr.rxbus.annotation.Subscribe;
import com.hwangjr.rxbus.annotation.Tag;
@ -30,9 +30,7 @@ import com.monke.monkeybook.dao.DbHelper;
import com.monke.monkeybook.dao.DownloadChapterBeanDao;
import com.monke.monkeybook.model.impl.WebBookModelImpl;
import com.monke.monkeybook.view.impl.MainActivity;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
@ -43,24 +41,18 @@ import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
// DownloadService类继承自Service用于处理书籍下载相关的业务逻辑如启动、暂停、取消下载等操作
public class DownloadService extends Service {
// 用于管理通知的显示和取消等操作
private NotificationManager notifyManager;
// 通知的唯一标识符
private int notifiId = 19931118;
// 标记是否开始下载
private Boolean isStartDownload = false;
// 标记是否已经初始化
private Boolean isInit = false;
// 服务创建时调用的方法
@Override
public void onCreate() {
super.onCreate();
}
// 服务销毁时调用的方法用于注销RxBus注册以及设置初始化标记为true
@Override
public void onDestroy() {
super.onDestroy();
@ -68,31 +60,25 @@ public class DownloadService extends Service {
isInit = true;
}
// 服务启动时调用的方法进行一些初始化操作如获取通知管理器、注册RxBus等
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!isInit) {
isInit = true;
// 获取系统的通知服务
notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 注册RxBus用于接收相关事件
RxBus.get().register(this);
}
return super.onStartCommand(intent, flags, startId);
}
// 用于绑定服务这里返回null表示不支持绑定
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
// 添加新的下载任务,将新的下载章节数据插入数据库,并根据下载状态决定是否开始下载
private void addNewTask(final List<DownloadChapterBean> newData) {
isStartDownload = true;
Observable.create(new ObservableOnSubscribe<Boolean>() {
// 在这个方法中执行将下载章节数据插入数据库的操作
@Override
public void subscribe(ObservableEmitter<Boolean> e) throws Exception {
DbHelper.getInstance().getmDaoSession().getDownloadChapterBeanDao().insertOrReplaceInTx(newData);
@ -100,12 +86,9 @@ public class DownloadService extends Service {
e.onComplete();
}
})
// 指定在主线程观察结果
.observeOn(AndroidSchedulers.mainThread())
// 指定在IO线程执行插入数据库操作
.subscribeOn(Schedulers.io())
.subscribe(new SimpleObserver<Boolean>() {
// 插入成功后如果当前没有正在下载则调用toDownload方法开始下载
@Override
public void onNext(Boolean value) {
if (!isDownloading) {
@ -120,38 +103,30 @@ public class DownloadService extends Service {
});
}
// 标记是否正在下载
private Boolean isDownloading = false;
// 下载失败时的重试次数
public static final int reTryTimes = 1;
// 开始下载的主要逻辑方法,查找待下载的章节并进行下载操作
private void toDownload() {
isDownloading = true;
if (isStartDownload) {
Observable.create(new ObservableOnSubscribe<DownloadChapterBean>() {
@Override
public void subscribe(ObservableEmitter<DownloadChapterBean> e) throws Exception {
// 获取书架上的书籍列表,按最后更新日期降序排列
List<BookShelfBean> bookShelfBeanList = DbHelper.getInstance().getmDaoSession().getBookShelfBeanDao().queryBuilder().orderDesc(BookShelfBeanDao.Properties.FinalDate).list();
if (bookShelfBeanList!= null && bookShelfBeanList.size() > 0) {
if (bookShelfBeanList != null && bookShelfBeanList.size() > 0) {
for (BookShelfBean bookItem : bookShelfBeanList) {
// 排除本地标签的书籍(可能表示已经下载好的本地书籍)
if (!bookItem.getTag().equals(BookShelfBean.LOCAL_TAG)) {
// 查询该书籍下待下载的章节列表,按章节索引升序排列,取第一个章节(即下一个要下载的章节)
List<DownloadChapterBean> downloadChapterList = DbHelper.getInstance().getmDaoSession().getDownloadChapterBeanDao().queryBuilder().where(DownloadChapterBeanDao.Properties.NoteUrl.eq(bookItem.getNoteUrl())).orderAsc(DownloadChapterBeanDao.Properties.DurChapterIndex).limit(1).list();
if (downloadChapterList!= null && downloadChapterList.size() > 0) {
if (downloadChapterList != null && downloadChapterList.size() > 0) {
e.onNext(downloadChapterList.get(0));
e.onComplete();
return;
}
}
}
// 如果没有待下载章节,删除所有下载章节记录
DbHelper.getInstance().getmDaoSession().getDownloadChapterBeanDao().deleteAll();
e.onNext(new DownloadChapterBean());
} else {
// 如果书架列表为空,同样删除所有下载章节记录
DbHelper.getInstance().getmDaoSession().getDownloadChapterBeanDao().deleteAll();
e.onNext(new DownloadChapterBean());
}
@ -163,14 +138,12 @@ public class DownloadService extends Service {
.subscribe(new SimpleObserver<DownloadChapterBean>() {
@Override
public void onNext(DownloadChapterBean value) {
if (value.getNoteUrl()!= null && value.getNoteUrl().length() > 0) {
// 如果章节有有效的URL开始下载该章节
if (value.getNoteUrl() != null && value.getNoteUrl().length() > 0) {
downloading(value, 0);
} else {
Observable.create(new ObservableOnSubscribe<Object>() {
@Override
public void subscribe(ObservableEmitter<Object> e) throws Exception {
// 如果章节URL为空删除所有下载章节记录
DbHelper.getInstance().getmDaoSession().getDownloadChapterBeanDao().deleteAll();
e.onNext(new Object());
e.onComplete();
@ -205,17 +178,14 @@ public class DownloadService extends Service {
}
}
// 具体下载章节的逻辑方法,处理下载过程中的重试、保存数据等操作
private void downloading(final DownloadChapterBean data, final int durTime) {
if (durTime < reTryTimes && isStartDownload) {
// 发送下载进度相关的事件,并显示通知
isProgress(data);
Observable.create(new ObservableOnSubscribe<BookContentBean>() {
@Override
public void subscribe(ObservableEmitter<BookContentBean> e) throws Exception {
// 查询数据库中是否已经存在该章节的内容
List<BookContentBean> result = DbHelper.getInstance().getmDaoSession().getBookContentBeanDao().queryBuilder().where(BookContentBeanDao.Properties.DurChapterUrl.eq(data.getDurChapterUrl())).list();
if (result!= null && result.size() > 0) {
if (result != null && result.size() > 0) {
e.onNext(result.get(0));
} else {
e.onNext(new BookContentBean());
@ -226,7 +196,6 @@ public class DownloadService extends Service {
@Override
public ObservableSource<BookContentBean> apply(final BookContentBean bookContentBean) throws Exception {
if (bookContentBean.getDurChapterUrl() == null || bookContentBean.getDurChapterUrl().length() <= 0) {
// 如果章节内容不存在通过WebBookModelImpl获取章节内容然后进行相关数据库操作插入内容、更新章节列表等
return WebBookModelImpl.getInstance().getBookContent(data.getDurChapterUrl(), data.getDurChapterIndex(), data.getTag()).map(new Function<BookContentBean, BookContentBean>() {
@Override
public BookContentBean apply(BookContentBean bookContentBean) throws Exception {
@ -239,7 +208,6 @@ public class DownloadService extends Service {
}
});
} else {
// 如果章节内容已经存在,直接删除下载章节记录并返回该章节内容
return Observable.create(new ObservableOnSubscribe<BookContentBean>() {
@Override
public void subscribe(ObservableEmitter<BookContentBean> e) throws Exception {
@ -256,18 +224,18 @@ public class DownloadService extends Service {
.subscribe(new SimpleObserver<BookContentBean>() {
@Override
public void onNext(BookContentBean value) {
if (isStartDownload) {
if(isStartDownload){
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (isStartDownload) {
if(isStartDownload){
toDownload();
} else {
}else{
isPause();
}
}
}, 800);
} else {
},800);
}else{
isPause();
}
}
@ -284,7 +252,6 @@ public class DownloadService extends Service {
Observable.create(new ObservableOnSubscribe<Boolean>() {
@Override
public void subscribe(ObservableEmitter<Boolean> e) throws Exception {
// 如果重试次数超过限制或者下载失败,删除该下载章节记录
DbHelper.getInstance().getmDaoSession().getDownloadChapterBeanDao().delete(data);
e.onNext(true);
e.onComplete();
@ -295,18 +262,18 @@ public class DownloadService extends Service {
.subscribe(new SimpleObserver<Boolean>() {
@Override
public void onNext(Boolean value) {
if (isStartDownload) {
if(isStartDownload){
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (isStartDownload) {
if(isStartDownload){
toDownload();
} else {
}else{
isPause();
}
}
}, 800);
} else {
},800);
}else{
isPause();
}
}
@ -314,7 +281,7 @@ public class DownloadService extends Service {
@Override
public void onError(Throwable e) {
e.printStackTrace();
if (!isStartDownload)
if(!isStartDownload)
isPause();
}
});
@ -323,19 +290,16 @@ public class DownloadService extends Service {
}
}
// 对外提供的启动下载的方法设置下载开始标记并调用toDownload方法
public void startDownload() {
isStartDownload = true;
toDownload();
}
// 对外提供的暂停下载的方法,设置下载暂停标记并取消所有通知
public void pauseDownload() {
isStartDownload = false;
notifyManager.cancelAll();
}
// 对外提供的取消下载的方法,先删除所有下载章节记录,然后暂停下载
public void cancelDownload() {
Observable.create(new ObservableOnSubscribe<Object>() {
@Override
@ -370,18 +334,17 @@ public class DownloadService extends Service {
});
}
// 处理下载暂停相关逻辑判断是否还有待下载章节根据情况发送相应的RxBus事件
private void isPause() {
isDownloading = false;
Observable.create(new ObservableOnSubscribe<DownloadChapterBean>() {
@Override
public void subscribe(ObservableEmitter<DownloadChapterBean> e) throws Exception {
List<BookShelfBean> bookShelfBeanList = DbHelper.getInstance().getmDaoSession().getBookShelfBeanDao().queryBuilder().orderDesc(BookShelfBeanDao.Properties.FinalDate).list();
if (bookShelfBeanList!= null && bookShelfBeanList.size() > 0) {
if (bookShelfBeanList != null && bookShelfBeanList.size() > 0) {
for (BookShelfBean bookItem : bookShelfBeanList) {
if (!bookItem.getTag().equals(BookShelfBean.LOCAL_TAG)) {
List<DownloadChapterBean> downloadChapterList = DbHelper.getInstance().getmDaoSession().getDownloadChapterBeanDao().queryBuilder().where(DownloadChapterBeanDao.Properties.NoteUrl.eq(bookItem.getNoteUrl())).orderAsc(DownloadChapterBeanDao.Properties.DurChapterIndex).limit(1).list();
if (downloadChapterList!= null && downloadChapterList.size() > 0) {
if (downloadChapterList != null && downloadChapterList.size() > 0) {
e.onNext(downloadChapterList.get(0));
e.onComplete();
return;
@ -401,11 +364,9 @@ public class DownloadService extends Service {
.subscribe(new SimpleObserver<DownloadChapterBean>() {
@Override
public void onNext(DownloadChapterBean value) {
if (value.getNoteUrl()!= null && value.getNoteUrl().length() > 0) {
// 如果还有待下载章节,发送暂停下载的监听事件
if (value.getNoteUrl() != null && value.getNoteUrl().length() > 0){
RxBus.get().post(RxBusTag.PAUSE_DOWNLOAD_LISTENER, new Object());
} else {
// 如果没有待下载章节,发送下载完成的监听事件
}else{
RxBus.get().post(RxBusTag.FINISH_DOWNLOAD_LISTENER, new Object());
}
}
@ -416,43 +377,27 @@ public class DownloadService extends Service {
}
});
}
// 方法作用处理下载进度相关操作发送下载进度相关信息到RxBus并创建和发送通知展示下载进度情况
// 参数downloadChapterBean表示下载章节相关的数据对象包含了如书籍名称、当前章节名称等信息
private void isProgress(DownloadChapterBean downloadChapterBean) {
// 通过RxBus发送下载进度监听器相关的事件传递下载章节数据对象以便其他地方能接收到并处理进度信息
RxBus.get().post(RxBusTag.PROGRESS_DOWNLOAD_LISTENER, downloadChapterBean);
// 创建一个意图用于启动主活动MainActivity这里的上下文是当前类所在的上下文环境
Intent mainIntent = new Intent(this, MainActivity.class);
// 根据创建的意图创建一个PendingIntent对象用于后续设置到通知中使得点击通知可以启动对应的活动
// 这里的请求码设置为0并且设置当有相同请求码的PendingIntent更新时更新当前的PendingIntent
PendingIntent mainPendingIntent = PendingIntent.getActivity(this, 0, mainIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// 创建 Notification.Builder 对象,用于构建通知
//创建 Notification.Builder 对象
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
// 设置通知的小图标这里使用的是资源文件中定义的ic_launcher图标
.setSmallIcon(R.mipmap.ic_launcher)
// 设置点击通知后自动清除该通知
//点击通知后自动清除
.setAutoCancel(true)
// 设置通知的标题,展示正在下载的书籍名称
.setContentTitle("正在下载:" + downloadChapterBean.getBookName())
// 根据当前章节名称是否为空来设置通知的内容文本,如果为空则设置为空格,否则设置为章节名称
.setContentText(downloadChapterBean.getDurChapterName() == null? " " : downloadChapterBean.getDurChapterName())
// 将前面创建的PendingIntent设置到通知中使得点击通知能启动对应的活动
.setContentTitle("正在下载:"+downloadChapterBean.getBookName())
.setContentText(downloadChapterBean.getDurChapterName()==null?" ":downloadChapterBean.getDurChapterName())
.setContentIntent(mainPendingIntent);
// 使用通知管理器发送通知notifiId应该是预先定义好的通知的唯一标识用于区分不同的通知
//发送通知
notifyManager.notify(notifiId, builder.build());
}
// 方法作用完成下载相关的后续操作发送下载完成的事件到RxBus取消所有通知并弹出提示告知用户全部离线章节下载完成
private void finishDownload() {
// 通过RxBus发送下载完成监听器相关的事件传递一个空的Object对象作为完成下载的一种通知机制方便其他地方监听并处理完成下载的情况
RxBus.get().post(RxBusTag.FINISH_DOWNLOAD_LISTENER, new Object());
// 取消所有已发送的通知
notifyManager.cancelAll();
// 在主线程中执行以下代码块用于弹出一个短暂显示的Toast提示信息告知用户全部离线章节下载完成
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
@ -461,8 +406,6 @@ public class DownloadService extends Service {
});
}
// 方法作用响应RxBus中PAUSE_DOWNLOAD事件调用pauseDownload方法此处假设pauseDownload方法在别处定义用于暂停下载任务相关操作
// 该方法被标注为订阅RxBus中特定标签的事件并且在主线程中执行
@Subscribe(
thread = EventThread.MAIN_THREAD,
tags = {
@ -473,8 +416,6 @@ public class DownloadService extends Service {
pauseDownload();
}
// 方法作用响应RxBus中START_DOWNLOAD事件调用startDownload方法此处假设startDownload方法在别处定义用于启动下载任务相关操作
// 该方法被标注为订阅RxBus中特定标签的事件并且在主线程中执行
@Subscribe(
thread = EventThread.MAIN_THREAD,
tags = {
@ -485,8 +426,6 @@ public class DownloadService extends Service {
startDownload();
}
// 方法作用响应RxBus中CANCEL_DOWNLOAD事件调用cancelDownload方法此处假设cancelDownload方法在别处定义用于取消下载任务相关操作
// 该方法被标注为订阅RxBus中特定标签的事件并且在主线程中执行
@Subscribe(
thread = EventThread.MAIN_THREAD,
tags = {
@ -497,9 +436,6 @@ public class DownloadService extends Service {
cancelDownload();
}
// 方法作用响应RxBus中ADD_DOWNLOAD_TASK事件调用addNewTask方法此处假设addNewTask方法在别处定义用于添加新的下载任务相关操作
// 该方法接收一个DownloadChapterListBean类型的对象newData从其中获取具体的数据假设是下载任务列表数据传递给addNewTask方法进行处理
// 该方法被标注为订阅RxBus中特定标签的事件并且在主线程中执行
@Subscribe(
thread = EventThread.MAIN_THREAD,
tags = {
@ -508,4 +444,5 @@ public class DownloadService extends Service {
)
public void addTask(DownloadChapterListBean newData) {
addNewTask(newData.getData());
}
}
}

@ -1,133 +1,96 @@
// 版权信息
//Copyright (c) 2017. 章钦豪. All rights reserved.
// 包名
package com.monke.monkeybook.utils;
// 导入所需的Android和Glide库中的类
import android.content.Context;
// 导入Android的Context类用于获取应用程序的环境信息。
import android.graphics.Bitmap;
// 导入Android的Bitmap类用于操作图像数据。
import android.graphics.Canvas;
// 导入Android的Canvas类用于绘制图像。
import android.graphics.Paint;
// 导入Android的Paint类用于定义绘制时的属性如颜色、样式等。
import android.renderscript.Allocation;
// 导入Android RenderScript框架中的Allocation类用于内存分配。
import android.renderscript.Element;
// 导入Android RenderScript框架中的Element类用于定义数据类型。
import android.renderscript.RenderScript;
// 导入Android RenderScript框架中的RenderScript类用于执行RenderScript脚本。
import android.renderscript.ScriptIntrinsicBlur;
// 导入Android RenderScript框架中的ScriptIntrinsicBlur类用于实现图像的模糊效果。
import com.bumptech.glide.Glide;
// 导入Glide库的Glide类用于图像的加载和缓存。
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
// 导入Glide库的BitmapPool类用于复用Bitmap对象减少内存分配。
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
// 导入Glide库的BitmapTransformation类用于定义Bitmap的转换操作。
// 定义一个类BlurTransformation它扩展了Glide的BitmapTransformation类
public class BlurTransformation extends BitmapTransformation {
// 定义最大模糊半径
private static int MAX_RADIUS = 25;
// 定义默认的下采样比例
private static int DEFAULT_DOWN_SAMPLING = 1;
// 定义成员变量
// Android上下文
private Context mContext;
// Glide的Bitmap池用于重用Bitmap
private BitmapPool mBitmapPool; // Glide的Bitmap池用于重用Bitmap
private BitmapPool mBitmapPool;
// 模糊半径
private int mRadius;
// 下采样比例
private int mSampling;
// 构造函数,使用默认模糊半径和下采样比例
public BlurTransformation(Context context) {
this(context, Glide.get(context).getBitmapPool(), MAX_RADIUS, DEFAULT_DOWN_SAMPLING);
}
// 重写transform方法用于实现模糊效果
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
// 获取原始Bitmap的宽和高
int width = toTransform.getWidth();
int height = toTransform.getHeight();
// 根据下采样比例计算缩放后的宽和高
int scaledWidth = width / mSampling;
int scaledHeight = height / mSampling;
// 从Bitmap池中获取一个Bitmap如果池中没有则创建一个新的
Bitmap bitmap = mBitmapPool.get(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
if (bitmap == null) {
bitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
}
// 创建一个Canvas用于在bitmap上绘图
Canvas canvas = new Canvas(bitmap);
// 设置Canvas的缩放比例以应用下采样
canvas.scale(1 / (float) mSampling, 1 / (float) mSampling);
// 创建一个Paint对象并设置其标志以启用位图过滤
Paint paint = new Paint();
paint.setFlags(Paint.FILTER_BITMAP_FLAG);
// 将原始Bitmap绘制到缩放后的Canvas上
canvas.drawBitmap(toTransform, 0, 0, paint);
// 创建一个RenderScript上下文
RenderScript rs = RenderScript.create(mContext);
// 从RenderScript上下文创建一个Allocation用于存储输入Bitmap数据
Allocation input = Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
// 创建一个输出Allocation类型与输入相同
Allocation output = Allocation.createTyped(rs, input.getType());
// 创建一个ScriptIntrinsicBlur对象用于执行模糊操作
ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
// 设置输入Allocation和模糊半径
blur.setInput(input);
blur.setRadius(mRadius);
// 执行模糊操作将结果存储在输出Allocation中
blur.forEach(output);
// 将输出Allocation的数据复制回Bitmap
output.copyTo(bitmap);
// 销毁RenderScript上下文
rs.destroy();
// 返回处理后的Bitmap
return bitmap;
}
// 其他构造函数,允许用户自定义模糊半径和下采样比例
// ... (省略了其他构造函数的实现,它们主要是调用最后一个构造函数)
public BlurTransformation(Context context, BitmapPool pool) {
this(context, pool, MAX_RADIUS, DEFAULT_DOWN_SAMPLING);
}
public BlurTransformation(Context context, BitmapPool pool, int radius) {
this(context, pool, radius, DEFAULT_DOWN_SAMPLING);
}
public BlurTransformation(Context context, int radius) {
this(context, Glide.get(context).getBitmapPool(), radius, DEFAULT_DOWN_SAMPLING);
}
public BlurTransformation(Context context, BitmapPool pool, int radius, int sampling) {
super(context);
mContext = context;
mBitmapPool = pool;
mRadius = radius;
mSampling = sampling;
}
public BlurTransformation(Context context, int radius, int sampling) {
super(context);
mContext = context;
mBitmapPool = Glide.get(context).getBitmapPool();
mRadius = radius;
mSampling = sampling;
}
// 重写getId方法返回一个唯一标识此转换的字符串
@Override public String getId() {
return "BlurTransformation(radius=" + mRadius + ", sampling=" + mSampling + ")";
}
}
}

@ -1,203 +1,101 @@
// 版权信息
//Copyright (c) 2017. 章钦豪. All rights reserved.
// 这行注释声明了代码的版权所有者和版权年份。
package com.monke.monkeybook.utils;
// 声明包名
// 导入所需的Android类
import android.app.Activity;
// 导入Activity类用于获取窗口管理器和显示度量。
import android.content.Context;
// 导入Context类提供了一个操作当前环境的抽象。
import android.graphics.Point;
// 导入Point类用于表示屏幕分辨率的点。
import android.util.DisplayMetrics;
// 导入DisplayMetrics类用于获取屏幕相关的度量信息。
import android.util.TypedValue;
// 导入TypedValue类用于进行单位转换。
import android.view.Display;
// 导入Display类用于获取屏幕显示相关的信息。
import android.view.WindowManager;
// 导入WindowManager类用于全局控制窗口的创建和管理。
import java.lang.reflect.Method;
// 导入Method类用于反射机制。
public class DensityUtil {
// 声明DensityUtil工具类
/**
* dppx
*
* @param context
*
* @param dpVal dp
* dppx
*
* @return px
* @param context
* @param
* @return
*/
public static int dp2px(Context context, float dpVal) {
// 使用TypedValue类的applyDimension方法进行单位转换
// TypedValue.COMPLEX_UNIT_DIP表示dp单位
// dpVal是要转换的dp值
// context.getResources().getDisplayMetrics()提供了设备的显示度量信息,用于计算转换
public static int dp2px(Context context, float dpVal)
{
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.getResources().getDisplayMetrics());
}
/**
* sppx
*
* sppx
*
* @param context
*
* @param spVal sp
*
* @return px
* @param context
* @param
* @return
*/
public static int sp2px(Context context, float spVal) {
public static int sp2px(Context context, float spVal)
{
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
spVal, context.getResources().getDisplayMetrics());
}
/**
* pxdp
*
* @param context
*
* @param pxVal px
*
* @return dp
* pxdp
*
* @param context
* @param pxVal
* @return
*/
public static float px2dp(Context context, float pxVal) {
// 使用TypedValue类的applyDimension方法进行单位转换
// TypedValue.COMPLEX_UNIT_SP表示sp单位
// spVal是要转换的sp值
// context.getResources().getDisplayMetrics()提供了设备的显示度量信息,用于计算转换
public static float px2dp(Context context, float pxVal)
{
final float scale = context.getResources().getDisplayMetrics().density;
return (pxVal / scale);
}
/**
* pxsp
* @param context
*
* @param pxVal px
*
* @return sp
* pxsp
*
* @param
* @param pxVal
* @return
*/
public static float px2sp(Context context, float pxVal) {
// 获取设备的显示度量信息特别是scaledDensity它表示屏幕密度的缩放因子
// 将px值除以scaledDensity得到sp值因为sp设计为与用户感知的尺度无关
public static float px2sp(Context context, float pxVal)
{
return (pxVal / context.getResources().getDisplayMetrics().scaledDensity);
}
/**
* UI
*
* @param context
*
* @return Point
*/
public static Point getDisplayPoint(Context context) {
// 获取WindowManager服务用于操作和查询窗口的属性
public static Point getDisplayPoint(Context context){
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 获取默认的Display对象包含了屏幕的显示信息
Display display = windowManager.getDefaultDisplay();
// 创建DisplayMetrics对象用于存储屏幕的度量信息
DisplayMetrics displayMetrics = new DisplayMetrics();
// 抑制原生类型警告因为Class类无法确定具体的类型参数
@SuppressWarnings("rawtypes")
Class c;
// 声明一个Class类型的变量c
try {
c = Class.forName("android.view.Display");
// 通过反射获取Display类
@SuppressWarnings("unchecked")
Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
// 获取getRealMetrics方法
Method method = c.getMethod("getRealMetrics",DisplayMetrics.class);
method.invoke(display, displayMetrics);
// 调用getRealMetrics方法获取真实分辨率
return new Point(displayMetrics.widthPixels , displayMetrics.heightPixels);
// 返回分辨率
} catch (Exception e) {
return new Point(displayMetrics.widthPixels ,displayMetrics.heightPixels );
}catch(Exception e){
e.printStackTrace();
// 打印异常信息
}
DisplayMetrics dm = new DisplayMetrics();
// 创建一个DisplayMetrics对象
((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(dm);
// 获取默认的分辨率
return new Point(dm.widthPixels , dm.heightPixels);
// 返回默认的分辨率
return new Point(dm.widthPixels ,dm.heightPixels);
}
/**
*
*
* @param context
*
* @return
*/
public static int getWindowWidth(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 获取WindowManager服务
public static int getWindowWidth(Context context){
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
// 创建一个DisplayMetrics对象
wm.getDefaultDisplay().getMetrics(dm);
// 获取默认的分辨率
int width = dm.widthPixels;
// 获取宽度
return width;
// 返回宽度
}
/**
*
*
* @param context
*
* @return
*/
public static int getWindowHeight(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 获取WindowManager服务
public static int getWindowHeight(Context context){
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
// 创建一个DisplayMetrics对象
wm.getDefaultDisplay().getMetrics(dm);
// 获取默认的分辨率
int height = dm.heightPixels;
// 获取高度
return height;
// 返回高度
}
}
}

@ -1,74 +1,37 @@
//Copyright (c) 2017. 章钦豪. All rights reserved.
// 版权声明,表明代码的版权归属于章钦豪。
package com.monke.monkeybook.utils;
// 声明包名。
import android.content.Context;
// 导入Context类提供了一个操作当前环境的抽象。
import android.net.ConnectivityManager;
// 导入ConnectivityManager类用于获取网络连接信息。
import android.net.NetworkInfo;
// 导入NetworkInfo类用于获取网络状态信息。
import com.monke.monkeybook.MApplication;
// 导入MApplication类用于获取应用程序的实例。
import com.monke.monkeybook.R;
// 导入R类用于访问应用程序的资源。
import java.util.HashMap;
// 导入HashMap类用于创建键值对映射。
import java.util.Map;
// 导入Map接口用于提供键值对集合。
public class NetworkUtil {
// 声明NetworkUtil工具类用于网络相关的工具方法。
private static final Map<Integer, String> errorMap = new HashMap<>();
// 创建一个HashMap用于存储错误代码和错误信息的映射。
public static final int SUCCESS = 10000;
// 定义一个常量,表示操作成功。
public static final int ERROR_CODE_NONET = 10001;
// 定义一个常量,表示网络不可用错误代码。
public static final int ERROR_CODE_OUTTIME = 10002;
// 定义一个常量,表示网络超时错误代码。
public static final int ERROR_CODE_ANALY = 10003;
// 定义一个常量,表示网络分析错误代码。
static{
// 初始化静态代码块用于初始化errorMap。
errorMap.put(ERROR_CODE_NONET, MApplication.getInstance().getString(R.string.net_error_10001));
// 将网络不可用错误代码和错误信息添加到errorMap。
errorMap.put(ERROR_CODE_OUTTIME, MApplication.getInstance().getString(R.string.net_error_10002));
// 将网络超时错误代码和错误信息添加到errorMap。
errorMap.put(ERROR_CODE_ANALY, MApplication.getInstance().getString(R.string.net_error_10003));
// 将网络分析错误代码和错误信息添加到errorMap。
}
public static String getErrorTip(int code) {
// 定义一个方法,用于根据错误代码获取错误提示信息。
return errorMap.get(code);
}
public static boolean isNetWorkAvailable() {
// 定义一个方法,用于检查网络是否可用。
ConnectivityManager manager = (ConnectivityManager) MApplication.getInstance()
.getSystemService(Context.CONNECTIVITY_SERVICE);
// 获取ConnectivityManager服务实例。
if (manager != null) {
NetworkInfo info = manager.getActiveNetworkInfo();
// 获取当前活跃的网络信息。
if (info != null && info.isConnected()) {
return true;
} else {
@ -78,4 +41,4 @@ public class NetworkUtil {
return false;
}
}
}
}

@ -1,73 +1,36 @@
//Copyright (c) 2017. 章钦豪. All rights reserved.
// 版权声明,表明代码的版权归属于章钦豪。
package com.monke.monkeybook.utils;
// 声明包名。
public class NumberUtil {
// 声明NumberUtil工具类用于数字转换相关的工具方法。
/**
*
*/
private static int chineseNumber2Int(String chineseNumber){
// 定义一个私有静态方法,用于将中文数字转换为阿拉伯数字。
int result = 0;
// 用于存储转换结果。
int temp = 1;//存放一个单位的数字如:十万
// 临时存储一个单位的数值如“十”则为10“百”则为100。
int count = 0;//判断是否有chArr
// 用于计数“千”、“万”、“亿”等单位出现的次数。
char[] cnArr = new char[]{'一','二','三','四','五','六','七','八','九'};
// 包含中文数字0-9的数组。
char[] chArr = new char[]{'十','百','千','万','亿'};
// 包含中文单位“十”、“百”、“千”、“万”、“亿”的数组。
for (int i = 0; i < chineseNumber.length(); i++) {
// 遍历输入的中文数字字符串。
boolean b = true;//判断是否是chArr
// 标记当前字符是否属于单位数组chArr。
char c = chineseNumber.charAt(i);
// 获取当前遍历到的字符。
for (int j = 0; j < cnArr.length; j++) {
//非单位,即数字
// 检查当前字符是否为数字('一'到'九')。
for (int j = 0; j < cnArr.length; j++) {//非单位,即数字
if (c == cnArr[j]) {
if(0 != count){
//添加下一个单位之前,先把上一个单位值添加到结果中
// 如果count不为0说明之前已经遇到了单位字符需要先计算之前的数值。
if(0 != count){//添加下一个单位之前,先把上一个单位值添加到结果中
result += temp;
// 将临时数值累加到结果中。
temp = 1;
// 重置临时数值为1。
count = 0;
// 重置计数器。
}
// 下标+1就是对应的值
temp = j + 1;
// 将当前数字字符转换为对应的数值并更新temp。
b = false;
// 标记当前字符已处理。
break;
}
}
if(b){//单位{'十','百','千','万','亿'}
// 如果当前字符不是数字,那么它应该是一个单位。
for (int j = 0; j < chArr.length; j++) {
// 遍历单位数组,检查当前字符是否为单位。
if (c == chArr[j]) {
// 根据单位字符更新temp的值。
switch (j) {
case 0:
temp *= 10;
@ -88,21 +51,13 @@ public class NumberUtil {
break;
}
count++;
// 每遇到一个单位字符,计数器增加。
}
}
}
if (i == chineseNumber.length() - 1) {
//遍历到最后一个字符
// 如果已经遍历到字符串的最后一个字符。
if (i == chineseNumber.length() - 1) {//遍历到最后一个字符
result += temp;
// 将最后一个单位的值累加到结果中。
}
}
return result;
// 返回转换后的阿拉伯数字。
}
}
}

@ -1,69 +1,36 @@
// 声明一个名为ParseSystemUtil的公共类它位于com.monke.monkeybook.utils包下
package com.monke.monkeybook.utils;
public class ParseSystemUtil {
/**
*
*
* @param buf
* @return
/**16
* @param buf
* @return
*/
public static String parseByte2HexStr(byte buf[]) {
// 创建一个StringBuffer对象用于构建最终的十六进制字符串
StringBuffer sb = new StringBuffer();
// 遍历输入的字节数组
for (int i = 0; i < buf.length; i++) {
// 将当前字节转换为无符号的十六进制字符串
// 注意byte在Java中是有符号的所以使用& 0xFF来转换为无符号数
String hex = Integer.toHexString(buf[i] & 0xFF);
// 如果转换后的十六进制字符串长度为1即只包含一个十六进制数字则在其前面补0
if (hex.length() == 1) {
hex = '0' + hex;
}
// 将转换后的十六进制字符串大写追加到StringBuffer中
sb.append(hex.toUpperCase());
}
// 返回构建好的十六进制字符串
return sb.toString();
}
/**
*
*
* @param hexStr
* @return
/**16
* @param hexStr
* @return
*/
public static byte[] parseHexStr2Byte(String hexStr) {
// 如果输入的十六进制字符串为空或为null则返回null
if (hexStr == null || hexStr.length() == 0) {
if (hexStr==null || hexStr.length() ==0) {
return null;
}
// 根据输入的十六进制字符串长度计算字节数组的长度
// 每两个十六进制字符表示一个字节
byte[] result = new byte[hexStr.length() / 2];
// 遍历输入的十六进制字符串,每两个字符一组进行转换
for (int i = 0; i < hexStr.length() / 2; i++) {
// 从当前位置开始,截取两个十六进制字符
// 第一个参数是起始位置,第二个参数是结束位置(不包含)
int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
// 将两个十六进制数字组合成一个字节,并存储到结果数组中
// 注意这里需要将结果转换为byte类型因为parseInt返回的是int类型
byte[] result = new byte[hexStr.length()/2];
for (int i = 0;i< hexStr.length()/2; i++) {
int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);
int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);
result[i] = (byte) (high * 16 + low);
}
// 返回转换后的字节数组
return result;
}
}
}

@ -1,101 +1,54 @@
// 版权信息表明此代码由章钦豪在2017年创建并保留所有权利
//Copyright (c) 2017. 章钦豪. All rights reserved.
// 声明包名表示此类位于com.monke.monkeybook.utils包下
package com.monke.monkeybook.utils;
// 导入所需的Android类
// 导入Android内容上下文类用于访问应用的特定资源和类以及调用应用级别的操作
import android.content.Context;
// 导入Android意图类用于在不同组件如Activity、Service等之间进行通信
import android.content.Intent;
// 导入Android包信息类用于获取已安装应用包的信息如版本号、签名等
import android.content.pm.PackageInfo;
// 导入Android包管理器类用于访问应用包的信息如已安装应用的列表、应用签名等
import android.content.pm.PackageManager;
// 导入Android URI类用于表示统一资源标识符URI通常用于访问网络资源或文件
import android.net.Uri;
// 导入Android构建类用于获取当前Android系统的版本信息
import android.os.Build;
// 导入Android支持库中的权限检查类用于检查应用是否具有执行特定操作的权限
import android.support.v4.content.PermissionChecker;
// 声明一个名为PremissionCheck的公共类用于检查权限和请求权限设置
public class PremissionCheck {
// 定义一个静态方法,用于检查应用是否具有指定的权限
public static Boolean checkPremission(Context context, String permission) {
boolean result = false; // 初始化结果为false表示没有权限
// 检查目标SDK版本和当前SDK版本是否都大于或等于Android M6.0
public static Boolean checkPremission(Context context,String permission){
boolean result = false;
if (getTargetSdkVersion(context) >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 如果是则使用context的checkSelfPermission方法检查权限
result = context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
result = context.checkSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED;
} else {
// 如果不是则使用PermissionChecker的checkSelfPermission方法检查权限
result = PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker.PERMISSION_GRANTED;
result = PermissionChecker.checkSelfPermission(context, permission)
== PermissionChecker.PERMISSION_GRANTED;
}
// 返回检查结果
return result;
}
// 定义一个私有静态方法用于获取应用的目标SDK版本
private static int getTargetSdkVersion(Context context) {
int version = 0; // 初始化版本号为0
int version = 0;
try {
// 通过包管理器获取应用的包信息
final PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
// 从包信息中获取目标SDK版本
final PackageInfo info = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
version = info.applicationInfo.targetSdkVersion;
} catch (PackageManager.NameNotFoundException e) {
// 如果捕获到包未找到的异常,则打印堆栈跟踪信息
e.printStackTrace();
}
// 返回目标SDK版本
return version;
}
// 定义一个静态方法,用于请求跳转到应用的权限设置页面
public static void requestPermissionSetting(Context from) {
try {
// 创建一个Intent对象
Intent localIntent = new Intent();
// 为Intent添加新任务标志
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 根据Android版本选择不同的Action和Data来跳转到权限设置页面
if (Build.VERSION.SDK_INT >= 9) {
// 对于Android 2.3API级别9及以上版本使用APPLICATION_DETAILS_SETTINGS Action
localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
// 设置Data为当前应用的包名
localIntent.setData(Uri.fromParts("package", from.getPackageName(), null));
} else if (Build.VERSION.SDK_INT <= 8) {
// 对于Android 2.2API级别8及以下版本使用Intent的ACTION_VIEW Action和特定的类名
localIntent.setAction(Intent.ACTION_VIEW);
localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
// 通过Extra传递当前应用的包名
localIntent.putExtra("com.android.settings.ApplicationPkgName", from.getPackageName());
}
// 启动Intent跳转到权限设置页面
from.startActivity(localIntent);
} catch (Exception e) {
// 如果捕获到异常,则打印堆栈跟踪信息
e.printStackTrace();
}
}
}
}

@ -1,97 +1,47 @@
package com.monke.monkeybook.utils.aes;
// 声明包名,指定该类属于哪个包。
import com.monke.monkeybook.utils.ParseSystemUtil;
// 导入ParseSystemUtil类用于处理字节和十六进制字符串的转换。
import javax.crypto.*;
// 导入javax.crypto包中的所有类用于AES加密和解密。
import javax.crypto.spec.SecretKeySpec;
// 导入SecretKeySpec类用于指定AES密钥。
import java.io.UnsupportedEncodingException;
// 导入UnsupportedEncodingException类用于处理可能的编码不支持异常。
public class AESUtil {
// 声明AESUtil类用于提供AES加密和解密的工具方法。
public static String aesEncode(String cleartext, String seed) throws Exception {
// 定义公有静态方法用于AES加密。
byte[] rawKey = deriveKeyInsecurely(seed, 32).getEncoded();
// 从种子字符串生成AES密钥。
byte[] result = encrypt(rawKey, cleartext.getBytes());
// 使用AES密钥对明文进行加密。
return ParseSystemUtil.parseByte2HexStr(result);
// 将加密结果转换为十六进制字符串并返回。
}
public static String aesDecode(String encrypted, String seed) throws Exception {
// 定义公有静态方法用于AES解密。
byte[] rawKey = deriveKeyInsecurely(seed, 32).getEncoded();
// 从种子字符串生成AES密钥。
byte[] enc = ParseSystemUtil.parseHexStr2Byte(encrypted);
// 将十六进制字符串转换为字节数组。
byte[] result = decrypt(rawKey, enc);
// 使用AES密钥对密文进行解密。
return new String(result);
// 将解密结果转换为字符串并返回。
}
private static SecretKey deriveKeyInsecurely(String password, int keySizeInBytes) throws UnsupportedEncodingException {
// 定义私有静态方法用于不安全地从密码生成AES密钥。
byte[] passwordBytes = password.getBytes("UTF-8");
// 将密码字符串转换为UTF-8编码的字节数组。
return new SecretKeySpec(
InsecureSHA1PRNGKeyDerivator.deriveInsecureKey(
passwordBytes, keySizeInBytes),
"AES");
// 使用SHA1PRNGKeyDerivator生成密钥并创建SecretKeySpec对象。
}
private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
// 定义私有静态方法用于AES加密。
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
// 创建AES密钥规范。
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// 获取AES/ECB/PKCS5Padding模式的Cipher实例。
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
// 初始化Cipher为加密模式。
byte[] encrypted = cipher.doFinal(clear);
// 对明文进行加密。
return encrypted;
// 返回加密结果。
}
private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
// 定义私有静态方法用于AES解密。
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
// 创建AES密钥规范。
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// 获取AES/ECB/PKCS5Padding模式的Cipher实例。
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
// 初始化Cipher为解密模式。
byte[] decrypted = cipher.doFinal(encrypted);
// 对密文进行解密。
return decrypted;
// 返回解密结果。
}
}

@ -1,16 +1,7 @@
// 包声明
package com.monke.monkeybook.utils.base64;
// 导入Java的输入输出IO相关的类
// IOException类表示输入输出操作中可能发生的异常
import java.io.IOException;
// OutputStream类是字节输出流的所有类的超类用于写入字节流到一个目标如文件、内存等
import java.io.OutputStream;
// PushbackInputStream类是一个包含了一个内部回退缓冲区的输入流
// 它允许程序将最近从输入流中读取的字节(或者字节数组)回退到输入流中
// 这可以用于重新读取已经读取的字节,或者用于纠正读取错误
import java.io.PushbackInputStream;
public class BASE64Decoder extends CharacterDecoder{
@ -18,113 +9,83 @@ public class BASE64Decoder extends CharacterDecoder{
private static final byte[] pem_convert_array = new byte[256];
byte[] decode_buffer = new byte[4];
// 构造函数,无特定操作
public BASE64Decoder() {}
// 返回解码过程中每个“原子”在这里是Base64编码的4个字符对应的原始字节数3或4
protected int bytesPerAtom() {
return 4; // 这里的4指的是解码过程中处理的Base64字符数不是字节数
}
// 返回解码后每行的字节数限制Base64编码通常没有每行字节数的限制但这里可能是为了格式化输出
protected int bytesPerLine() {
return 72; // 假设每行最多72个字节的输出用于格式化
}
// 解码一个Base64编码的“原子”4个字符并将其写入输出流
protected void decodeAtom(PushbackInputStream var1, OutputStream var2, int var3) throws IOException {
// 初始化四个变量来存储解码后的字节,初始值为-1表示无效
byte var5 = -1, var6 = -1, var7 = -1, var8 = -1;
public BASE64Decoder() {
}
protected int bytesPerAtom() {
return 4;
}
protected int bytesPerLine() {
return 72;
}
protected void decodeAtom(PushbackInputStream var1, OutputStream var2, int var3) throws IOException {
byte var5 = -1;
byte var6 = -1;
byte var7 = -1;
byte var8 = -1;
if (var3 < 2) {
throw new CEFormatException("BASE64Decoder: Not enough bytes for an atom.");
} else {
int var4;
do {
var4 = var1.read();
if (var4 == -1) {
throw new CEStreamExhausted();
}
} while(var4 == 10 || var4 == 13);
// 如果提供的字符数少于2个则无法解码因为至少需要2个字符来表示一个有效的Base64编码单元
if (var3 < 2) {
throw new CEFormatException("BASE64Decoder: Not enough bytes for an atom.");
this.decode_buffer[0] = (byte)var4;
var4 = this.readFully(var1, this.decode_buffer, 1, var3 - 1);
if (var4 == -1) {
throw new CEStreamExhausted();
} else {
int var4;
// 跳过任何换行符(\n或\r直到找到一个有效的Base64字符
do {
var4 = var1.read();
if (var4 == -1) {
// 如果到达输入流的末尾
if (var3 > 3 && this.decode_buffer[3] == 61) {
var3 = 3;
}
throw new CEStreamExhausted();
// 抛出流耗尽异常
}
} while (var4 == 10 || var4 == 13); // 10是\n13是\r
if (var3 > 2 && this.decode_buffer[2] == 61) {
var3 = 2;
}
// 将读取的第一个有效字符存入解码缓冲区
this.decode_buffer[0] = (byte) var4;
// 读取剩余的字符到解码缓冲区,并返回实际读取的字符数
var4 = this.readFully(var1, this.decode_buffer, 1, var3 - 1);
if (var4 == -1) {
// 如果再次到达输入流的末尾
throw new CEStreamExhausted();
// 抛出流耗尽异常
} else {
// 如果缓冲区中有'='表示Base64编码的填充字符则调整要解码的字符数
if (var3 > 3 && this.decode_buffer[3] == 61) { // 61是'='的ASCII码
var3 = 3;
}
if (var3 > 2 && this.decode_buffer[2] == 61) {
var3 = 2;
}
switch(var3) {
case 4:
var8 = pem_convert_array[this.decode_buffer[3] & 255];
case 3:
var7 = pem_convert_array[this.decode_buffer[2] & 255];
case 2:
var6 = pem_convert_array[this.decode_buffer[1] & 255];
var5 = pem_convert_array[this.decode_buffer[0] & 255];
default:
switch(var3) {
case 2:
var2.write((byte)(var5 << 2 & 252 | var6 >>> 4 & 3));
break;
case 3:
var2.write((byte)(var5 << 2 & 252 | var6 >>> 4 & 3));
var2.write((byte)(var6 << 4 & 240 | var7 >>> 2 & 15));
break;
case 4:
var2.write((byte)(var5 << 2 & 252 | var6 >>> 4 & 3));
var2.write((byte)(var6 << 4 & 240 | var7 >>> 2 & 15));
var2.write((byte)(var7 << 6 & 192 | var8 & 63));
}
// 根据读取的字符数var3进行解码
switch (var3) {
case 4: // 如果读取了4个字符
var8 = pem_convert_array[this.decode_buffer[3] & 255];
// 解码最后一个字符
case 3: // 如果读取了3个字符或原本读取了4个但最后一个是'='
var7 = pem_convert_array[this.decode_buffer[2] & 255];
// 解码倒数第二个字符
case 2: // 如果读取了2个字符或原本读取了更多但后面有'='
var6 = pem_convert_array[this.decode_buffer[1] & 255];
// 解码第三个字符
var5 = pem_convert_array[this.decode_buffer[0] & 255];
// 解码第一个字符
default:
// 这里的default实际上不会执行因为switch已经覆盖了所有可能的case
// 根据读取的字符数即var3的值执行相应的解码操作
switch (var3) {
case 2:
// 2个字符对应1个字节的原始数据
var2.write((byte) (var5 << 2 & 252 | var6 >>> 4 & 3));
// 解码并写入输出流
break;
case 3:
// 3个字符对应2个字节的原始数据
var2.write((byte) (var5 << 2 & 252 | var6 >>> 4 & 3));
// 解码第一个字节并写入
var2.write((byte) (var6 << 4 & 240 | var7 >>> 2 & 15));
// 解码第二个字节并写入
break;
case 4:
// 4个字符对应3个字节的原始数据或带填充的4个字节数据
var2.write((byte) (var5 << 2 & 252 | var6 >>> 4 & 3));
// 解码第一个字节并写入
var2.write((byte) (var6 << 4 & 240 | var7 >>> 2 & 15));
// 解码第二个字节并写入
var2.write((byte) (var7 << 6 & 192 | var8 & 63));
// 解码第三个字节并写入
}
}
}
}
}
}
// 静态初始化块用于填充pem_convert_array数组
static {
int var0;
// 初始化pem_convert_array数组将所有元素设置为-1表示无效字符
for (var0 = 0; var0 < 255; ++var0) {
pem_convert_array[var0] = -1;
}
static {
int var0;
for(var0 = 0; var0 < 255; ++var0) {
pem_convert_array[var0] = -1;
}
// 将pem_array中的字符映射到pem_convert_array中字符的ASCII值作为索引字符在pem_array中的位置作为值
for (var0 = 0; var0 < pem_array.length; ++var0) {
pem_convert_array[pem_array[var0]] = (byte) var0;
}
for(var0 = 0; var0 < pem_array.length; ++var0) {
pem_convert_array[pem_array[var0]] = (byte)var0;
}
}
}
}

@ -1,83 +1,52 @@
package com.monke.monkeybook.utils.base64;
// 定义该类所在的包
import java.io.IOException;
// 导入IOException类用于处理IO异常
import java.io.OutputStream;
// 导入OutputStream类用于处理输出流
public class BASE64Encoder extends CharacterEncoder{
// 定义BASE64Encoder类继承自CharacterEncoder类
// 定义BASE64编码表包含所有可用的字符
private static final char[] pem_array = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
public BASE64Encoder() {
// 构造方法初始化BASE64Encoder对象
}
// 每个原始数据块的字节数是3
protected int bytesPerAtom() {
return 3;
}
// 每行的字节数限制为57
protected int bytesPerLine() {
return 57;
}
// 编码一个原子即3字节数据块并将其写入输出流
protected void encodeAtom(OutputStream var1, byte[] var2, int var3, int var4) throws IOException {
byte var5;
// 如果剩余字节数为1
if (var4 == 1) {
var5 = var2[var3];
// 取第一个字节
byte var6 = 0;
// 由于只剩1个字节第二个字节为0
boolean var7 = false;
// 未使用的变量
var1.write(pem_array[var5 >>> 2 & 63]);
// 写入第一个字符
var1.write(pem_array[(var5 << 4 & 48) + (var6 >>> 4 & 15)]);
// 写入第二个字符
var1.write(61);
// 填充字符 '='
var1.write(61);
// 填充字符 '='
} else {
byte var8;
// 如果剩余字节数为2
if (var4 == 2) {
var5 = var2[var3];
// 取第一个字节
var8 = var2[var3 + 1];
// 取第二个字节
byte var9 = 0;
// 第三个字节为0
var1.write(pem_array[var5 >>> 2 & 63]);
// 写入第一个字符
var1.write(pem_array[(var5 << 4 & 48) + (var8 >>> 4 & 15)]);
// 写入第二个字符
var1.write(pem_array[(var8 << 2 & 60) + (var9 >>> 6 & 3)]);
// 写入第三个字符
var1.write(61);
// 填充字符 '='
} else {
var5 = var2[var3];
// 取第一个字节
var8 = var2[var3 + 1];
// 取第二个字节
byte var10 = var2[var3 + 2];
// 取第三个字节
var1.write(pem_array[var5 >>> 2 & 63]);
// 写入第一个字符
var1.write(pem_array[(var5 << 4 & 48) + (var8 >>> 4 & 15)]);
// 写入第二个字符
var1.write(pem_array[(var8 << 2 & 60) + (var10 >>> 6 & 3)]);
// 写入第三个字符
var1.write(pem_array[var10 & 63]);
// 写入第四个字符
}
}
}
}

@ -1,17 +1,11 @@
package com.monke.monkeybook.utils.base64;
// 定义该类所在的包
import java.io.IOException;
// 导入IOException类用于处理IO异常
public class CEFormatException extends IOException {
// 定义CEFormatException类继承自IOException类
static final long serialVersionUID = -7139121221067081482L;
// 定义serialVersionUID确保类的序列化兼容性
public CEFormatException(String var1) {
// 构造方法,接收一个字符串作为异常信息
super(var1);
// 调用父类IOException的构造方法传递异常信息
}
}
}

@ -1,15 +1,10 @@
package com.monke.monkeybook.utils.base64;
// 定义该类所在的包
import java.io.IOException;
// 导入IOException类用于处理输入输出异常
public class CEStreamExhausted extends IOException {
// 定义CEStreamExhausted类继承自IOException类
static final long serialVersionUID = -5889118049525891904L;
// 定义serialVersionUID用于序列化时验证类的兼容性
public CEStreamExhausted() { // 构造方法,不带参数
public CEStreamExhausted() {
}
}

@ -1,177 +1,102 @@
package com.monke.monkeybook.utils.base64; // 定义该类所在的包
package com.monke.monkeybook.utils.base64;
import java.io.ByteArrayInputStream;
// 导入ByteArrayInputStream类用于从字节数组中读取数据
import java.io.ByteArrayOutputStream;
// 导入ByteArrayOutputStream类用于将数据写入字节数组
import java.io.IOException;
// 导入IOException类用于处理输入输出异常
import java.io.InputStream;
// 导入InputStream类用于处理输入流
import java.io.OutputStream;
// 导入OutputStream类用于处理输出流
import java.io.PushbackInputStream;
// 导入PushbackInputStream类用于从输入流中推回字节
import java.nio.ByteBuffer;
// 导入ByteBuffer类用于操作字节缓冲区
public abstract class CharacterDecoder {
// 定义CharacterDecoder抽象类用于解码字符流
public CharacterDecoder() {
// 构造方法
}
// 抽象方法,返回每个原子(数据块)字节数
protected abstract int bytesPerAtom();
// 抽象方法,返回每行字节数
protected abstract int bytesPerLine();
// 解码缓冲区前缀,空实现,子类可以重写
protected void decodeBufferPrefix(PushbackInputStream var1, OutputStream var2) throws IOException {
}
// 解码缓冲区后缀,空实现,子类可以重写
protected void decodeBufferSuffix(PushbackInputStream var1, OutputStream var2) throws IOException {
}
// 解码行前缀,默认实现返回每行的字节数
protected int decodeLinePrefix(PushbackInputStream var1, OutputStream var2) throws IOException {
return this.bytesPerLine(); // 返回每行的字节数
return this.bytesPerLine();
}
// 解码行后缀,空实现,子类可以重写
protected void decodeLineSuffix(PushbackInputStream var1, OutputStream var2) throws IOException {
}
// 解码原子数据块此方法抛出CEStreamExhausted异常表示流已耗尽
protected void decodeAtom(PushbackInputStream var1, OutputStream var2, int var3) throws IOException {
throw new CEStreamExhausted(); // 抛出流耗尽异常
throw new CEStreamExhausted();
}
// 从输入流中读取指定字节数,直到读取完成或遇到流结束
protected int readFully(InputStream var1, byte[] var2, int var3, int var4) throws IOException {
for (int var5 = 0; var5 < var4; ++var5) {
// 循环读取字节直到满足要求
for(int var5 = 0; var5 < var4; ++var5) {
int var6 = var1.read();
// 读取一个字节
if (var6 == -1) {
// 如果遇到流结束
return var5 == 0 ? -1 : var5;
// 如果没有读取字节则返回-1否则返回已读取字节数
}
var2[var5 + var3] = (byte)var6;
// 将读取的字节存入目标数组
}
return var4; // 返回读取的字节数
return var4;
}
// 解码整个缓冲区数据,并将解码后的结果写入输出流
public void decodeBuffer(InputStream var1, OutputStream var2) throws IOException {
int var4 = 0;
// 已解码字节数
PushbackInputStream var5 = new PushbackInputStream(var1);
// 使用PushbackInputStream处理输入流
this.decodeBufferPrefix(var5, var2);
// 解码前缀,子类可以实现
while (true) {
// 不断读取并解码数据直到流结束
while(true) {
try {
int var6 = this.decodeLinePrefix(var5, var2);
// 解码行前缀
int var3;
// 解码每个原子(数据块),直到当前行的数据量达到要求
for (var3 = 0; var3 + this.bytesPerAtom() < var6; var3 += this.bytesPerAtom()) {
for(var3 = 0; var3 + this.bytesPerAtom() < var6; var3 += this.bytesPerAtom()) {
this.decodeAtom(var5, var2, this.bytesPerAtom());
// 解码一个原子
var4 += this.bytesPerAtom();
// 累加已解码字节数
}
if (var3 + this.bytesPerAtom() == var6) {
// 如果行正好能填满
this.decodeAtom(var5, var2, this.bytesPerAtom());
// 解码一个原子
var4 += this.bytesPerAtom();
// 累加已解码字节数
} else {
// 如果行不足一个原子
this.decodeAtom(var5, var2, var6 - var3);
// 解码剩余字节
var4 += var6 - var3;
// 累加已解码字节数
}
this.decodeLineSuffix(var5, var2);
// 解码行后缀,子类可以实现
} catch (CEStreamExhausted var8) {
// 捕获流耗尽异常
this.decodeBufferSuffix(var5, var2);
// 解码后缀
return;
// 结束解码
}
}
}
// 将一个字符串解码为字节数组
public byte[] decodeBuffer(String var1) throws IOException {
byte[] var2 = new byte[var1.length()];
// 创建字节数组
var1.getBytes(0, var1.length(), var2, 0);
// 将字符串转为字节数组
ByteArrayInputStream var3 = new ByteArrayInputStream(var2);
// 创建字节数组输入流
ByteArrayOutputStream var4 = new ByteArrayOutputStream();
// 创建字节数组输出流
this.decodeBuffer(var3, var4);
// 解码缓冲区
return var4.toByteArray();
// 返回解码后的字节数组
}
// 将输入流解码为字节数组
public byte[] decodeBuffer(InputStream var1) throws IOException {
ByteArrayOutputStream var2 = new ByteArrayOutputStream();
// 创建字节数组输出流
this.decodeBuffer(var1, var2);
// 解码缓冲区
return var2.toByteArray();
// 返回解码后的字节数组
}
// 将解码后的数据转为ByteBuffer对象
public ByteBuffer decodeBufferToByteBuffer(String var1) throws IOException {
return ByteBuffer.wrap(this.decodeBuffer(var1));
// 解码字符串并包装为ByteBuffer
}
// 将解码后的数据转为ByteBuffer对象
public ByteBuffer decodeBufferToByteBuffer(InputStream var1) throws IOException {
return ByteBuffer.wrap(this.decodeBuffer(var1));
// 解码输入流并包装为ByteBuffer
}
}
}

@ -1,310 +1,181 @@
package com.monke.monkeybook.utils.base64;
// 定义该类所在的包
import java.io.ByteArrayInputStream;
// 导入ByteArrayInputStream类用于从字节数组中读取数据
import java.io.ByteArrayOutputStream;
// 导入ByteArrayOutputStream类用于将数据写入字节数组
import java.io.IOException;
// 导入IOException类用于处理输入输出异常
import java.io.InputStream;
// 导入InputStream类用于处理输入流
import java.io.OutputStream;
// 导入OutputStream类用于处理输出流
import java.io.PrintStream;
// 导入PrintStream类用于处理带格式的输出流
import java.nio.ByteBuffer;
// 导入ByteBuffer类用于操作字节缓冲区
public abstract class CharacterEncoder {
// 定义一个抽象类CharacterEncoder用于字符编码
protected PrintStream pStream;
// 声明PrintStream对象pStream用于处理输出流
public CharacterEncoder() {
// 构造方法
}
// 抽象方法,返回每个原子(数据块)字节数
protected abstract int bytesPerAtom();
// 抽象方法,返回每行字节数
protected abstract int bytesPerLine();
// 编码缓冲区前缀,设置输出流
protected void encodeBufferPrefix(OutputStream var1) throws IOException {
this.pStream = new PrintStream(var1);
// 使用PrintStream包装输出流
}
// 编码缓冲区后缀,空实现,子类可以重写
protected void encodeBufferSuffix(OutputStream var1) throws IOException {
}
// 编码行前缀,空实现,子类可以重写
protected void encodeLinePrefix(OutputStream var1, int var2) throws IOException {
}
// 编码行后缀,打印换行符
protected void encodeLineSuffix(OutputStream var1) throws IOException {
this.pStream.println(); // 输出换行符
this.pStream.println();
}
// 抽象方法,编码原子(数据块)
protected abstract void encodeAtom(OutputStream var1, byte[] var2, int var3, int var4) throws IOException;
// 从输入流读取数据到字节数组
protected int readFully(InputStream var1, byte[] var2) throws IOException {
for (int var3 = 0; var3 < var2.length; ++var3) {
// 循环读取字节
for(int var3 = 0; var3 < var2.length; ++var3) {
int var4 = var1.read();
// 从输入流读取一个字节
if (var4 == -1) {
// 如果读取到流末尾
return var3;
// 返回已经读取的字节数
}
var2[var3] = (byte) var4;
// 存储读取的字节
var2[var3] = (byte)var4;
}
return var2.length;
// 返回读取的字节数
}
// 编码输入流数据并将结果写入输出流
public void encode(InputStream var1, OutputStream var2) throws IOException {
byte[] var5 = new byte[this.bytesPerLine()];
// 创建字节数组用于存储每行数据
this.encodeBufferPrefix(var2);
// 编码缓冲区前缀
while (true) {
// 不断读取并编码数据直到流结束
while(true) {
int var4 = this.readFully(var1, var5);
// 从输入流中读取数据
if (var4 == 0) {
// 如果没有读取到数据
break;
// 结束编码
}
this.encodeLinePrefix(var2, var4);
// 编码行前缀
for (int var3 = 0; var3 < var4; var3 += this.bytesPerAtom()) {
// 遍历每个原子(数据块)
for(int var3 = 0; var3 < var4; var3 += this.bytesPerAtom()) {
if (var3 + this.bytesPerAtom() <= var4) {
// 如果当前原子不超过剩余字节数
this.encodeAtom(var2, var5, var3, this.bytesPerAtom());
// 编码一个原子
} else {
// 如果当前原子超过剩余字节数
this.encodeAtom(var2, var5, var3, var4 - var3);
// 编码剩余的字节
}
}
if (var4 < this.bytesPerLine()) {
// 如果当前行数据不足一行
break;
// 结束编码
}
this.encodeLineSuffix(var2);
// 编码行后缀
}
this.encodeBufferSuffix(var2);
// 编码缓冲区后缀
}
// 编码字节数组并将结果写入输出流
public void encode(byte[] var1, OutputStream var2) throws IOException {
ByteArrayInputStream var3 = new ByteArrayInputStream(var1);
// 创建字节数组输入流
this.encode((InputStream) var3, var2);
// 调用encode方法进行编码
this.encode((InputStream)var3, var2);
}
// 编码字节数组并返回编码后的字符串
public String encode(byte[] var1) {
ByteArrayOutputStream var2 = new ByteArrayOutputStream();
// 创建字节数组输出流
ByteArrayInputStream var3 = new ByteArrayInputStream(var1);
// 创建字节数组输入流
String var4 = null;
// 初始化编码结果字符串
try {
this.encode((InputStream) var3, var2);
// 调用encode方法进行编码
this.encode((InputStream)var3, var2);
var4 = var2.toString("8859_1");
// 将输出流内容转换为字符串
return var4;
// 返回编码结果
} catch (Exception var6) {
// 捕获异常
throw new Error("CharacterEncoder.encode internal error");
// 抛出错误
}
}
// 从ByteBuffer获取字节数组
private byte[] getBytes(ByteBuffer var1) {
byte[] var2 = null;
// 初始化字节数组
if (var1.hasArray()) {
// 如果ByteBuffer内部有数组
byte[] var3 = var1.array(); // 获取数组
byte[] var3 = var1.array();
if (var3.length == var1.capacity() && var3.length == var1.remaining()) {
// 如果数组长度符合要求
var2 = var3;
// 直接使用该数组
var1.position(var1.limit());
// 更新ByteBuffer位置
}
}
if (var2 == null) {
// 如果没有直接可用的数组
var2 = new byte[var1.remaining()];
// 创建新数组
var1.get(var2);
// 将ByteBuffer中的内容复制到新数组
}
return var2;
// 返回字节数组
}
// 编码ByteBuffer数据并将结果写入输出流
public void encode(ByteBuffer var1, OutputStream var2) throws IOException {
byte[] var3 = this.getBytes(var1);
// 获取ByteBuffer中的字节数组
this.encode(var3, var2);
// 调用encode方法进行编码
}
// 编码ByteBuffer数据并返回编码后的字符串
public String encode(ByteBuffer var1) {
byte[] var2 = this.getBytes(var1);
// 获取ByteBuffer中的字节数组
return this.encode(var2);
// 调用encode方法进行编码并返回结果
}
// 编码整个输入流缓冲区并将结果写入输出流
public void encodeBuffer(InputStream var1, OutputStream var2) throws IOException {
byte[] var5 = new byte[this.bytesPerLine()];
// 创建字节数组用于存储每行数据
this.encodeBufferPrefix(var2);
// 编码缓冲区前缀
int var4;
do {
var4 = this.readFully(var1, var5);
// 从输入流中读取数据
if (var4 == 0) {
// 如果没有读取到数据
break;
// 结束编码
}
this.encodeLinePrefix(var2, var4);
// 编码行前缀
for (int var3 = 0; var3 < var4; var3 += this.bytesPerAtom()) {
// 遍历每个原子(数据块)
for(int var3 = 0; var3 < var4; var3 += this.bytesPerAtom()) {
if (var3 + this.bytesPerAtom() <= var4) {
// 如果当前原子不超过剩余字节数
this.encodeAtom(var2, var5, var3, this.bytesPerAtom());
// 编码一个原子
} else {
// 如果当前原子超过剩余字节数
this.encodeAtom(var2, var5, var3, var4 - var3);
// 编码剩余的字节
}
}
this.encodeLineSuffix(var2);
// 编码行后缀
} while (var4 >= this.bytesPerLine());
// 如果当前行数据不够一行,则结束
} while(var4 >= this.bytesPerLine());
this.encodeBufferSuffix(var2);
// 编码缓冲区后缀
}
// 编码字节数组并将结果写入输出流
public void encodeBuffer(byte[] var1, OutputStream var2) throws IOException {
ByteArrayInputStream var3 = new ByteArrayInputStream(var1);
// 创建字节数组输入流
this.encodeBuffer((InputStream) var3, var2);
// 调用encodeBuffer方法进行编码
this.encodeBuffer((InputStream)var3, var2);
}
// 编码字节数组并返回编码后的字符串
public String encodeBuffer(byte[] var1) {
ByteArrayOutputStream var2 = new ByteArrayOutputStream();
// 创建字节数组输出流
ByteArrayInputStream var3 = new ByteArrayInputStream(var1);
// 创建字节数组输入流
try {
this.encodeBuffer((InputStream) var3, var2);
// 调用encodeBuffer方法进行编码
this.encodeBuffer((InputStream)var3, var2);
} catch (Exception var5) {
// 捕获异常
throw new Error("CharacterEncoder.encodeBuffer internal error");
// 抛出错误
}
return var2.toString();
// 返回编码后的字符串
}
// 编码ByteBuffer并将结果写入输出流
public void encodeBuffer(ByteBuffer var1, OutputStream var2) throws IOException {
byte[] var3 = this.getBytes(var1);
// 获取ByteBuffer中的字节数组
this.encodeBuffer(var3, var2);
// 调用encodeBuffer方法进行编码
}
// 编码ByteBuffer并返回编码后的字符串
public String encodeBuffer(ByteBuffer var1) {
byte[] var2 = this.getBytes(var1);
// 获取ByteBuffer中的字节数组
return this.encodeBuffer(var2);
// 调用encodeBuffer方法进行编码并返回结果
}
}

@ -1,26 +1,16 @@
//Copyright (c) 2017. 章钦豪. All rights reserved.
// 版权声明,注明该代码的版权所有者
package com.monke.monkeybook.view;
// 定义该接口所在的包
import com.monke.basemvplib.IView;
// 导入IView接口IView是基类接口所有视图接口都需要实现它
// 定义IBookDetailView接口继承自IView接口表示书籍详情页面的视图
public interface IBookDetailView extends IView {
public interface IBookDetailView extends IView{
/**
* UI
*
*/
void updateView();
// 定义一个抽象方法用于更新书籍详情的UI界面
/**
*
*
*/
void getBookShelfError();
// 定义一个抽象方法,用于处理获取书架数据时发生的错误
}

@ -1,85 +1,45 @@
//Copyright (c) 2017. 章钦豪. All rights reserved. // 版权声明,标明代码的版权所有者及年份
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.view;
// 定义该接口所在的包
import android.graphics.Paint;
// 导入Paint类用于图形绘制
import com.monke.basemvplib.IView;
// 导入IView接口IView是一个通用视图接口所有视图接口都需要实现它
// 定义IBookReadView接口继承自IView接口表示小说阅读页面的视图
public interface IBookReadView extends IView {
public interface IBookReadView extends IView{
/**
* UI
* @return
* @return
*/
Paint getPaint();
// 定义一个抽象方法,返回用于绘制文本的画笔对象
/**
*
* @return
* @return
*/
int getContentWidth();
// 定义一个抽象方法,返回小说内容区域的宽度
/**
*
* @param durChapterIndex
* @param chapterAll
* @param durPageIndex
* @param durChapterIndex
* @param chapterAll
* @param durPageIndex
*/
void initContentSuccess(int durChapterIndex, int chapterAll, int durPageIndex); // 定义一个方法,初始化小说内容成功时调用,传递当前章节、总章节和当前页码信息
void initContentSuccess(int durChapterIndex, int chapterAll, int durPageIndex);
/**
*
*
*
*/
void startLoadingBook();
// 定义一个方法开始加载小说时调用用于显示加载过程中的UI状态
/**
*
* @param count
*/
void setHpbReadProgressMax(int count);
// 定义一个方法,用于设置小说阅读进度条的最大值
/**
*
*
*/
void initPop();
// 定义一个方法初始化弹出框相关UI操作
/**
*
*
*/
void showLoadBook();
// 定义一个方法,用于显示加载小说的过程
/**
*
*
*/
void dimissLoadBook();
// 定义一个方法,用于取消加载小说页面的显示
/**
*
*
*/
void loadLocationBookError();
// 定义一个方法,用于加载书籍时发生错误时调用
/**
*
*
*/
void showDownloadMenu();
// 定义一个方法,用于显示下载菜单
}

@ -1,89 +1,30 @@
//Copyright (c) 2017. 章钦豪. All rights reserved.
// 版权声明,标明代码的版权所有者及年份
package com.monke.monkeybook.view;
// 定义该接口所在的包
import com.monke.basemvplib.IView;
// 导入IView接口IView是一个通用的视图接口所有视图接口都需要实现它
import com.monke.monkeybook.bean.SearchBookBean;
// 导入SearchBookBean类用于表示书籍数据
import com.monke.monkeybook.view.adapter.ChoiceBookAdapter;
// 导入ChoiceBookAdapter类用于书籍列表的适配器
import java.util.List;
// 导入List类表示书籍列表的数据类型
// 定义IChoiceBookView接口继承自IView接口表示选择书籍界面的视图
public interface IChoiceBookView extends IView {
public interface IChoiceBookView extends IView{
/**
*
* @param books
*/
void refreshSearchBook(List<SearchBookBean> books);
// 定义方法,刷新搜索到的书籍列表,传入书籍列表
/**
*
* @param books
*/
void loadMoreSearchBook(List<SearchBookBean> books);
// 定义方法,用于加载更多书籍,传入更多的书籍列表
/**
*
* @param isAll
*/
void refreshFinish(Boolean isAll);
// 定义方法,表示刷新操作完成,传入是否已加载完所有数据
/**
*
* @param isAll
*/
void loadMoreFinish(Boolean isAll);
// 定义方法,表示加载更多操作完成,传入是否已加载完所有数据
/**
*
*/
void searchBookError();
// 定义方法,表示在搜索书籍时发生错误
/**
*
* @param searchBooks
*/
void addBookShelfSuccess(List<SearchBookBean> searchBooks);
// 定义方法,表示成功将书籍添加到书架
/**
*
* @param code
*/
void addBookShelfFailed(int code);
// 定义方法,表示将书籍添加到书架失败,并返回错误代码
/**
*
* @return ChoiceBookAdapter
*/
ChoiceBookAdapter getSearchBookAdapter();
// 定义方法返回ChoiceBookAdapter实例用于获取书籍列表适配器
/**
*
* @param index
*/
void updateSearchItem(int index);
// 定义方法,用于更新搜索书籍列表中的某一项,传入该项的索引
/**
*
*/
void startRefreshAnim();
// 定义方法,用于启动刷新动画,通常用于界面显示加载动画
}

@ -1,40 +1,29 @@
//Copyright (c) 2017. 章钦豪. All rights reserved.
// 版权声明,标明代码的版权所有者及年份
package com.monke.monkeybook.view;
// 定义该接口所在的包
import com.monke.basemvplib.IView;
// 导入IView接口所有视图接口都需要实现它
import java.io.File;
// 导入File类用于处理文件操作
// 定义IImportBookView接口继承自IView接口表示导入书籍界面的视图
public interface IImportBookView extends IView {
public interface IImportBookView extends IView{
/**
*
* @param newFile
* @param newFile
*/
void addNewBook(File newFile);
// 定义方法,用于将一个新书籍文件添加到系统
/**
*
*/
void searchFinish();
// 定义方法,表示书籍的搜索操作已完成
/**
*
*
*/
void addSuccess();
// 定义方法,表示书籍添加操作成功
/**
*
*
*/
void addError();
// 定义方法,表示书籍添加操作失败
}
}

@ -1,27 +1,19 @@
//Copyright (c) 2017. 章钦豪. All rights reserved.
// 版权声明,标明代码的版权所有者及年份
package com.monke.monkeybook.view;
// 定义该接口所在的包
import com.monke.basemvplib.IView;
// 导入IView接口所有视图接口都需要实现它
import com.monke.monkeybook.bean.LibraryBean;
// 导入LibraryBean类表示书城中的书籍数据模型
// 定义ILibraryView接口继承自IView接口表示书城页面的视图
public interface ILibraryView extends IView {
public interface ILibraryView extends IView{
/**
* UI
* @param library LibraryBean
* UI
* @param library
*/
void updateUI(LibraryBean library);
// 定义方法表示书城书籍数据获取成功并更新UI传入一个LibraryBean对象
/**
* UI
* UI
*/
void finishRefresh();
// 定义方法表示书城数据刷新成功并更新UI
}

@ -1,52 +1,43 @@
//Copyright (c) 2017. 章钦豪. All rights reserved. // 版权声明,标明代码的版权所有者及年份
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.view;
package com.monke.monkeybook.view; // 定义该接口所在的包
import com.monke.basemvplib.IView; // 导入IView接口所有视图接口都需要实现它
import com.monke.basemvplib.IView;
import com.monke.monkeybook.bean.BookShelfBean;
// 导入BookShelfBean类表示书架上的书籍数据模型
import java.util.List;
// 导入List类用于存储多个BookShelfBean对象
// 定义IMainView接口继承自IView接口表示书架页面的视图
public interface IMainView extends IView {
public interface IMainView extends IView{
/**
* UI
* @param bookShelfBeanList
* UI
* @param bookShelfBeanList
*/
void refreshBookShelf(List<BookShelfBean> bookShelfBeanList);
// 定义方法传入书架数据更新UI界面显示书架中的书籍信息
/**
*
*/
void activityRefreshView();
// 定义方法,表示触发刷新书架小说信息操作
/**
*
*/
void refreshFinish();
// 定义方法,表示刷新操作完成
/**
*
* @param error
* @param error
*/
void refreshError(String error);
// 定义方法,表示刷新操作发生错误,传入错误信息
/**
* UI
* UI
*/
void refreshRecyclerViewItemAdd();
// 定义方法表示刷新操作时更新UI进度如RecyclerView的进度
/**
*
* @param x
* @param x
*/
void setRecyclerMaxProgress(int x); // 定义方法,设置刷新进度条的最大值
void setRecyclerMaxProgress(int x);
}

@ -1,111 +1,77 @@
//Copyright (c) 2017. 章钦豪. All rights reserved. // 版权声明,标明代码的版权所有者及年份
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.view;
// 定义该接口所在的包
import android.widget.EditText;
// 导入EditText类用于获取搜索框的输入内容
import com.monke.basemvplib.IView;
// 导入IView接口所有视图接口都需要实现它
import com.monke.monkeybook.bean.SearchBookBean;
// 导入SearchBookBean类表示搜索到的书籍数据模型
import com.monke.monkeybook.bean.SearchHistoryBean;
// 导入SearchHistoryBean类表示搜索历史记录的数据模型
import com.monke.monkeybook.view.adapter.SearchBookAdapter;
// 导入SearchBookAdapter类适配器用于显示搜索书籍的列表
import java.util.List;
// 导入List类用于存储多个SearchBookBean对象或SearchHistoryBean对象
// 定义ISearchView接口继承自IView接口表示搜索页面的视图
public interface ISearchView extends IView {
public interface ISearchView extends IView{
/**
*
* @param searchHistoryBean
*
* @param searchHistoryBean
*/
void insertSearchHistorySuccess(SearchHistoryBean searchHistoryBean);
// 定义方法,表示成功添加一条新的搜索历史记录
/**
*
* @param datas
*
* @param datas
*/
void querySearchHistorySuccess(List<SearchHistoryBean> datas);
// 定义方法,表示成功查询到搜索历史记录
/**
* UI
* @param books
* UI
* @param books
*/
void refreshSearchBook(List<SearchBookBean> books);
// 定义方法表示首次搜索成功后更新UI显示搜索到的书籍
/**
* UI
* @param books
* UI
* @param books
*/
void loadMoreSearchBook(List<SearchBookBean> books);
// 定义方法表示成功加载更多书籍并更新UI显示
/**
*
* @param isAll
*
* @param isAll
*/
void refreshFinish(Boolean isAll);
// 定义方法,表示刷新操作完成,并传入是否已经加载所有数据的状态
/**
*
* @param isAll
*
* @param isAll
*/
void loadMoreFinish(Boolean isAll);
// 定义方法,表示加载更多操作完成,并传入是否已经加载所有数据的状态
/**
*
* @param isRefresh
* @param isRefresh
*/
void searchBookError(Boolean isRefresh);
// 定义方法,表示搜索失败,传入是否是刷新操作
/**
* EditText
* @return EditText
* EditText
* @return
*/
EditText getEdtContent();
// 定义方法返回搜索框的EditText组件供其他逻辑使用
/**
*
* @param code
* @param code
*/
void addBookShelfFailed(int code);
// 定义方法,表示添加书籍到书架失败,传入错误码
/**
* SearchBookAdapter
* @return SearchBookAdapter
*/
SearchBookAdapter getSearchBookAdapter();
// 定义方法,返回用于显示搜索书籍的适配器
/**
*
* @param index
*/
void updateSearchItem(int index);
// 定义方法,表示更新指定索引的搜索项
/**
*
* @param searchBookBean
* @return Boolean truefalse
* @param searchBookBean
* @return
*/
Boolean checkIsExist(SearchBookBean searchBookBean);
// 定义方法,检查给定的书籍是否已经在书架上
}

@ -1,182 +1,107 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.view.adapter; // 声明包名,适配器所在的包
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.view.adapter;
import android.os.Handler;
// 导入Handler类处理异步任务
import android.support.v7.widget.RecyclerView;
// 导入RecyclerView类展示列表的控件
import android.view.LayoutInflater;
// 导入LayoutInflater类加载布局文件
import android.view.View;
// 导入View类视图的基类
import android.view.ViewGroup;
// 导入ViewGroup类视图组的基类
import android.view.animation.Animation;
// 导入Animation类动画效果
import android.view.animation.AnimationUtils;
// 导入AnimationUtils类动画加载工具
import android.widget.FrameLayout;
// 导入FrameLayout布局布局控件
import android.widget.ImageButton;
// 导入ImageButton控件带图片的按钮
import android.widget.ImageView;
// 导入ImageView控件用于显示图片
import android.widget.LinearLayout;
// 导入LinearLayout布局线性布局控件
import android.widget.TextView;
// 导入TextView控件用于显示文本
import com.bumptech.glide.Glide;
// 导入Glide库用于加载图片
import com.bumptech.glide.load.engine.DiskCacheStrategy;
// 导入Glide的缓存策略
import com.monke.monkeybook.R;
// 导入资源文件,包含布局、字符串等资源
import com.monke.monkeybook.bean.BookShelfBean;
// 导入书架数据模型类
import com.monke.monkeybook.widget.refreshview.RefreshRecyclerViewAdapter;
// 导入刷新功能的RecyclerView适配器
import com.monke.mprogressbar.MHorProgressBar;
// 导入进度条类
import com.monke.mprogressbar.OnProgressListener;
// 导入进度条的监听器
import java.util.ArrayList;
// 导入ArrayList类动态数组
import java.util.List;
//导入List接口集合类型
import me.grantland.widget.AutofitTextView;
// 导入自动调整文本大小的TextView控件
// 书架适配器类继承自RefreshRecyclerViewAdapter提供书架数据的绑定与展示
public class BookShelfAdapter extends RefreshRecyclerViewAdapter {
private final int TYPE_LASTEST = 1;
// 最新阅读类型
private final int TYPE_OTHER = 2;
// 其他书籍类型
private final long DURANIMITEM = 130;
// 每个item动画启动的间隔时间
private final long DURANIMITEM = 130; //item动画启动间隔
private List<BookShelfBean> books;
// 书架数据的集合
private Boolean needAnim = true;
// 是否需要动画
private OnItemClickListener itemClickListener;
// 条目点击监听器接口
// 定义条目点击事件接口
public interface OnItemClickListener {
void toSearch(); // 跳转到搜索界面
void toSearch();
void onClick(BookShelfBean bookShelfBean, int index);
// 点击书籍条目时的回调方法
void onLongClick(View view, BookShelfBean bookShelfBean, int index);
// 长按书籍条目时的回调方法
}
// 构造函数,初始化书架数据
public BookShelfAdapter() {
super(false);
// 调用父类构造函数,禁用下拉刷新
books = new ArrayList<>();
// 初始化书籍集合
}
// 获取数据项数量,处理每三项作为一组
@Override
public int getItemcount() {
if (books.size() == 0) {
return 1;
// 如果书架没有书籍返回1项显示"空书架"提示
} else {
if (books.size() % 3 == 0) {
return 1 + books.size() / 3;
// 每3个书籍一组
} else {
return 1 + (books.size() / 3 + 1);
// 每3个书籍一组最后一组可能不足3个
}
}
}
// 获取真实的数据项数量(去除提示项)
public int getRealItemCount() {
return books.size();
}
// 根据位置返回对应的view类型最新阅读和其他书籍
@Override
public int getItemViewtype(int position) {
if (position == 0) {
return TYPE_LASTEST;
// 第一项为最新阅读项
} else {
return TYPE_OTHER;
// 其他为普通书籍项
}
}
// 创建对应viewHolder
@Override
public RecyclerView.ViewHolder onCreateViewholder(ViewGroup parent, int viewType) {
if (viewType == TYPE_LASTEST) {
return new LastestViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_bookshelf_lastest, parent, false));
// 最新阅读视图
} else {
return new OtherViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_bookshelf_other, parent, false));
// 其他书籍视图
}
}
// 绑定数据到视图
@Override
public void onBindViewholder(RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == TYPE_LASTEST) {
bindLastestViewHolder((LastestViewHolder) holder, position);
// 绑定最新阅读视图
} else {
bindOtherViewHolder((OtherViewHolder) holder, position - 1);
// 绑定其他书籍视图
}
}
// 绑定其他书籍视图
private void bindOtherViewHolder(final OtherViewHolder holder, int index) {
final int index_1 = index * 3;
// 计算第一个书籍的位置
if (needAnim) {
final Animation animation = AnimationUtils.loadAnimation(holder.flContent_1.getContext(), R.anim.anim_bookshelf_item); // 加载动画
final Animation animation = AnimationUtils.loadAnimation(holder.flContent_1.getContext(), R.anim.anim_bookshelf_item);
animation.setAnimationListener(new AnimatontStartListener() {
@Override
void onAnimStart(Animation animation) {
needAnim = false;
holder.flContent_1.setVisibility(View.VISIBLE);
// 动画开始时显示内容
}
});
new Handler().postDelayed(new Runnable() {
@ -184,24 +109,19 @@ public class BookShelfAdapter extends RefreshRecyclerViewAdapter {
public void run() {
if (null != holder)
holder.flContent_1.startAnimation(animation);
// 延迟启动动画
}
}, index_1 * DURANIMITEM);
} else {
holder.flContent_1.setVisibility(View.VISIBLE);
// 不需要动画时直接显示
}
Glide.with(holder.ivCover_1.getContext()).load(books.get(index_1).getBookInfoBean().getCoverUrl()).dontAnimate().diskCacheStrategy(DiskCacheStrategy.RESULT).centerCrop().placeholder(R.drawable.img_cover_default).into(holder.ivCover_1); // 使用Glide加载书籍封面图片
Glide.with(holder.ivCover_1.getContext()).load(books.get(index_1).getBookInfoBean().getCoverUrl()).dontAnimate().diskCacheStrategy(DiskCacheStrategy.RESULT).centerCrop().placeholder(R.drawable.img_cover_default).into(holder.ivCover_1);
holder.tvName_1.setText(books.get(index_1).getBookInfoBean().getName());
// 设置书籍名称
// 设置点击和长按事件
holder.ibContent_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (itemClickListener != null)
itemClickListener.onClick(books.get(index_1), index_1);
// 调用点击事件
}
});
holder.ibContent_1.setOnLongClickListener(new View.OnLongClickListener() {
@ -209,17 +129,106 @@ public class BookShelfAdapter extends RefreshRecyclerViewAdapter {
public boolean onLongClick(View v) {
if (itemClickListener != null) {
itemClickListener.onLongClick(holder.ivCover_1, books.get(index_1), index_1);
// 调用长按事件
return true;
} else
return false;
}
});
// 绑定第二、第三本书籍(与第一本类似,略)
final int index_2 = index_1 + 1;
if (index_2 < books.size()) {
if (needAnim) {
final Animation animation = AnimationUtils.loadAnimation(holder.flContent_2.getContext(), R.anim.anim_bookshelf_item);
animation.setAnimationListener(new AnimatontStartListener() {
@Override
void onAnimStart(Animation animation) {
needAnim = false;
holder.flContent_2.setVisibility(View.VISIBLE);
}
});
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (null != holder)
holder.flContent_2.startAnimation(animation);
}
}, index_2 * DURANIMITEM);
} else {
holder.flContent_2.setVisibility(View.VISIBLE);
}
Glide.with(holder.ivCover_2.getContext()).load(books.get(index_2).getBookInfoBean().getCoverUrl()).dontAnimate().diskCacheStrategy(DiskCacheStrategy.RESULT).centerCrop().placeholder(R.drawable.img_cover_default).into(holder.ivCover_2);
holder.tvName_2.setText(books.get(index_2).getBookInfoBean().getName());
holder.ibContent_2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (itemClickListener != null)
itemClickListener.onClick(books.get(index_2), index_2);
}
});
holder.ibContent_2.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (itemClickListener != null) {
if (itemClickListener != null)
itemClickListener.onLongClick(holder.ivCover_2, books.get(index_2), index_2);
return true;
} else
return false;
}
});
final int index_3 = index_2 + 1;
if (index_3 < books.size()) {
if (needAnim) {
final Animation animation = AnimationUtils.loadAnimation(holder.flContent_3.getContext(), R.anim.anim_bookshelf_item);
animation.setAnimationListener(new AnimatontStartListener() {
@Override
void onAnimStart(Animation animation) {
needAnim = false;
holder.flContent_3.setVisibility(View.VISIBLE);
}
});
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (null != holder)
holder.flContent_3.startAnimation(animation);
}
}, index_3 * DURANIMITEM);
} else {
holder.flContent_3.setVisibility(View.VISIBLE);
}
Glide.with(holder.ivCover_3.getContext()).load(books.get(index_3).getBookInfoBean().getCoverUrl()).dontAnimate().diskCacheStrategy(DiskCacheStrategy.RESULT).centerCrop().placeholder(R.drawable.img_cover_default).into(holder.ivCover_3);
holder.tvName_3.setText(books.get(index_3).getBookInfoBean().getName());
holder.ibContent_3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (itemClickListener != null)
itemClickListener.onClick(books.get(index_3), index_3);
}
});
holder.ibContent_3.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (itemClickListener != null) {
if (itemClickListener != null)
itemClickListener.onLongClick(holder.ivCover_3, books.get(index_3), index_3);
return true;
} else
return false;
}
});
}else{
holder.flContent_3.setVisibility(View.INVISIBLE);
}
}else{
holder.flContent_2.setVisibility(View.INVISIBLE);
holder.flContent_3.setVisibility(View.INVISIBLE);
}
}
// 绑定最新阅读视图
private void bindLastestViewHolder(final LastestViewHolder holder, final int index) {
if (books.size() == 0) {
holder.tvWatch.setOnClickListener(new View.OnClickListener() {
@ -227,210 +236,170 @@ public class BookShelfAdapter extends RefreshRecyclerViewAdapter {
public void onClick(View v) {
if (null != itemClickListener) {
itemClickListener.toSearch();
// 调用跳转到搜索界面
}
}
});
holder.ivCover.setImageResource(R.drawable.img_cover_default);
// 默认封面
holder.flLastestTip.setVisibility(View.INVISIBLE);
// 隐藏提示
holder.tvName.setText("最近阅读的书在这里");
// 提示文字
holder.tvDurprogress.setText("");
// 进度文本为空
holder.llDurcursor.setVisibility(View.INVISIBLE);
// 隐藏进度条
holder.mpbDurprogress.setVisibility(View.INVISIBLE);
// 隐藏进度条
holder.mpbDurprogress.setProgressListener(null);
// 清除进度监听器
holder.tvWatch.setText("去选书"); // 显示"去选书"按钮
holder.tvWatch.setText("去选书");
} else {
Glide.with(holder.ivCover.getContext()).load(books.get(index).getBookInfoBean().getCoverUrl()).dontAnimate().diskCacheStrategy(DiskCacheStrategy.RESULT).centerCrop().placeholder(R.drawable.img_cover_default).into(holder.ivCover); // 加载最新阅读封面
holder.flLastestTip.setVisibility(View.VISIBLE); // 显示最新阅读提示
holder.tvName.setText(String.format(holder.tvName.getContext().getString(R.string.tv_book_name), books.get(index).getBookInfoBean().getName())); // 显示书名
// 显示章节进度和其他信息
Glide.with(holder.ivCover.getContext()).load(books.get(index).getBookInfoBean().getCoverUrl()).dontAnimate().diskCacheStrategy(DiskCacheStrategy.RESULT).centerCrop().placeholder(R.drawable.img_cover_default).into(holder.ivCover);
holder.flLastestTip.setVisibility(View.VISIBLE);
holder.tvName.setText(String.format(holder.tvName.getContext().getString(R.string.tv_book_name), books.get(index).getBookInfoBean().getName()));
if (null != books.get(index).getBookInfoBean() && null != books.get(index).getBookInfoBean().getChapterlist() && books.get(index).getBookInfoBean().getChapterlist().size() > books.get(index).getDurChapter()) {
holder.tvDurprogress.setText(String.format(holder.tvDurprogress.getContext().getString(R.string.tv_read_durprogress), books.get(index).getBookInfoBean().getChapterlist().get(books.get(index).getDurChapter()).getDurChapterName()));
}
holder.llDurcursor.setVisibility(View.VISIBLE);
holder.mpbDurprogress.setVisibility(View.VISIBLE);
holder.mpbDurprogress.setMaxProgress(books.get(index).getBookInfoBean().getChapterlist().size());
float speed = books.get(index).getBookInfoBean().getChapterlist().size()*1.0f/100;
holder.mpbDurprogress.setSpeed(speed<=0?1:speed);
holder.mpbDurprogress.setProgressListener(new OnProgressListener() {
@Override
public void moveStartProgress(float dur) {
}
@Override
public void durProgressChange(float dur) {
float rate = dur / holder.mpbDurprogress.getMaxProgress();
holder.llDurcursor.setPadding((int) (holder.mpbDurprogress.getMeasuredWidth() * rate), 0, 0, 0);
}
@Override
public void moveStopProgress(float dur) {
}
@Override
public void setDurProgress(float dur) {
}
});
if (needAnim) {
holder.mpbDurprogress.setDurProgressWithAnim(books.get(index).getDurChapter());
} else {
holder.mpbDurprogress.setDurProgress(books.get(index).getDurChapter());
}
holder.tvWatch.setText("继续阅读");
holder.tvWatch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != itemClickListener) {
itemClickListener.onClick(books.get(index), index);
}
}
});
}
}
// 设置点击事件监听器
public void setItemClickListener(OnItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
// 获取是否需要动画的状态
public Boolean getNeedAnim() {
return needAnim;
}
// 设置是否需要动画
public void setNeedAnim(Boolean needAnim) {
this.needAnim = needAnim;
}
// 最新阅读视图的ViewHolder
class LastestViewHolder extends RecyclerView.ViewHolder {
ImageView ivCover;
// 书籍封面
FrameLayout flLastestTip;
// 最新阅读提示
AutofitTextView tvName;
// 书籍名称
AutofitTextView tvDurprogress;
// 阅读进度
LinearLayout llDurcursor;
// 进度条的滑动控件
MHorProgressBar mpbDurprogress;
// 水平进度条
TextView tvWatch;
// 操作按钮
public LastestViewHolder(View itemView) {
super(itemView);
ivCover = itemView.findViewById(R.id.iv_cover);
// 获取封面控件
flLastestTip = itemView.findViewById(R.id.fl_lastest_tip);
// 获取最新提示控件
tvName = itemView.findViewById(R.id.tv_name);
// 获取书名控件
tvDurprogress = itemView.findViewById(R.id.tv_durprogress);
// 获取进度文本控件
llDurcursor = itemView.findViewById(R.id.ll_durcursor);
// 获取进度条控件
mpbDurprogress = itemView.findViewById(R.id.mpb_durprogress);
// 获取进度条控件
tvWatch = itemView.findViewById(R.id.tv_watch);
// 获取操作按钮控件
ivCover = (ImageView) itemView.findViewById(R.id.iv_cover);
flLastestTip = (FrameLayout) itemView.findViewById(R.id.fl_lastest_tip);
tvName = (AutofitTextView) itemView.findViewById(R.id.tv_name);
tvDurprogress = (AutofitTextView) itemView.findViewById(R.id.tv_durprogress);
llDurcursor = (LinearLayout) itemView.findViewById(R.id.ll_durcursor);
mpbDurprogress = (MHorProgressBar) itemView.findViewById(R.id.mpb_durprogress);
tvWatch = (TextView) itemView.findViewById(R.id.tv_watch);
}
}
// 其他书籍视图的ViewHolder
class OtherViewHolder extends RecyclerView.ViewHolder {
FrameLayout flContent_1;
// 第一个书籍的容器
ImageView ivCover_1;
// 第一个书籍封面
AutofitTextView tvName_1;
// 第一个书籍名称
ImageButton ibContent_1;
// 第一个书籍按钮
FrameLayout flContent_2;
// 第二个书籍的容器
ImageView ivCover_2;
// 第二个书籍封面
AutofitTextView tvName_2;
// 第二个书籍名称
ImageButton ibContent_2;
// 第二个书籍按钮
FrameLayout flContent_3;
// 第三个书籍的容器
ImageView ivCover_3;
// 第三个书籍封面
AutofitTextView tvName_3;
// 第三个书籍名称
ImageButton ibContent_3;
// 第三个书籍按钮
public OtherViewHolder(View itemView) {
super(itemView);
flContent_1 = itemView.findViewById(R.id.fl_content_1);
// 获取第一个书籍的容器
ivCover_1 = itemView.findViewById(R.id.iv_cover_1);
// 获取第一个书籍的封面
tvName_1 = itemView.findViewById(R.id.tv_name_1);
// 获取第一个书籍的名称
ibContent_1 = itemView.findViewById(R.id.ib_content_1);
// 获取第一个书籍的按钮
flContent_2 = itemView.findViewById(R.id.fl_content_2);
// 获取第二个书籍的容器
ivCover_2 = itemView.findViewById(R.id.iv_cover_2);
// 获取第二个书籍的封面
tvName_2 = itemView.findViewById(R.id.tv_name_2);
// 获取第二个书籍的名称
ibContent_2 = itemView.findViewById(R.id.ib_content_2);
// 获取第二个书籍的按钮
flContent_3 = itemView.findViewById(R.id.fl_content_3);
// 获取第三个书籍的容器
ivCover_3 = itemView.findViewById(R.id.iv_cover_3);
// 获取第三个书籍的封面
tvName_3 = itemView.findViewById(R.id.tv_name_3);
// 获取第三个书籍的名称
ibContent_3 = itemView.findViewById(R.id.ib_content_3);
// 获取第三个书籍的按钮
flContent_1 = (FrameLayout) itemView.findViewById(R.id.fl_content_1);
ivCover_1 = (ImageView) itemView.findViewById(R.id.iv_cover_1);
tvName_1 = (AutofitTextView) itemView.findViewById(R.id.tv_name_1);
ibContent_1 = (ImageButton) itemView.findViewById(R.id.ib_content_1);
flContent_2 = (FrameLayout) itemView.findViewById(R.id.fl_content_2);
ivCover_2 = (ImageView) itemView.findViewById(R.id.iv_cover_2);
tvName_2 = (AutofitTextView) itemView.findViewById(R.id.tv_name_2);
ibContent_2 = (ImageButton) itemView.findViewById(R.id.ib_content_2);
flContent_3 = (FrameLayout) itemView.findViewById(R.id.fl_content_3);
ivCover_3 = (ImageView) itemView.findViewById(R.id.iv_cover_3);
tvName_3 = (AutofitTextView) itemView.findViewById(R.id.tv_name_3);
ibContent_3 = (ImageButton) itemView.findViewById(R.id.ib_content_3);
}
}
// 动画开始监听器
abstract class AnimatontStartListener implements Animation.AnimationListener {
@Override
public void onAnimationStart(Animation animation) {
onAnimStart(animation); // 调用子类实现的动画开始方法
onAnimStart(animation);
}
@Override
public void onAnimationEnd(Animation animation) { }
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) { }
public void onAnimationRepeat(Animation animation) {
abstract void onAnimStart(Animation animation); // 抽象方法,由子类实现动画开始逻辑
}
abstract void onAnimStart(Animation animation);
}
// 替换所有书籍数据并刷新列表
public synchronized void replaceAll(List<BookShelfBean> newDatas) {
books.clear(); // 清空现有数据
books.clear();
if (null != newDatas && newDatas.size() > 0) {
books.addAll(newDatas); // 添加新数据
books.addAll(newDatas);
}
order(); // 对书籍按照最后阅读时间排序
notifyDataSetChanged(); // 通知适配器数据已改变
order();
notifyDataSetChanged();
}
// 排序书籍,根据最终阅读时间降序排列
private void order() {
if (books != null && books.size() > 0) {
for (int i = 0; i < books.size(); i++) {
@ -440,7 +409,6 @@ public class BookShelfAdapter extends RefreshRecyclerViewAdapter {
temp = j;
}
}
// 交换排序
BookShelfBean tempBookShelfBean = books.get(i);
books.set(i, books.get(temp));
books.set(temp, tempBookShelfBean);
@ -448,8 +416,7 @@ public class BookShelfAdapter extends RefreshRecyclerViewAdapter {
}
}
// 获取书架数据
public List<BookShelfBean> getBooks() {
return books;
}
}
}

@ -1,174 +1,92 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.view.adapter;
// 声明该类所在的包,适配器的包名
import android.graphics.Color;
// 导入Color类用于设置颜色
import android.support.annotation.NonNull;
// 导入NonNull注解表示该参数不允许为null
import android.support.v7.widget.RecyclerView;
// 导入RecyclerView类用于实现列表显示
import android.view.LayoutInflater;
// 导入LayoutInflater类用于动态加载布局
import android.view.View;
// 导入View类视图的基类
import android.view.ViewGroup;
// 导入ViewGroup类视图容器的基类
import android.widget.FrameLayout;
// 导入FrameLayout类布局控件作为容器
import android.widget.TextView;
// 导入TextView类显示文本控件
import com.monke.monkeybook.R;
// 导入资源文件,包含布局和样式等资源
import com.monke.monkeybook.bean.BookShelfBean;
// 导入书架数据模型类
import com.monke.monkeybook.widget.ChapterListView;
// 导入ChapterListView显示章节列表控件
// 定义ChapterListAdapter类继承RecyclerView.Adapter用于章节列表的适配
public class ChapterListAdapter extends RecyclerView.Adapter<ChapterListAdapter.Viewholder> {
private BookShelfBean bookShelfBean;
// 书架数据对象
private ChapterListView.OnItemClickListener itemClickListener;
// 章节点击监听器接口
private int index = 0;
// 当前选中的章节索引
private Boolean isAsc = true;
// 是否升序显示章节
// 构造函数,初始化书架数据和点击监听器
public ChapterListAdapter(BookShelfBean bookShelfBean, @NonNull ChapterListView.OnItemClickListener itemClickListener) {
this.bookShelfBean = bookShelfBean;
// 设置书架数据
this.itemClickListener = itemClickListener;
// 设置章节点击监听器
}
// 创建ViewHolder用于加载每个章节的布局
@Override
public Viewholder onCreateViewHolder(ViewGroup parent, int viewType) {
// 加载布局并返回ViewHolder
return new Viewholder(LayoutInflater.from(parent.getContext()).inflate(R.layout.view_adapter_chapterlist, parent, false));
}
// 绑定数据到ViewHolder
@Override
public void onBindViewHolder(Viewholder holder, final int posiTion) {
// 如果是最后一项,隐藏分隔线;否则显示分隔线
if (posiTion == getItemCount() - 1) {
holder.vLine.setVisibility(View.INVISIBLE);
// 隐藏分隔线
} else {
} else
holder.vLine.setVisibility(View.VISIBLE);
// 显示分隔线
}
// 计算实际章节位置,支持升序和降序显示
final int position;
if (isAsc) {
position = posiTion;
// 如果是升序,直接使用当前位置
} else {
position = getItemCount() - 1 - posiTion;
// 如果是降序,倒序显示
}
// 设置章节名称
holder.tvName.setText(bookShelfBean.getBookInfoBean().getChapterlist().get(position).getDurChapterName());
// 设置点击事件,当用户点击章节时
holder.flContent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setIndex(position);
// 设置选中的章节索引
itemClickListener.itemClick(position);
// 调用点击回调
}
});
// 判断当前章节是否为选中的章节
if (position == index) {
holder.flContent.setBackgroundColor(Color.parseColor("#cfcfcf"));
// 设置选中章节的背景色为灰色
holder.flContent.setClickable(false);
// 禁用选中章节的点击
} else {
holder.flContent.setBackgroundResource(R.drawable.bg_ib_pre2);
// 恢复未选中的章节背景
holder.flContent.setClickable(true);
// 允许未选中的章节点击
}
}
// 获取章节总数如果bookShelfBean为null则返回0
@Override
public int getItemCount() {
if (bookShelfBean == null)
return 0;
// 如果书架数据为空返回0
else
return bookShelfBean.getBookInfoBean().getChapterlist().size();
// 返回章节列表的大小
}
// 定义ViewHolder类绑定章节布局控件
public class Viewholder extends RecyclerView.ViewHolder {
private FrameLayout flContent;
// 章节项的容器
private TextView tvName;
// 章节名称的TextView
private View vLine;
// 分隔线视图
// 构造函数,初始化控件
public Viewholder(View itemView) {
super(itemView);
flContent = (FrameLayout) itemView.findViewById(R.id.fl_content);
// 获取章节内容容器
tvName = (TextView) itemView.findViewById(R.id.tv_name);
// 获取章节名称TextView
vLine = itemView.findViewById(R.id.v_line);
// 获取分隔线视图
}
}
// 获取当前选中的章节索引
public int getIndex() {
return index;
}
// 设置当前选中的章节索引并刷新UI
public void setIndex(int index) {
notifyItemChanged(this.index);
// 通知RecyclerView更新之前选中的章节
this.index = index;
// 更新选中的章节索引
notifyItemChanged(this.index);
// 通知RecyclerView更新新选中的章节
}
}

@ -1,344 +1,178 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.view.adapter;
// 声明该类所在的包,适配器的包名
import android.support.v7.widget.RecyclerView;
// 导入RecyclerView类用于实现列表显示
import android.view.LayoutInflater;
// 导入LayoutInflater类用于动态加载布局
import android.view.View;
// 导入View类视图的基类
import android.view.ViewGroup;
// 导入ViewGroup类视图容器的基类
import android.widget.FrameLayout;
// 导入FrameLayout类布局控件作为容器
import android.widget.ImageView;
// 导入ImageView类图片显示控件
import android.widget.TextView;
// 导入TextView类显示文本控件
import com.bumptech.glide.Glide;
// 导入Glide库用于加载图片
import com.bumptech.glide.load.engine.DiskCacheStrategy;
// 导入Glide的缓存策略类
import com.monke.monkeybook.R;
// 导入资源文件,包含布局和样式等资源
import com.monke.monkeybook.bean.SearchBookBean;
// 导入书籍数据模型类
import com.monke.monkeybook.widget.refreshview.RefreshRecyclerViewAdapter;
// 导入自定义的刷新RecyclerView适配器
import java.text.DecimalFormat;
// 导入DecimalFormat类用于格式化数字
import java.util.ArrayList;
// 导入ArrayList类用于存储书籍列表
import java.util.List;
// 导入List接口表示书籍列表
// ChoiceBookAdapter继承自RefreshRecyclerViewAdapter负责展示搜索书籍的适配器
public class ChoiceBookAdapter extends RefreshRecyclerViewAdapter {
private List<SearchBookBean> searchBooks;
// 存储搜索结果的书籍列表
// 定义点击事件的接口
public interface OnItemClickListener {
void clickAddShelf(View clickView, int position, SearchBookBean searchBookBean);
// 添加到书架
void clickItem(View animView, int position, SearchBookBean searchBookBean);
// 点击书籍项
}
private OnItemClickListener itemClickListener;
// 点击事件监听器
// 构造函数,初始化适配器,设置书籍列表为空
public ChoiceBookAdapter() {
super(true);
// 调用父类构造函数,启用刷新功能
searchBooks = new ArrayList<>();
// 初始化书籍列表为空
}
// 创建ViewHolder对象绑定布局
@Override
public RecyclerView.ViewHolder onCreateViewholder(ViewGroup parent, int viewType) {
// 加载单个书籍项的布局并返回ViewHolder
return new Viewholder(LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_searchbook_item, parent, false));
}
// 绑定数据到ViewHolder
@Override
public void onBindViewholder(final RecyclerView.ViewHolder holder, final int position) {
final int realposition = position;
// 保存真实的position值
// 使用Glide加载书籍封面图片
Glide.with(((Viewholder) holder).ivCover.getContext())
.load(searchBooks.get(realposition).getCoverUrl())
// 加载书籍封面URL
.diskCacheStrategy(DiskCacheStrategy.RESULT)
// 设置缓存策略
.fitCenter()
// 图片缩放适应控件
.dontAnimate()
// 禁用动画
.placeholder(R.drawable.img_cover_default)
// 设置加载中的默认图片
.into(((Viewholder) holder).ivCover);
// 设置图片到ImageView
// 设置书籍名称和作者
((Viewholder) holder).tvName.setText(searchBooks.get(realposition).getName());
((Viewholder) holder).tvAuthor.setText(searchBooks.get(realposition).getAuthor());
// 处理书籍的状态(例如是否连载中)
String state = searchBooks.get(position).getState();
if (state == null || state.length() == 0) {
((Viewholder) holder).tvState.setVisibility(View.GONE);
// 如果没有状态,隐藏
} else {
((Viewholder) holder).tvState.setVisibility(View.VISIBLE);
// 否则显示
((Viewholder) holder).tvState.setText(state);
// 设置状态文本
}
// 处理书籍的字数若字数大于10000则显示为万字
long words = searchBooks.get(realposition).getWords();
if (words <= 0) {
((Viewholder) holder).tvWords.setVisibility(View.GONE);
// 如果字数为0或负数隐藏字数
} else {
String wordsS = Long.toString(words) + "字";
// 默认显示字数
if (words > 10000) {
DecimalFormat df = new DecimalFormat("#.#");
// 格式化字数,显示万字
wordsS = df.format(words * 1.0f / 10000f) + "万字";
// 格式化为万字
}
((Viewholder) holder).tvWords.setVisibility(View.VISIBLE);
// 显示字数
((Viewholder) holder).tvWords.setText(wordsS);
// 设置字数文本
}
// 处理书籍类型
String kind = searchBooks.get(realposition).getKind();
if (kind == null || kind.length() <= 0) {
((Viewholder) holder).tvKind.setVisibility(View.GONE);
// 如果没有类型,隐藏
} else {
((Viewholder) holder).tvKind.setVisibility(View.VISIBLE);
// 否则显示
((Viewholder) holder).tvKind.setText(kind);
// 设置类型文本
}
// 处理书籍的最新章节或描述
if (searchBooks.get(realposition).getLastChapter() != null && searchBooks.get(realposition).getLastChapter().length() > 0)
((Viewholder) holder).tvLastest.setText(searchBooks.get(realposition).getLastChapter());
else if (searchBooks.get(realposition).getDesc() != null && searchBooks.get(realposition).getDesc().length() > 0) {
((Viewholder) holder).tvLastest.setText(searchBooks.get(realposition).getDesc());
} else
((Viewholder) holder).tvLastest.setText("");
// 如果没有最新章节或描述,显示空文本
// 处理书籍的来源
if (searchBooks.get(realposition).getOrigin() != null && searchBooks.get(realposition).getOrigin().length() > 0) {
((Viewholder) holder).tvOrigin.setVisibility(View.VISIBLE);
// 如果有来源,显示
((Viewholder) holder).tvOrigin.setText("来源:" + searchBooks.get(realposition).getOrigin());
// 显示来源
} else {
((Viewholder) holder).tvOrigin.setVisibility(View.GONE);
// 如果没有来源,隐藏
}
// 处理书籍是否已添加到书架
if (searchBooks.get(realposition).getAdd()) {
((Viewholder) holder).tvAddShelf.setText("已添加");
// 已添加,显示"已添加"
((Viewholder) holder).tvAddShelf.setEnabled(false);
// 禁用添加按钮
} else {
((Viewholder) holder).tvAddShelf.setText("+添加");
// 未添加,显示"+添加"
((Viewholder) holder).tvAddShelf.setEnabled(true);
// 启用添加按钮
}
// 设置书籍项点击事件
((Viewholder) holder).flContent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (itemClickListener != null)
itemClickListener.clickItem(((Viewholder) holder).ivCover, realposition, searchBooks.get(realposition));
// 调用点击书籍项的回调方法
}
});
// 设置添加书架按钮点击事件
((Viewholder) holder).tvAddShelf.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (itemClickListener != null)
itemClickListener.clickAddShelf(((Viewholder) holder).tvAddShelf, realposition, searchBooks.get(realposition));
// 调用添加到书架的回调方法
}
});
}
// 返回每个item的视图类型当前不使用不同类型的视图
@Override
public int getItemViewtype(int position) {
return 0;
}
// 返回列表中的书籍项数
@Override
public int getItemcount() {
return searchBooks.size(); // 返回书籍列表的大小
return searchBooks.size();
}
// ViewHolder类管理书籍项的视图控件
class Viewholder extends RecyclerView.ViewHolder {
FrameLayout flContent;
// 书籍项的根布局
ImageView ivCover;
// 书籍封面
TextView tvName;
// 书籍名称
TextView tvAuthor;
// 书籍作者
TextView tvState;
// 书籍状态
TextView tvWords;
// 书籍字数
TextView tvKind;
// 书籍类型
TextView tvLastest;
// 书籍最新章节
TextView tvAddShelf;
// 添加书架按钮
TextView tvOrigin;
// 书籍来源
// 构造函数,初始化控件
public Viewholder(View itemView) {
super(itemView);
flContent = (FrameLayout) itemView.findViewById(R.id.fl_content);
// 获取根布局
ivCover = (ImageView) itemView.findViewById(R.id.iv_cover);
// 获取封面ImageView
tvName = (TextView) itemView.findViewById(R.id.tv_name);
// 获取书名TextView
tvAuthor = (TextView) itemView.findViewById(R.id.tv_author);
// 获取作者TextView
tvState = (TextView) itemView.findViewById(R.id.tv_state);
// 获取状态TextView
tvWords = (TextView) itemView.findViewById(R.id.tv_words);
// 获取字数TextView
tvLastest = (TextView) itemView.findViewById(R.id.tv_lastest);
// 获取最新章节TextView
tvAddShelf = (TextView) itemView.findViewById(R.id.tv_addshelf);
// 获取添加书架按钮
tvKind = (TextView) itemView.findViewById(R.id.tv_kind);
// 获取类型TextView
tvOrigin = (TextView) itemView.findViewById(R.id.tv_origin);
// 获取来源TextView
}
}
// 设置点击事件监听器
public void setItemClickListener(OnItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
// 添加新数据到书籍列表
public void addAll(List<SearchBookBean> newData) {
if (newData != null && newData.size() > 0) {
int position = getItemcount();
// 获取当前列表大小
searchBooks.addAll(newData);
// 将新数据添加到列表中
if (newData != null && newData.size() > 0) {
searchBooks.addAll(newData);
}
notifyItemInserted(position);
// 通知RecyclerView插入新项
notifyItemRangeChanged(position, newData.size());
// 通知RecyclerView更新范围内的项
}
}
// 替换整个列表的数据
public void replaceAll(List<SearchBookBean> newData) {
searchBooks.clear();
// 清空现有数据
if (newData != null && newData.size() > 0) {
searchBooks.addAll(newData);
// 将新数据添加到列表中
}
notifyDataSetChanged();
// 通知RecyclerView更新所有项
}
// 获取当前的书籍列表
public List<SearchBookBean> getSearchBooks() {
return searchBooks;
}
}
}

@ -1,243 +1,126 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.view.adapter;
// 声明该类所在的包
import android.os.Environment;
// 导入Environment类访问设备的存储环境
import android.support.annotation.NonNull;
// 导入NonNull注解用于标记非空参数
import android.support.v7.widget.RecyclerView;
// 导入RecyclerView类用于实现列表显示
import android.view.LayoutInflater;
// 导入LayoutInflater类用于动态加载布局
import android.view.View;
// 导入View类视图的基类
import android.view.ViewGroup;
// 导入ViewGroup类视图容器的基类
import android.widget.LinearLayout;
// 导入LinearLayout类布局控件线性布局
import android.widget.TextView;
// 导入TextView类显示文本控件
import com.monke.monkeybook.R;
// 导入资源文件,包含布局和样式等资源
import com.monke.monkeybook.widget.checkbox.SmoothCheckBox;
// 导入自定义的平滑复选框控件
import java.io.File;
// 导入File类表示文件
import java.text.DecimalFormat;
// 导入DecimalFormat类用于格式化数字
import java.util.ArrayList;
// 导入ArrayList类用于存储列表数据
import java.util.List;
// 导入List接口表示列表数据
// ImportBookAdapter继承自RecyclerView.Adapter用于显示导入书籍的适配器
public class ImportBookAdapter extends RecyclerView.Adapter<ImportBookAdapter.Viewholder> {
public class ImportBookAdapter extends RecyclerView.Adapter<ImportBookAdapter.Viewholder>{
private List<File> datas;
// 存储所有的文件数据(导入的书籍文件)
private List<File> selectDatas;
// 存储已选中的文件数据
// 定义一个接口,监听选中文件的变化
public interface OnCheckBookListener {
public interface OnCheckBookListener{
void checkBook(int count);
// 当选中的书籍数量变化时调用
}
private OnCheckBookListener checkBookListener;
// 接口实例,用于回调选中数量变化
// 构造函数初始化适配器并传入OnCheckBookListener
public ImportBookAdapter(@NonNull OnCheckBookListener checkBookListener) {
public ImportBookAdapter(@NonNull OnCheckBookListener checkBookListener){
datas = new ArrayList<>();
// 初始化文件数据列表
selectDatas = new ArrayList<>();
// 初始化选中文件列表
this.checkBookListener = checkBookListener;
// 设置回调监听器
}
// 创建ViewHolder实例并返回
@Override
public Viewholder onCreateViewHolder(ViewGroup parent, int viewType) {
// 加载单个书籍项的布局并返回ViewHolder
return new Viewholder(LayoutInflater.from(parent.getContext()).inflate(R.layout.view_adapter_importbook, parent, false));
return new Viewholder(LayoutInflater.from(parent.getContext()).inflate(R.layout.view_adapter_importbook,parent,false));
}
// 绑定数据到ViewHolder
@Override
public void onBindViewHolder(final Viewholder holder, final int position) {
// 设置书籍名称、大小、存储路径等文本信息
holder.tvNmae.setText(datas.get(position).getName());
// 显示文件名
holder.tvSize.setText(convertByte(datas.get(position).length()));
// 显示文件大小,转换为易读格式
holder.tvLoc.setText(datas.get(position).getAbsolutePath().replace(Environment.getExternalStorageDirectory().getAbsolutePath(), "存储空间"));
// 显示文件路径
holder.tvLoc.setText(datas.get(position).getAbsolutePath().replace(Environment.getExternalStorageDirectory().getAbsolutePath(),"存储空间"));
// 设置复选框状态变更监听
holder.scbSelect.setOnCheckedChangeListener(new SmoothCheckBox.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(SmoothCheckBox checkBox, boolean isChecked) {
// 根据复选框状态更新选中文件列表
if (isChecked) {
if(isChecked){
selectDatas.add(datas.get(position));
// 如果选中,则添加到选中列表
} else {
}else{
selectDatas.remove(datas.get(position));
// 如果取消选中,则从选中列表中移除
}
// 调用监听器,通知选中的书籍数量
checkBookListener.checkBook(selectDatas.size());
}
});
// 控制是否显示复选框和设置点击事件
if (canCheck) {
// 如果允许选择
if(canCheck){
holder.scbSelect.setVisibility(View.VISIBLE);
// 显示复选框
// 设置点击项的点击事件,切换复选框状态
holder.llContent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
holder.scbSelect.setChecked(!holder.scbSelect.isChecked(), true);
// 切换复选框状态
holder.scbSelect.setChecked(!holder.scbSelect.isChecked(),true);
}
});
} else { // 如果不允许选择
}else{
holder.scbSelect.setVisibility(View.INVISIBLE);
// 隐藏复选框
holder.llContent.setOnClickListener(null);
// 取消点击事件
}
}
// 添加新数据到文件列表
public void addData(File newItem) {
public void addData(File newItem){
int position = datas.size();
// 获取当前列表末尾位置
datas.add(newItem);
// 将新项添加到列表中
notifyItemInserted(position);
// 通知RecyclerView插入新项
notifyItemRangeChanged(position, 1);
// 更新新增项
}
private Boolean canCheck = false;
// 控制是否可以选择文件
// 设置是否允许选择文件
public void setCanCheck(Boolean canCheck) {
public void setCanCheck(Boolean canCheck){
this.canCheck = canCheck;
// 更新选择状态
notifyDataSetChanged();
// 通知RecyclerView更新所有项
}
// 返回列表中项的总数
@Override
public int getItemCount() {
return datas.size(); // 返回文件数据的大小
return datas.size();
}
// ViewHolder类管理书籍项的视图控件
class Viewholder extends RecyclerView.ViewHolder {
LinearLayout llContent;
// 根布局,整个书籍项的容器
TextView tvNmae;
// 显示书籍名称的TextView
TextView tvSize;
// 显示文件大小的TextView
TextView tvLoc;
// 显示文件路径的TextView
SmoothCheckBox scbSelect;
// 显示复选框
// 构造函数,初始化控件
public Viewholder(View itemView) {
super(itemView);
llContent = (LinearLayout) itemView.findViewById(R.id.ll_content);
// 获取根布局
tvNmae = (TextView) itemView.findViewById(R.id.tv_name);
// 获取书籍名称TextView
tvSize = (TextView) itemView.findViewById(R.id.tv_size);
// 获取文件大小TextView
scbSelect = (SmoothCheckBox) itemView.findViewById(R.id.scb_select);
// 获取复选框
tvLoc = (TextView) itemView.findViewById(R.id.tv_loc);
// 获取文件路径TextView
}
}
// 将字节数转换为可读的文件大小单位B, KB, MB, GB
public static String convertByte(long size) {
DecimalFormat df = new DecimalFormat("###.#");
// 设置数字格式
float f;
if (size < 1024) {
f = size / 1.0f;
return (df.format(new Float(f).doubleValue()) + "B");
// 小于1KB返回字节数
} else if (size < 1024 * 1024) {
f = (float) ((float) size / (float) 1024);
return (df.format(new Float(f).doubleValue()) + "KB");
// 小于1MB返回KB
} else if (size < 1024 * 1024 * 1024) {
f = (float) ((float) size / (float) (1024 * 1024));
return (df.format(new Float(f).doubleValue()) + "MB");
// 小于1GB返回MB
} else {
f = (float) ((float) size / (float) (1024 * 1024 * 1024));
return (df.format(new Float(f).doubleValue()) + "GB");
// 大于等于1GB返回GB
}
}
// 获取选中的文件列表
public List<File> getSelectDatas() {
return selectDatas;
// 返回选中的文件列表
}
}

@ -1,207 +1,100 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.view.adapter;
import android.support.v7.widget.RecyclerView;
// 导入RecyclerView类用于显示列表项
import android.view.LayoutInflater;
// 导入LayoutInflater用于加载布局
import android.view.View;
// 导入View类视图的基类
import android.view.ViewGroup;
// 导入ViewGroup类视图容器的基类
import android.widget.FrameLayout;
// 导入FrameLayout容器布局
import android.widget.ImageView;
// 导入ImageView用于显示图像
import android.widget.TextView;
// 导入TextView用于显示文本
import com.bumptech.glide.Glide;
// 导入Glide库用于图片加载和缓存
import com.bumptech.glide.load.engine.DiskCacheStrategy;
// 导入Glide的缓存策略
import com.monke.monkeybook.R;
// 导入资源文件,包含布局、字符串、图片等资源
import com.monke.monkeybook.bean.SearchBookBean;
// 导入自定义的书籍数据模型
import com.monke.monkeybook.widget.refreshview.RefreshRecyclerViewAdapter;
// 导入自定义的RecyclerView适配器基类
import java.text.DecimalFormat;
// 导入DecimalFormat用于格式化数字
import java.util.ArrayList;
// 导入ArrayList类动态数组
import java.util.List;
// 导入List接口用于列表类型
// SearchBookAdapter继承自RefreshRecyclerViewAdapter适配器用于展示搜索到的书籍
public class SearchBookAdapter extends RefreshRecyclerViewAdapter {
private List<SearchBookBean> searchBooks;
// 存储书籍的列表数据
// 定义点击事件的回调接口
public interface OnItemClickListener {
void clickAddShelf(View clickView, int position, SearchBookBean searchBookBean);
// 添加书籍到书架回调
void clickItem(View animView, int position, SearchBookBean searchBookBean);
// 点击书籍项回调
}
private OnItemClickListener itemClickListener;
// 接口实例,通知外部点击事件
// 构造函数,初始化适配器
public SearchBookAdapter() {
super(true);
// 初始化基类
searchBooks = new ArrayList<>();
// 初始化书籍列表
}
// 创建ViewHolder加载每个书籍项的布局
@Override
public RecyclerView.ViewHolder onCreateViewholder(ViewGroup parent, int viewType) {
// 加载单个书籍项的布局并返回ViewHolder
return new Viewholder(LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_searchbook_item, parent, false));
}
// 绑定数据到ViewHolder
@Override
public void onBindViewholder(final RecyclerView.ViewHolder holder, final int position) {
// 使用Glide加载书籍封面图
Glide.with(((Viewholder) holder).ivCover.getContext())
.load(searchBooks.get(position).getCoverUrl())
// 设置封面图片的URL
.diskCacheStrategy(DiskCacheStrategy.RESULT)
// 设置磁盘缓存策略,缓存最终结果
.centerCrop()
// 以中心裁剪方式展示图片
.dontAnimate()
// 禁用图片加载动画
.placeholder(R.drawable.img_cover_default)
// 设置加载过程中的占位符
.into(((Viewholder) holder).ivCover);
// 加载图片到ImageView
// 设置书籍名称、作者、状态、字数、类型、简介等信息
((Viewholder) holder).tvName.setText(searchBooks.get(position).getName());
// 设置书籍名称
((Viewholder) holder).tvAuthor.setText(searchBooks.get(position).getAuthor());
// 设置作者名称
// 设置书籍状态(如连载状态)
String state = searchBooks.get(position).getState();
if (state == null || state.length() == 0) {
((Viewholder) holder).tvState.setVisibility(View.GONE);
// 如果没有状态信息,隐藏
} else {
((Viewholder) holder).tvState.setVisibility(View.VISIBLE);
// 否则显示状态
((Viewholder) holder).tvState.setText(state);
}
// 设置字数信息超过1万字时显示“万字”单位
long words = searchBooks.get(position).getWords();
if (words <= 0) {
((Viewholder) holder).tvWords.setVisibility(View.GONE);
// 如果字数小于等于0隐藏字数信息
} else {
String wordsS = Long.toString(words) + "字";
// 默认显示字数
if (words > 10000) {
DecimalFormat df = new DecimalFormat("#.#");
// 格式化为“万字”
wordsS = df.format(words * 1.0f / 10000f) + "万字";
}
((Viewholder) holder).tvWords.setVisibility(View.VISIBLE);
// 显示字数信息
((Viewholder) holder).tvWords.setText(wordsS);
}
// 设置书籍类型(如小说、历史等)
String kind = searchBooks.get(position).getKind();
if (kind == null || kind.length() <= 0) {
((Viewholder) holder).tvKind.setVisibility(View.GONE);
// 如果没有类型信息,隐藏
} else {
((Viewholder) holder).tvKind.setVisibility(View.VISIBLE);
// 否则显示类型
((Viewholder) holder).tvKind.setText(kind);
}
// 设置书籍的最新章节或简介
if (searchBooks.get(position).getLastChapter() != null && searchBooks.get(position).getLastChapter().length() > 0)
((Viewholder) holder).tvLastest.setText(searchBooks.get(position).getLastChapter());
// 显示最新章节
else if (searchBooks.get(position).getDesc() != null && searchBooks.get(position).getDesc().length() > 0) {
((Viewholder) holder).tvLastest.setText(searchBooks.get(position).getDesc());
// 显示简介
} else
((Viewholder) holder).tvLastest.setText("");
// 如果没有最新章节和简介,设置为空
// 设置书籍的来源信息
if (searchBooks.get(position).getOrigin() != null && searchBooks.get(position).getOrigin().length() > 0) {
((Viewholder) holder).tvOrigin.setVisibility(View.VISIBLE);
// 如果有来源,显示
((Viewholder) holder).tvOrigin.setText("来源:" + searchBooks.get(position).getOrigin());
} else {
((Viewholder) holder).tvOrigin.setVisibility(View.GONE);
// 如果没有来源,隐藏
}
// 设置书籍是否已添加到书架的状态
if (searchBooks.get(position).getAdd()) {
((Viewholder) holder).tvAddShelf.setText("已添加");
((Viewholder) holder).tvAddShelf.setEnabled(false);
// 如果已添加,禁用添加按钮
} else {
((Viewholder) holder).tvAddShelf.setText("+添加");
((Viewholder) holder).tvAddShelf.setEnabled(true);
// 如果未添加,启用添加按钮
}
// 设置点击事件,点击书籍项时触发
((Viewholder) holder).flContent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -209,8 +102,6 @@ public class SearchBookAdapter extends RefreshRecyclerViewAdapter {
itemClickListener.clickItem(((Viewholder) holder).ivCover, position, searchBooks.get(position));
}
});
// 设置点击事件,点击添加书架按钮时触发
((Viewholder) holder).tvAddShelf.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -220,129 +111,68 @@ public class SearchBookAdapter extends RefreshRecyclerViewAdapter {
});
}
// 获取每个列表项的类型
@Override
public int getItemViewtype(int position) {
return 0;
// 默认返回0表示只有一种类型
}
// 获取列表项数量
@Override
public int getItemcount() {
return searchBooks.size();
// 返回书籍列表的大小
}
// ViewHolder类管理书籍项的视图控件
class Viewholder extends RecyclerView.ViewHolder {
FrameLayout flContent;
// 根布局,书籍项的容器
ImageView ivCover;
// 显示书籍封面的ImageView
TextView tvName;
// 显示书籍名称的TextView
TextView tvAuthor;
// 显示作者的TextView
TextView tvState;
// 显示书籍状态的TextView如连载状态
TextView tvWords;
// 显示字数的TextView
TextView tvKind;
// 显示书籍类型的TextView
TextView tvLastest;
// 显示最新章节或简介的TextView
TextView tvAddShelf;
// 显示添加到书架按钮的TextView
TextView tvOrigin;
// 显示书籍来源的TextView
// 构造函数,初始化视图控件
public Viewholder(View itemView) {
super(itemView);
flContent = (FrameLayout) itemView.findViewById(R.id.fl_content);
// 获取根布局
ivCover = (ImageView) itemView.findViewById(R.id.iv_cover);
// 获取书籍封面ImageView
tvName = (TextView) itemView.findViewById(R.id.tv_name);
// 获取书籍名称TextView
tvAuthor = (TextView) itemView.findViewById(R.id.tv_author);
// 获取作者TextView
tvState = (TextView) itemView.findViewById(R.id.tv_state);
// 获取书籍状态TextView
tvWords = (TextView) itemView.findViewById(R.id.tv_words);
// 获取字数TextView
tvLastest = (TextView) itemView.findViewById(R.id.tv_lastest);
// 获取最新章节TextView
tvAddShelf = (TextView) itemView.findViewById(R.id.tv_addshelf);
// 获取添加到书架按钮TextView
tvKind = (TextView) itemView.findViewById(R.id.tv_kind);
// 获取书籍类型TextView
tvOrigin = (TextView) itemView.findViewById(R.id.tv_origin);
// 获取来源TextView
}
}
// 设置点击事件监听器
public void setItemClickListener(OnItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
// 保存监听器
}
// 批量添加数据到列表
public void addAll(List<SearchBookBean> newDatas) {
if (newDatas != null && newDatas.size() > 0) {
if(newDatas!=null && newDatas.size()>0){
int oldCount = getItemcount();
// 获取当前列表项数
searchBooks.addAll(newDatas);
// 将新数据添加到列表
notifyItemRangeInserted(oldCount, newDatas.size());
// 通知RecyclerView插入新的数据项
notifyItemRangeInserted(oldCount,newDatas.size());
}
}
// 替换列表中的所有数据
public void replaceAll(List<SearchBookBean> newData) {
searchBooks.clear();
// 清空现有数据
if (newData != null && newData.size() > 0) {
searchBooks.addAll(newData);
// 添加新数据
}
notifyDataSetChanged();
// 通知RecyclerView更新数据
}
// 获取当前的书籍列表数据
public List<SearchBookBean> getSearchBooks() {
return searchBooks;
// 返回书籍列表
}
// 设置新的书籍列表数据
public void setSearchBooks(List<SearchBookBean> searchBooks) {
this.searchBooks = searchBooks;
// 更新书籍列表
}
}
}

@ -1,104 +1,54 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
// 定义适配器类所在的包
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.view.adapter;
// 导入所需的类和库
import android.view.LayoutInflater;
// 用于布局填充
import android.view.View;
// 用于表示视图组件
import android.widget.TextView;
// 用于显示文本的控件
import com.monke.monkeybook.R;
// 引入项目的资源文件
import com.monke.monkeybook.bean.SearchHistoryBean;
// 导入数据模型类,用于封装搜索历史
import com.monke.monkeybook.widget.flowlayout.FlowLayout;
// 引入FlowLayout类显示标签的布局
import com.monke.monkeybook.widget.flowlayout.TagAdapter;
// 引入TagAdapter基类扩展标签的适配器
import java.util.ArrayList;
// 引入ArrayList类创建一个可动态变化的列表
// 定义SearchHistoryAdapter类继承TagAdapter<SearchHistoryBean>
// 用于显示搜索历史数据,并处理点击事件
public class SearchHistoryAdapter extends TagAdapter<SearchHistoryBean> {
// 构造函数调用父类TagAdapter的构造方法初始化空的数据集合
public SearchHistoryAdapter() {
super(new ArrayList<SearchHistoryBean>());
}
// 定义一个接口OnItemClickListener用于处理点击事件
public interface OnItemClickListener {
// 定义点击条目的回调方法传递一个SearchHistoryBean对象
public interface OnItemClickListener{
void itemClick(SearchHistoryBean searchHistoryBean);
}
// 声明一个成员变量onItemClickListener用于保存点击事件监听器
private SearchHistoryAdapter.OnItemClickListener onItemClickListener;
// 获取当前的点击事件监听器
public OnItemClickListener getListener() {
return onItemClickListener;
}
// 设置点击事件监听器
public void setOnItemClickListener(OnItemClickListener listener) {
this.onItemClickListener = listener;
}
// 重写getView方法返回每个标签SearchHistoryBean对应的视图
@Override
public View getView(FlowLayout parent, int position, final SearchHistoryBean searchHistoryBean) {
// 使用LayoutInflater填充自定义的item布局文件
TextView tv = (TextView) LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_searchhistory_item,
parent, false);
// 加载adapter_searchhistory_item布局
// 设置TextView的文本为SearchHistoryBean的内容
tv.setText(searchHistoryBean.getContent());
// 设置点击事件监听器
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 如果设置了点击事件监听器调用它的itemClick方法
if (null != onItemClickListener) {
if(null != onItemClickListener){
onItemClickListener.itemClick(searchHistoryBean);
// 触发点击事件回调
}
}
});
// 返回填充好的TextView作为标签视图
return tv;
}
// 获取指定位置的SearchHistoryBean对象
public SearchHistoryBean getItemData(int position) {
public SearchHistoryBean getItemData(int position){
return mTagDatas.get(position);
// 从TagAdapter父类中获取数据集合并返回对应位置的数据
}
// 获取数据集合的大小
public int getDataSize() {
public int getDataSize(){
return mTagDatas.size();
// 返回标签数据的数量
}
}

@ -1,219 +1,136 @@
// Copyright (c) 2017. 章钦豪. All rights reserved.
//Copyright (c) 2017. 章钦豪. All rights reserved.
package com.monke.monkeybook.view.impl;
// 声明包名,表示这是项目中的一个实现类
// 导入相关类
import android.content.Intent;
// 用于启动其他Activity
import android.os.Build;
// 用于获取Android设备的版本信息
import android.text.method.ScrollingMovementMethod;
// 用于使TextView支持滚动
import android.view.View;
// 用于视图的点击事件监听
import android.view.animation.Animation;
// 动画类
import android.view.animation.AnimationUtils;
// 用于加载动画资源
import android.widget.FrameLayout;
// 帧布局
import android.widget.ImageView;
// 图片视图控件
import android.widget.TextView;
// 文本视图控件
import com.bumptech.glide.Glide;
// 图片加载库,用于加载图片
import com.bumptech.glide.load.engine.DiskCacheStrategy;
// 设置缓存策略
import com.monke.monkeybook.BitIntentDataManager;
// 用于管理Intent数据
import com.monke.monkeybook.R;
// 引用项目中的资源文件
import com.monke.monkeybook.base.MBaseActivity;
// 基类Activity
import com.monke.monkeybook.presenter.IBookDetailPresenter;
// 书籍详情的Presenter接口
import com.monke.monkeybook.presenter.impl.BookDetailPresenterImpl;
// 书籍详情的Presenter实现类
import com.monke.monkeybook.presenter.impl.ReadBookPresenterImpl;
// 阅读书籍的Presenter实现类
import com.monke.monkeybook.utils.BlurTransformation;
// 图片模糊效果工具
import com.monke.monkeybook.view.IBookDetailView;
// 书籍详情视图接口
// 书籍详情页面 Activity 类
public class BookDetailActivity extends MBaseActivity<IBookDetailPresenter> implements IBookDetailView {
// 定义页面上的控件
private FrameLayout iflContent;
// 内容区域,可能用于包裹其他控件
private ImageView ivBlurCover;
// 用于显示模糊效果的封面
private ImageView ivCover;
// 用于显示书籍封面
private TextView tvName;
// 显示书籍名称
private TextView tvAuthor;
// 显示书籍作者
private TextView tvOrigin;
// 显示书籍来源
private TextView tvChapter;
// 显示当前章节
private TextView tvIntro;
// 显示书籍简介
private TextView tvShelf;
// 操作书架(放入/移出)
private TextView tvRead;
// 操作阅读(开始阅读/继续阅读)
private TextView tvLoading;
// 显示加载提示
// 动画相关
private Animation animHideLoading; // 隐藏加载动画
private Animation animShowInfo; // 显示书籍信息动画
private Animation animHideLoading;
private Animation animShowInfo;
@Override
protected IBookDetailPresenter initInjector() {
// 初始化Presenter负责处理业务逻辑
return new BookDetailPresenterImpl(getIntent());
}
@Override
protected void onCreateActivity() {
// 设置当前Activity的布局文件
setContentView(R.layout.activity_detail);
}
@Override
protected void initData() {
// 初始化动画
animShowInfo = AnimationUtils.loadAnimation(this, android.R.anim.fade_in); // 加载淡入动画
animHideLoading = AnimationUtils.loadAnimation(this, android.R.anim.fade_out); // 加载淡出动画
animHideLoading.setAnimationListener(new Animation.AnimationListener() { // 动画结束后执行的回调
animShowInfo = AnimationUtils.loadAnimation(this, android.R.anim.fade_in);
animHideLoading = AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
animHideLoading.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// 动画开始时的处理(此处未做任何操作)
}
@Override
public void onAnimationEnd(Animation animation) {
// 动画结束时,隐藏加载提示
tvLoading.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animation animation) {
// 动画重复时的处理(此处未做任何操作)
}
});
}
@Override
protected void bindView() {
// 绑定视图组件
iflContent = (FrameLayout) findViewById(R.id.ifl_content);
// 获取内容区域
ivBlurCover = (ImageView) findViewById(R.id.iv_blur_cover);
// 获取模糊封面
ivCover = (ImageView) findViewById(R.id.iv_cover);
// 获取书籍封面
tvName = (TextView) findViewById(R.id.tv_name);
// 获取书籍名称
tvAuthor = (TextView) findViewById(R.id.tv_author);
// 获取书籍作者
tvOrigin = (TextView) findViewById(R.id.tv_origin);
// 获取书籍来源
tvChapter = (TextView) findViewById(R.id.tv_chapter);
// 获取当前章节
tvIntro = (TextView) findViewById(R.id.tv_intro);
// 获取书籍简介
tvShelf = (TextView) findViewById(R.id.tv_shelf);
// 获取书架操作按钮
tvRead = (TextView) findViewById(R.id.tv_read);
// 获取阅读操作按钮
tvLoading = (TextView) findViewById(R.id.tv_loading);
// 获取加载提示
tvIntro.setMovementMethod(ScrollingMovementMethod.getInstance());
// 使简介支持滚动
initView();
// 初始化界面
updateView();
// 更新视图
}
@Override
public void updateView() {
// 根据Presenter获取的数据更新视图
if (null != mPresenter.getBookShelf()) {
// 如果书架信息存在
if (mPresenter.getInBookShelf()) {
// 如果书籍在书架中
// 显示当前章节
if (mPresenter.getBookShelf().getBookInfoBean().getChapterlist().size() > 0)
tvChapter.setText(String.format(getString(R.string.tv_read_durprogress), mPresenter.getBookShelf().getBookInfoBean().getChapterlist().get(mPresenter.getBookShelf().getDurChapter()).getDurChapterName()));
else
tvChapter.setText("无章节");
// 设置书架操作按钮为移出
tvShelf.setText("移出书架");
tvRead.setText("继续阅读");
tvShelf.setOnClickListener(new View.OnClickListener() {
// 移出书架点击事件
@Override
public void onClick(View v) {
//从书架移出
mPresenter.removeFromBookShelf();
// 从书架移除
}
});
} else {
// 如果书籍不在书架中
// 显示最新章节
if (mPresenter.getBookShelf().getBookInfoBean().getChapterlist().size() == 0) {
tvChapter.setText("无章节");
} else {
tvChapter.setText(String.format(getString(R.string.tv_searchbook_lastest), mPresenter.getBookShelf().getBookInfoBean().getChapterlist().get(mPresenter.getBookShelf().getBookInfoBean().getChapterlist().size() - 1).getDurChapterName()));
}
// 设置书架操作按钮为放入
tvShelf.setText("放入书架");
tvRead.setText("开始阅读");
tvShelf.setOnClickListener(new View.OnClickListener() { // 放入书架点击事件
tvShelf.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.addToBookShelf(); // 放入书架
//放入书架
mPresenter.addToBookShelf();
}
});
}
// 如果简介为空,则设置为书籍的介绍
if (tvIntro.getText().toString().trim().length() == 0) {
tvIntro.setText(mPresenter.getBookShelf().getBookInfoBean().getIntroduce());
}
// 显示简介并执行动画
if (tvIntro.getVisibility() != View.VISIBLE) {
tvIntro.setVisibility(View.VISIBLE);
tvIntro.startAnimation(animShowInfo);
// 显示动画
tvLoading.startAnimation(animHideLoading);
// 隐藏加载动画
}
// 显示书籍来源
if (mPresenter.getBookShelf().getBookInfoBean().getOrigin() != null && mPresenter.getBookShelf().getBookInfoBean().getOrigin().length() > 0) {
tvOrigin.setVisibility(View.VISIBLE);
tvOrigin.setText("来源:" + mPresenter.getBookShelf().getBookInfoBean().getOrigin());
@ -221,38 +138,29 @@ public class BookDetailActivity extends MBaseActivity<IBookDetailPresenter> impl
tvOrigin.setVisibility(View.GONE);
}
} else {
// 如果书架信息为空
tvChapter.setText(String.format(getString(R.string.tv_searchbook_lastest), mPresenter.getSearchBook().getLastChapter())); // 显示搜索书籍的最新章节
tvChapter.setText(String.format(getString(R.string.tv_searchbook_lastest), mPresenter.getSearchBook().getLastChapter()));
tvShelf.setText("放入书架");
tvRead.setText("开始阅读");
tvRead.setOnClickListener(new View.OnClickListener() {
// 阅读按钮点击事件
@Override
public void onClick(View v) {
// 放入书架操作(此处未实现)
//放入书架
}
});
tvIntro.setVisibility(View.INVISIBLE);
// 隐藏简介
tvLoading.setVisibility(View.VISIBLE);
// 显示加载提示
tvLoading.setText("加载中...");
// 设置加载提示文字
}
// 防止点击加载提示时触发操作
tvLoading.setOnClickListener(null);
}
@Override
public void getBookShelfError() {
// 获取书架信息失败,显示错误信息并设置点击重试
tvLoading.setVisibility(View.VISIBLE);
tvLoading.setText("加载失败,点击重试");
tvLoading.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 点击重试时重新获取书架信息
tvLoading.setText("加载中...");
tvLoading.setOnClickListener(null);
mPresenter.getBookShelfInfo();
@ -262,40 +170,31 @@ public class BookDetailActivity extends MBaseActivity<IBookDetailPresenter> impl
@Override
protected void firstRequest() {
// 初次请求时调用
super.firstRequest();
// 如果是从搜索页面打开,并且书架信息为空,则发起网络请求获取书架信息
if (mPresenter.getOpenfrom() == BookDetailPresenterImpl.FROM_SEARCH && mPresenter.getBookShelf() == null) {
//网络请求
mPresenter.getBookShelfInfo();
}
}
// 初始化页面控件显示
private void initView() {
String coverUrl;
// 封面URL
String name;
// 书籍名称
String author;
// 书籍作者
if (mPresenter.getOpenfrom() == BookDetailPresenterImpl.FROM_BOOKSHELF) {
// 从书架打开
coverUrl = mPresenter.getBookShelf().getBookInfoBean().getCoverUrl();
name = mPresenter.getBookShelf().getBookInfoBean().getName();
author = mPresenter.getBookShelf().getBookInfoBean().getAuthor();
// 显示书籍来源
if (mPresenter.getBookShelf().getBookInfoBean().getOrigin() != null && mPresenter.getBookShelf().getBookInfoBean().getOrigin().length() > 0) {
tvOrigin.setVisibility(View.VISIBLE);
tvOrigin.setText("来源:" + mPresenter.getBookShelf().getBookInfoBean().getOrigin());
} else {
tvOrigin.setVisibility(View.GONE);
}
} else { // 从搜索页面打开
} else {
coverUrl = mPresenter.getSearchBook().getCoverUrl();
name = mPresenter.getSearchBook().getName();
author = mPresenter.getSearchBook().getAuthor();
// 显示书籍来源
if (mPresenter.getSearchBook().getOrigin() != null && mPresenter.getSearchBook().getOrigin().length() > 0) {
tvOrigin.setVisibility(View.VISIBLE);
tvOrigin.setText("来源:" + mPresenter.getSearchBook().getOrigin());
@ -304,47 +203,39 @@ public class BookDetailActivity extends MBaseActivity<IBookDetailPresenter> impl
}
}
// 使用Glide加载封面图像并显示模糊效果封面
Glide.with(this).load(coverUrl).dontAnimate().diskCacheStrategy(DiskCacheStrategy.RESULT).centerCrop().placeholder(R.drawable.img_cover_default).into(ivCover);
Glide.with(this).load(coverUrl).dontAnimate().diskCacheStrategy(DiskCacheStrategy.RESULT).centerCrop().bitmapTransform(new BlurTransformation(this, 6)).into(ivBlurCover);
// 设置书籍名称和作者
tvName.setText(name);
tvAuthor.setText(author);
}
@Override
protected void bindEvent() {
// 设置页面点击事件
iflContent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 根据Android版本处理不同的退出动画
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if(getStart_share_ele()) {
if(getStart_share_ele()){
finishAfterTransition();
} else {
}else{
finish();
overridePendingTransition(0, android.R.anim.fade_out);
overridePendingTransition(0,android.R.anim.fade_out);
}
} else {
finish();
overridePendingTransition(0, android.R.anim.fade_out);
overridePendingTransition(0,android.R.anim.fade_out);
}
}
});
// 设置阅读按钮点击事件
tvRead.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 跳转到阅读界面
//进入阅读
Intent intent = new Intent(BookDetailActivity.this, ReadBookActivity.class);
intent.putExtra("from", ReadBookPresenterImpl.OPEN_FROM_APP);
String key = String.valueOf(System.currentTimeMillis());
intent.putExtra("data_key", key);
// 使用BitIntentDataManager保存书籍数据并传递
try {
BitIntentDataManager.getInstance().putData(key, mPresenter.getBookShelf().clone());
} catch (CloneNotSupportedException e) {
@ -353,17 +244,16 @@ public class BookDetailActivity extends MBaseActivity<IBookDetailPresenter> impl
}
startActivityByAnim(intent, android.R.anim.fade_in, android.R.anim.fade_out);
// 结束当前Activity并设置退出动画
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if(getStart_share_ele()) {
if(getStart_share_ele()){
finishAfterTransition();
} else {
}else{
finish();
overridePendingTransition(0, android.R.anim.fade_out);
overridePendingTransition(0,android.R.anim.fade_out);
}
} else {
finish();
overridePendingTransition(0, android.R.anim.fade_out);
overridePendingTransition(0,android.R.anim.fade_out);
}
}
});

@ -1,181 +1,164 @@
// 定义包名
package com.monke.monkeybook.widget;
import android.annotation.TargetApi;// 导入 TargetApi 注解,标明特定的 API 级别
import android.content.Context;// 导入 Context 类,提供应用的上下文
import android.os.Build;// 导入 Build 类,用于获取当前 Android 版本信息
import android.support.annotation.AttrRes; // 导入 AttrRes 注解,表示属性资源的类型。
import android.support.annotation.NonNull;// 导入 NonNull 注解,表示不允许为 null。
import android.support.annotation.Nullable;// 导入 Nullable 注解,表示可以为 null。
import android.support.annotation.StyleRes;// 导入 StyleRes 注解,表示样式资源的类型。
import android.support.v7.widget.LinearLayoutManager;// 导入 LinearLayoutManager用于 RecyclerView 的线性布局。
import android.support.v7.widget.RecyclerView;// 导入 RecyclerView 类,适用于高效的列表展示。
import android.util.AttributeSet;// 导入 AttributeSet 类,用于访问 XML 布局中的自定义属性。
import android.view.LayoutInflater;// 导入 LayoutInflater 类,用于将布局文件转换为 View 对象。
import android.view.View;// 导入 View 类,所有 UI 控件的基类。
import android.view.animation.Animation;// 导入 Animation 类,用于创建动画效果。
import android.view.animation.AnimationUtils;// 导入 AnimationUtils 类,提供加载动画资源的方法。
import android.widget.FrameLayout;// 导入 FrameLayout 类,允许堆叠子视图的布局。
import android.widget.LinearLayout; // 导入 LinearLayout 类,允许在垂直或水平方向上排列子视图。
import android.widget.TextView;// 导入 TextView 类,用于显示文本。
import com.monke.monkeybook.R;// 导入应用资源类,访问资源文件。
import com.monke.monkeybook.bean.BookShelfBean;// 导入 BookShelfBean 类,书架数据模型。
import com.monke.monkeybook.view.adapter.ChapterListAdapter;// 导入 ChapterListAdapter 类,章节列表适配器。
public class ChapterListView extends FrameLayout{// 创建 ChapterListView 类,继承自 FrameLayout
private TextView tvName;// 显示书名的 TextView
private TextView tvListCount;// 显示章节数量的 TextView
private RecyclerView rvList;// 显示章节列表的 RecyclerView
private RecyclerViewBar rvbSlider;// 自定义的 RecyclerView 导航条
private FrameLayout flBg;// 背景 FrameLayout
private LinearLayout llContent;// 内容 LinearLayout
private ChapterListAdapter chapterListAdapter;// 章节列表适配器
private Animation animIn; // 动画进入效果
private Animation animOut; // 动画退出效果
// 默认构造函数
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.AttrRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StyleRes;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.monke.monkeybook.R;
import com.monke.monkeybook.bean.BookShelfBean;
import com.monke.monkeybook.view.adapter.ChapterListAdapter;
public class ChapterListView extends FrameLayout{
private TextView tvName;
private TextView tvListCount;
private RecyclerView rvList;
private RecyclerViewBar rvbSlider;
private FrameLayout flBg;
private LinearLayout llContent;
private ChapterListAdapter chapterListAdapter;
private Animation animIn;
private Animation animOut;
public ChapterListView(@NonNull Context context) {
this(context,null);// 调用带参构造函数,传递 null
this(context,null);
}
// 带 AttributeSet 的构造函数
public ChapterListView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);// 调用带风格属性的构造函数
this(context, attrs,0);
}
// 带属性和风格的构造函数
public ChapterListView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);// 调用父类构造函数
init();// 调用初始化方法
super(context, attrs, defStyleAttr);
init();
}
// 具有更多参数的构造函数
@TargetApi(Build.VERSION_CODES.LOLLIPOP)// 指示支持 API 21 及以上
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ChapterListView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);// 调用父类构造函数
init();// 调用初始化方法
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
// 初始化方法
private void init() {
setVisibility(INVISIBLE);// 默认隐藏视图
LayoutInflater.from(getContext()).inflate(R.layout.view_chapterlist, this, true); // 加载布局
initData(); // 初始化数据
initView(); // 初始化视图组件
setVisibility(INVISIBLE);
LayoutInflater.from(getContext()).inflate(R.layout.view_chapterlist,this,true);
initData();
initView();
}
// 初始化数据和动画
private void initData() {
// 加载进入动画
animIn = AnimationUtils.loadAnimation(getContext(),R.anim.anim_pop_chapterlist_in);
/
animIn.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
flBg.setOnClickListener(null);// 动画开始时禁止点击
flBg.setOnClickListener(null);
}
@Override
public void onAnimationEnd(Animation animation) {
flBg.setOnClickListener(new OnClickListener() {// 动画结束后允许点击
flBg.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dimissChapterList();// 点击背景时关闭章节列表
dimissChapterList();
}
});
}
@Override
public void onAnimationRepeat(Animation animation) {
// 动画重复时的反应(可根据需要实现
}
});
// 加载退出动画
animOut = AnimationUtils.loadAnimation(getContext(),R.anim.anim_pop_chapterlist_out);// 退出动画
// 设置退出动画监听器
animOut = AnimationUtils.loadAnimation(getContext(),R.anim.anim_pop_chapterlist_out);
animOut.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
flBg.setOnClickListener(null);// 动画开始时禁止点击
flBg.setOnClickListener(null);
}
@Override
public void onAnimationEnd(Animation animation) {
llContent.setVisibility(INVISIBLE); // 隐藏内容
setVisibility(INVISIBLE); // 隐藏视图
llContent.setVisibility(INVISIBLE);
setVisibility(INVISIBLE);
}
@Override
public void onAnimationRepeat(Animation animation) {
// 动画重复时的反应(可根据需要实现)
}
});
}
// 显示章节列表
public void show(int durChapter) {
chapterListAdapter.setIndex(durChapter); // 设置当前章节索引
((LinearLayoutManager) rvList.getLayoutManager()).scrollToPositionWithOffset(durChapter, 0); // 滚动到特定章节
if(getVisibility() != VISIBLE) {
setVisibility(VISIBLE); // 显示章节列表
animOut.cancel(); // 取消退出动画
animIn.cancel(); // 取消进入动画
llContent.setVisibility(VISIBLE); // 显示内容
llContent.startAnimation(animIn); // 开始进入动画
chapterListAdapter.setIndex(durChapter);
((LinearLayoutManager) rvList.getLayoutManager()).scrollToPositionWithOffset(durChapter,0);
if(getVisibility()!=VISIBLE){
setVisibility(VISIBLE);
animOut.cancel();
animIn.cancel();
llContent.setVisibility(VISIBLE);
llContent.startAnimation(animIn);
}
}
// 点击监听接口
public interface OnItemClickListener{// 点击监听接口
public void itemClick(int index);// 点击事件方法
public interface OnItemClickListener{
public void itemClick(int index);
}
private OnItemClickListener itemClickListener; // 用于监听点击事件的实例
private BookShelfBean bookShelfBean; // 书架数据类实例
// 初始化视图组件
private void initView() { // 初始化视图组件
flBg = (FrameLayout) findViewById(R.id.fl_bg); // 获取背景 FrameLayout
llContent = (LinearLayout) findViewById(R.id.ll_content); // 获取内容 LinearLayout
tvName = (TextView) findViewById(R.id.tv_name); // 获取书名 TextView
tvListCount = (TextView) findViewById(R.id.tv_listcount); // 获取章节数量 TextView
rvList = (RecyclerView) findViewById(R.id.rv_list); // 获取章节列表 RecyclerView
rvList.setLayoutManager(new LinearLayoutManager(getContext())); // 设置布局管理器
rvList.setItemAnimator(null); // 关闭动画效果
rvbSlider = (RecyclerViewBar) findViewById(R.id.rvb_slider); // 获取自定义滑动条
private OnItemClickListener itemClickListener;
private BookShelfBean bookShelfBean;
private void initView() {
flBg = (FrameLayout) findViewById(R.id.fl_bg);
llContent = (LinearLayout) findViewById(R.id.ll_content);
tvName = (TextView) findViewById(R.id.tv_name);
tvListCount = (TextView) findViewById(R.id.tv_listcount);
rvList = (RecyclerView) findViewById(R.id.rv_list);
rvList.setLayoutManager(new LinearLayoutManager(getContext()));
rvList.setItemAnimator(null);
rvbSlider = (RecyclerViewBar) findViewById(R.id.rvb_slider);
}
public void setData(BookShelfBean bookShelfBean, OnItemClickListener clickListener) { // 设置数据
this.itemClickListener = clickListener; // 设置点击监听器
this.bookShelfBean = bookShelfBean; // 设置书架数据
tvName.setText(bookShelfBean.getBookInfoBean().getName()); // 设置书名
tvListCount.setText("共" + bookShelfBean.getBookInfoBean().getChapterlist().size() + "章"); // 设置章节数量文本
chapterListAdapter = new ChapterListAdapter(bookShelfBean, new OnItemClickListener() { // 创建章节列表适配器
public void setData(BookShelfBean bookShelfBean,OnItemClickListener clickListener) {
this.itemClickListener = clickListener;
this.bookShelfBean = bookShelfBean;
tvName.setText(bookShelfBean.getBookInfoBean().getName());
tvListCount.setText("共"+bookShelfBean.getBookInfoBean().getChapterlist().size()+"章");
chapterListAdapter = new ChapterListAdapter(bookShelfBean, new OnItemClickListener() {
@Override
// 处理章节单击事件
public void itemClick(int index) {
if(itemClickListener != null) {
itemClickListener.itemClick(index); // 回调点击事件
rvbSlider.scrollToPositionWithOffset(index); // 滚动到对应位置
if(itemClickListener!=null){
itemClickListener.itemClick(index);
rvbSlider.scrollToPositionWithOffset(index);
}
}
});
rvList.setAdapter(chapterListAdapter); // 设置适配器到 RecyclerView
rvbSlider.setRecyclerView(rvList); // 设置滑动条的 RecyclerView
rvList.setAdapter(chapterListAdapter);
rvbSlider.setRecyclerView(rvList);
}
// 关闭章节列表
public Boolean dimissChapterList() {
if(getVisibility() != VISIBLE) {
return false; // 如果已经隐藏,返回 false
} else {
animOut.cancel(); // 取消退出动画
animIn.cancel(); // 取消进入动画
llContent.startAnimation(animOut); // 开始退出动画
return true; // 返回 true 表示正在关闭
public Boolean dimissChapterList(){
if(getVisibility()!=VISIBLE){
return false;
}else{
animOut.cancel();
animIn.cancel();
llContent.startAnimation(animOut);
return true;
}
}
}

@ -1,43 +1,33 @@
package com.monke.monkeybook.widget;
// 导入必要的 Android 类
import android.content.Context;// 导入 Context 类,用于访问应用的环境信息。
import android.graphics.Canvas;// 导入 Canvas 类,用于进行自定义绘制和图形操作。
import android.text.Layout;// 导入 Layout 类,用于处理文本的布局,控制文本的排列和格式。
import android.text.StaticLayout;// 导入 StaticLayout 类,帮助绘制多行静态文本。
import android.text.TextPaint;// 导入 TextPaint 类,扩展了 Paint 类用于绘制文本并包含字体相关的信息。
import android.util.AttributeSet;// 导入 AttributeSet 类,处理 XML 布局中定义的自定义属性。
import android.content.Context;
import android.graphics.Canvas;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
/**
* Created by ZQH on 2017/4/10.
*/
public class MTextView extends android.support.v7.widget.AppCompatTextView {
// 构造函数,接收上下文和属性集
public MTextView(Context context, AttributeSet attrs) {
super(context, attrs);// 调用父类构造函数
super(context, attrs);
}
// 布局方法,处理视图的布局改变
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);// 调用父类的方法
super.onLayout(changed, left, top, right, bottom);
}
// 绘制方法,用于在画布上绘制文本
@Override
protected void onDraw(Canvas canvas) {
TextPaint paint = getPaint();// 获取 TextPaint 对象
paint.setColor(getTextColors().getDefaultColor());// 设置文本颜色为默认颜色
// 创建 StaticLayout 用于处理多行文本的绘制
Layout layout = new StaticLayout(getText(),
paint,// 使用的 Paint 对象
canvas.getWidth(),// 输入的宽度
Layout.Alignment.ALIGN_NORMAL,// 文本对齐方式
getLineSpacingMultiplier(),// 行间距倍数
getLineSpacingExtra(),// 行间距额外值
false);// 是否需要添加额外的文本行
// 在画布上绘制文本
TextPaint paint = getPaint();
paint.setColor(getTextColors().getDefaultColor());
Layout layout = new StaticLayout(getText(), paint, canvas.getWidth(), Layout.Alignment.ALIGN_NORMAL, getLineSpacingMultiplier(), getLineSpacingExtra(), false);
layout.draw(canvas);
}
}

@ -1,270 +1,238 @@
//定义类所在的包路径,表示该文件属于com.monke.monkeybook.widget包
package com.monke.monkeybook.widget;
// 导入必要的类
import android.animation.Animator; // 导入 Animator 类,用于实现动画效果的基础类。
import android.animation.ObjectAnimator; // 导入 ObjectAnimator 类,提供对对象属性的动画效果。
import android.annotation.TargetApi; // 导入 TargetApi 注解,用于指示特定的 API 级别。
import android.content.Context; // 导入 Context 类,用于访问应用的环境信息,如资源、数据库等。
import android.content.res.TypedArray; // 导入 TypedArray 类,帮助从 XML 文件中解析自定义属性。
import android.graphics.Rect; // 导入 Rect 类,用于表示矩形区域,通常用于图形绘制和布局。
import android.os.Build; // 导入 Build 类,提供有关当前 Android 版本的静态信息。
import android.os.CountDownTimer; // 导入 CountDownTimer 类,提供一个倒计时的计时器。
import android.support.annotation.Nullable; // 导入 Nullable 注解,表示某个元素可以为 null。
import android.support.v7.widget.LinearLayoutManager; // 导入 LinearLayoutManager用于 RecyclerView 的线性布局管理。
import android.support.v7.widget.RecyclerView; // 导入 RecyclerView 类,提供高效的视图列表。
import android.util.AttributeSet; // 导入 AttributeSet 类,处理 XML 布局中定义的自定义属性。
import android.view.MotionEvent; // 导入 MotionEvent 类,用于处理触摸事件。
import android.view.View; // 导入 View 类Android 中所有视图控件的基类。
import android.view.ViewGroup; // 导入 ViewGroup 类,所有视图组的基类,用于管理子视图。
import android.view.ViewTreeObserver; // 导入 ViewTreeObserver 类,提供对视图树的观察功能
import android.widget.FrameLayout; // 导入 FrameLayout 类,一个可堆叠子视图的布局。
import android.widget.ImageView; // 导入 ImageView 类,显示图片的控件。
import android.widget.LinearLayout; // 导入 LinearLayout 类,垂直或水平排列子视图的布局。
import com.monke.monkeybook.R; // 导入应用资源类,访问资源(如布局、字符串、颜色等)。
import com.monke.monkeybook.utils.DensityUtil; // 导入 DensityUtil 类,通常用于处理与屏幕密度相关的工具函数。
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Build;
import android.os.CountDownTimer;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.monke.monkeybook.R;
import com.monke.monkeybook.utils.DensityUtil;
public class RecyclerViewBar extends LinearLayout {
// 定义滑动动画的时间常量
public static long SLIDE_ANIM_TIME = 800;
// 定义滑块的 ImageView
private ImageView ivSlider;
// 定义滑块的高度,默认为 35dp
private int sliderHeight = DensityUtil.dp2px(getContext(), 35f);
// 定义 RecyclerView
private RecyclerView recyclerView;
// 定义滑块显示和隐藏的动画对象
private Animator slideIn;
private Animator slideOut;
// 构造方法,初始化
public RecyclerViewBar(Context context) {
this(context, null);
}
// 带有属性集的构造方法
public RecyclerViewBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
// 带有样式属性的构造方法
public RecyclerViewBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);// 调用初始化方法
init(attrs);
}
// 针对 Lollipop 及以上版本的构造方法
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public RecyclerViewBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);// 调用初始化方法
init(attrs);
}
// 初始化方法
private void init(AttributeSet attrs) {
setOrientation(VERTICAL);// 设置垂直布局
// 获取自定义属性
setOrientation(VERTICAL);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RecyclerViewBar);
// 设置滑块高度
sliderHeight = a.getDimensionPixelSize(R.styleable.RecyclerViewBar_slider_height, sliderHeight);
// 设置滑块的左右内边距
int paddingLeft = a.getDimensionPixelSize(R.styleable.RecyclerViewBar_slider_paddingLeft, 0);
int paddingRight = a.getDimensionPixelSize(R.styleable.RecyclerViewBar_slider_paddingRight, 0);
// 创建滑块 ImageView
ivSlider = new ImageView(getContext());
ivSlider.setPadding(paddingLeft, 0, paddingRight, 0);// 设置滑块内边距
ivSlider.setAlpha(0f);// 初始透明度为 0
ivSlider.setClickable(true);// 设置为可点击
addView(ivSlider);// 将 ImageView 添加到布局中
// 设置滑块的布局参数
ivSlider.setPadding(paddingLeft, 0, paddingRight, 0);
ivSlider.setAlpha(0f);
ivSlider.setClickable(true);
addView(ivSlider);
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, sliderHeight);
ivSlider.setLayoutParams(layoutParams);// 应用布局参数
ivSlider.setImageResource(R.drawable.icon_slider);// 设置滑块图标
ivSlider.setScaleType(ImageView.ScaleType.FIT_XY);// 设置图像缩放模式
ivSlider.setLayoutParams(layoutParams);
ivSlider.setImageResource(R.drawable.icon_slider);
ivSlider.setScaleType(ImageView.ScaleType.FIT_XY);
initIvSlider();// 初始化滑块的触摸事件
initIvSlider();
// 添加全局布局监听
RecyclerViewBar.this.getViewTreeObserver().addOnGlobalLayoutListener(layoutInitListener);
}
// 用于记录触摸的 final Y 坐标
private float finalY = -10000;
// 初始化滑块的触摸事件监听
private void initIvSlider() {
// 设置触摸事件监听器
ivSlider.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();// 获取触摸事件类型
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
finalY = event.getY();// 记录初始 Y 坐标
return true;// 消耗事件
finalY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
if (finalY >= 0) {// 检查是否为合法的 Y 坐标
float tempY = event.getY();// 获取当前 Y 坐标
float durY = tempY - finalY;// 计算移动的距离
updateSlider(durY);// 更新滑块位置
if (finalY >= 0) {
float tempY = event.getY();
float durY = tempY - finalY;
updateSlider(durY);
showSlide(); // 显示滑块
showSlide();
} else {
finalY = event.getY();// 更新 finalY
finalY = event.getY();
}
return true;// 消耗事件
return true;
case MotionEvent.ACTION_UP:
if (finalY >= 0) {// 检查是否为合法的 Y 坐标
finalY = -10000;// 重置 finalY
timeCountDown.cancel();// 取消计时器
timeCountDown.start();// 启动计时器
return true;// 消耗事件
if (finalY >= 0) {
finalY = -10000;
timeCountDown.cancel();
timeCountDown.start();
return true;
}
break;
default:
if (finalY >= 0) {
finalY = -10000;// 重置 finalY
return true;// 消耗事件
finalY = -10000;
return true;
}
break;
}
return false;// 未消费事件
return false;
}
});
}
// 更新滑块位置
private void updateSlider(float durY) {
LayoutParams l = (LayoutParams) ivSlider.getLayoutParams();// 获取滑块的布局参数
float finalMarginTop = l.topMargin + durY;// 计算新的顶部边距
// 限制滑块的位置
LayoutParams l = (LayoutParams) ivSlider.getLayoutParams();
float finalMarginTop = l.topMargin + durY;
if (finalMarginTop < 0) {
finalMarginTop = 0;
} else if (finalMarginTop > getHeight() - sliderHeight) {
finalMarginTop = getHeight() - sliderHeight;
}
// 如果 RecyclerView 不为空
if (recyclerView != null) {
// 计算 RecyclerView 的滚动位置
int position = Math.round(finalMarginTop / (getHeight() - sliderHeight) * (recyclerView.getAdapter().getItemCount() - 1));
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(position, 0);// 滚动到指定位置
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(position, 0);
}
l.topMargin = Math.round(finalMarginTop);// 更新滑块的顶部边距
ivSlider.setLayoutParams(l);// 应用新的布局参数
l.topMargin = Math.round(finalMarginTop);
ivSlider.setLayoutParams(l);
}
// 设置 RecyclerView
public void setRecyclerView(RecyclerView recyclerView) {
this.recyclerView = recyclerView;// 保存 RecyclerView 的引用
this.recyclerView = recyclerView;
if (this.recyclerView != null) {
// 添加滚动监听
this.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
// 如果滚动状态不为停止,则显示滑块
super.onScrollStateChanged(recyclerView, newState);
if (newState != 0) {
showSlide();
} else {
timeCountDown.cancel();// 停止计时器
timeCountDown.start();// 启动计时器
timeCountDown.cancel();
timeCountDown.start();
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// 将滑块滚动到当前可见项目的位置
scrollToPositionWithOffset(((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition());
}
});
}
}
// 根据位置滚动并更新滑块位置
public void scrollToPositionWithOffset(int position) {
// 如果 RecyclerView 存在且位置合法
if (recyclerView != null && position < recyclerView.getAdapter().getItemCount()) {
float temp = position * 1.0f / recyclerView.getAdapter().getItemCount();
LayoutParams l = (LayoutParams) ivSlider.getLayoutParams();// 获取滑块的布局参数
l.topMargin = Math.round(((getHeight() - sliderHeight) * temp));// 计算新的顶部边距
ivSlider.setLayoutParams(l);// 应用新的布局参数
LayoutParams l = (LayoutParams) ivSlider.getLayoutParams();
l.topMargin = Math.round(((getHeight() - sliderHeight) * temp));
ivSlider.setLayoutParams(l);
}
}
// 显示滑块
private void showSlide() {
if (ivSlider.getAlpha() < 1) {// 如果透明度小于1
if (ivSlider.getAlpha() < 1) {
if (slideOut != null && slideOut.isRunning()) {
slideOut.cancel();// 如果滑出动画正在执行,取消它
slideOut.cancel();
}
if (slideIn == null) {
slideIn = ObjectAnimator.ofFloat(ivSlider, "alpha", ivSlider.getAlpha(), 1f);// 创建滑入动画
slideIn.setDuration((long) (SLIDE_ANIM_TIME * (1f - ivSlider.getAlpha())));// 设置动画持续时间
slideIn = ObjectAnimator.ofFloat(ivSlider, "alpha", ivSlider.getAlpha(), 1f);
slideIn.setDuration((long) (SLIDE_ANIM_TIME * (1f - ivSlider.getAlpha())));
}
if (!slideIn.isRunning()) {
slideIn.start();// 启动滑入动画
slideIn.start();
}
}
}
// 隐藏滑块
private void hideSlide() {
if (ivSlider.getAlpha() > 0) {// 如果透明度大于0
if (ivSlider.getAlpha() > 0) {
if (slideIn != null && slideIn.isRunning()) {
slideIn.cancel();// 如果滑入动画正在执行,取消它
slideIn.cancel();
}
if (slideOut == null) {
slideOut = ObjectAnimator.ofFloat(ivSlider, "alpha", ivSlider.getAlpha(), 0f);// 创建滑出动画
slideOut.setDuration((long) (SLIDE_ANIM_TIME * ivSlider.getAlpha()));// 设置动画持续时间
slideOut = ObjectAnimator.ofFloat(ivSlider, "alpha", ivSlider.getAlpha(), 0f);
slideOut.setDuration((long) (SLIDE_ANIM_TIME * ivSlider.getAlpha()));
}
if (!slideOut.isRunning()) {
slideOut.start();// 启动滑出动画
slideOut.start();
}
}
}
// 创建计时器,用于滑块的自动隐藏
private TimeCountDown timeCountDown = new TimeCountDown();
// 定义倒计时类
class TimeCountDown extends CountDownTimer {
// 构造方法,设置计时器
public TimeCountDown() {
this(1000, 1000);// 1000 毫秒后完成,间隔 1000 毫秒
this(1000, 1000);
}
public TimeCountDown(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);// 调用父类构造方法
super(millisInFuture, countDownInterval);
}
@Override
public void onTick(long millisUntilFinished) {
// 每次计时器滴答时调用(可选实现,暂时为空)
}
@Override
public void onFinish() {
hideSlide();// 计时结束时隐藏滑块
hideSlide();
}
}
// 用于存储视图的高度
private int height = 0;
// 全局布局监听器
private ViewTreeObserver.OnGlobalLayoutListener layoutInitListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if(getHeight()>0){// 确保当前高度大于0
if(getHeight()>0){
if (height == 0) {
height = getHeight();// 保存初始高度
height = getHeight();
} else {
int diff = height - getHeight();// 计算高度差
int diff = height - getHeight();
if (diff != 0) {
LayoutParams l = (LayoutParams) ivSlider.getLayoutParams();// 获取滑块的布局参数
LayoutParams l = (LayoutParams) ivSlider.getLayoutParams();
l.topMargin = (int) ((l.topMargin*1.0f/(height-sliderHeight))*(getHeight()-sliderHeight));
ivSlider.setLayoutParams(l);// 更新滑块的顶部边距
height = getHeight();// 更新高度
ivSlider.setLayoutParams(l);
height = getHeight();
}
}
}

@ -1,45 +1,40 @@
package com.monke.monkeybook.widget.refreshview; // 定义包名
package com.monke.monkeybook.widget.refreshview;
import android.content.Context; // 导入 Context 类,提供应用的上下文
import android.content.res.TypedArray; // 导入 TypedArray 类,用于处理自定义属性
import android.support.v7.widget.LinearLayoutManager; // 导入 LinearLayoutManager 类,用于线性布局管理器
import android.support.v7.widget.RecyclerView; // 导入 RecyclerView 类,支持高效的列表展示
import android.util.AttributeSet; // 导入 AttributeSet 类,用于访问 XML 布局中的自定义属性
import android.view.LayoutInflater; // 导入 LayoutInflater 类,用于将布局文件转换为 View 对象
import android.view.MotionEvent; // 导入 MotionEvent 类,处理触摸事件
import android.view.View; // 导入 View 类,所有 UI 控件的基类
import android.widget.FrameLayout; // 导入 FrameLayout 类,按层次排列子视图
import android.content.Context;
import android.content.res.TypedArray;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import com.monke.monkeybook.R; // 导入应用资源类,访问资源文件
import com.monke.monkeybook.R;
// 创建 RefreshRecyclerView 类,继承自 FrameLayout
public class RefreshRecyclerView extends FrameLayout {
private View view; // 根视图
private RefreshProgressBar rpb; // 自定义进度条
private RecyclerView recyclerView; // RecyclerView 控件
private View view;
private RefreshProgressBar rpb;
private RecyclerView recyclerView;
private View noDataView; // 无数据视图
private View refreshErrorView; // 刷新错误视图
private View noDataView;
private View refreshErrorView;
// 构造函数
public RefreshRecyclerView(Context context) {
this(context, null); // 调用带有 AttributeSet 的构造函数
this(context, null);
}
public RefreshRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0); // 调用带有默认样式的构造函数
this(context, attrs, 0);
}
public RefreshRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 从 XML 布局中加载视图
view = LayoutInflater.from(context).inflate(R.layout.view_refresh_recyclerview, this, false);
// 初始化 UI 组件
rpb = (RefreshProgressBar) view.findViewById(R.id.rpb);
recyclerView = (RecyclerView) view.findViewById(R.id.rv);
// 获取自定义属性并设置给 RefreshProgressBar
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RefreshProgressBar);
rpb.setSpeed(a.getDimensionPixelSize(R.styleable.RefreshProgressBar_speed, rpb.getSpeed()));
rpb.setMaxProgress(a.getInt(R.styleable.RefreshProgressBar_max_progress, rpb.getMaxProgress()));
@ -47,253 +42,235 @@ public class RefreshRecyclerView extends FrameLayout {
rpb.setBgColor(a.getColor(R.styleable.RefreshProgressBar_bg_color, rpb.getBgColor()));
rpb.setSecondColor(a.getColor(R.styleable.RefreshProgressBar_second_color, rpb.getSecondColor()));
rpb.setFontColor(a.getColor(R.styleable.RefreshProgressBar_font_color, rpb.getFontColor()));
a.recycle(); // 释放 TypedArray
a.recycle();
bindEvent(); // 绑定事件
addView(view); // 将根视图添加到 FrameLayout 中
bindEvent();
addView(view);
}
private float durTouchY = -1000000; // 记录触摸 Y 坐标
private BaseRefreshListener baseRefreshListener; // 刷新监听器
private float durTouchY = -1000000;
private BaseRefreshListener baseRefreshListener;
// 设置刷新监听器
public void setBaseRefreshListener(BaseRefreshListener baseRefreshListener) {
this.baseRefreshListener = baseRefreshListener;
}
private OnLoadMoreListener loadMoreListener; // 加载更多监听器
private OnLoadMoreListener loadMoreListener;
// 设置加载更多监听器
public void setLoadMoreListener(OnLoadMoreListener loadMoreListener) {
this.loadMoreListener = loadMoreListener;
}
// 绑定事件
private void bindEvent() {
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState); // 调用父类方法
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy); // 调用父类方法
// 判断是否可以加载更多
if (((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).canLoadMore() &&
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).getItemCount() - 1
== ((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition()) {
// 如果没有加载错误,开始加载更多
if (!((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).getLoadMoreError()) {
super.onScrolled(recyclerView, dx, dy);
if (((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).canLoadMore() && ((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).getItemCount() - 1 == ((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition()) {
if(!((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).getLoadMoreError()){
if (null != loadMoreListener) {
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(2, false); // 设置为正在加载更多状态
loadMoreListener.startLoadmore(); // 调用加载更多方法
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(2, false);
loadMoreListener.startLoadmore();
}
}
}
}
});
recyclerView.setOnTouchListener(refreshTouchListener); // 设置触摸事件监听器
recyclerView.setOnTouchListener(refreshTouchListener);
}
// 获取自定义进度条
public RefreshProgressBar getRpb() {
return rpb;
}
// 获取 RecyclerView
public RecyclerView getRecyclerView() {
return recyclerView;
}
// 处理刷新错误
public void refreshError() {
rpb.setIsAutoLoading(false); // 设置为非自动加载状态
rpb.clean(); // 清空进度
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(0, true); // 设置请求状态为未请求
rpb.setIsAutoLoading(false);
rpb.clean();
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(0, true);
if (noDataView != null) {
noDataView.setVisibility(GONE); // 隐藏无数据视图
noDataView.setVisibility(GONE);
}
if (refreshErrorView != null) {
refreshErrorView.setVisibility(VISIBLE); // 显示刷新错误视图
refreshErrorView.setVisibility(VISIBLE);
}
}
// 开始刷新
public void startRefresh() {
if (baseRefreshListener != null && baseRefreshListener instanceof OnRefreshWithProgressListener) {
// 带有进度的刷新
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsAll(false, false); // 设置还有更多数据
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(1, false); // 设置为正在刷新状态
rpb.setSecondDurProgress(rpb.getSecondMaxProgress()); // 设置当前进度为最大进度
rpb.setMaxProgress(((OnRefreshWithProgressListener) baseRefreshListener).getMaxProgress()); // 获取最大进度
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsAll(false, false);
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(1, false);
rpb.setSecondDurProgress(rpb.getSecondMaxProgress());
rpb.setMaxProgress(((OnRefreshWithProgressListener) baseRefreshListener).getMaxProgress());
} else {
// 非带进度的刷新
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(1, true); // 设置为正在刷新状态
rpb.setIsAutoLoading(true); // 设置为自动加载
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(1, true);
rpb.setIsAutoLoading(true);
if (noDataView != null) {
noDataView.setVisibility(GONE); // 隐藏无数据视图
noDataView.setVisibility(GONE);
}
if (refreshErrorView != null) {
refreshErrorView.setVisibility(GONE); // 隐藏刷新错误视图
refreshErrorView.setVisibility(GONE);
}
}
}
// 完成刷新
public void finishRefresh(Boolean needNoti) {
finishRefresh(((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).getItemcount() == 0, needNoti); // 判断是否有数据并完成刷新
finishRefresh(((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).getItemcount() == 0, needNoti);
}
// 完成刷新,判断是否还有更多数据
public void finishRefresh(Boolean isAll, Boolean needNoti) {
rpb.setDurProgress(0); // 重置进度条
rpb.setDurProgress(0);
if (isAll) {
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(0, false); // 设置请求状态为未请求
rpb.setIsAutoLoading(false); // 设置为非自动加载状态
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsAll(isAll, needNoti); // 设置没有更多数据
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(0, false);
rpb.setIsAutoLoading(false);
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsAll(isAll, needNoti);
} else {
rpb.setIsAutoLoading(false); // 设置为非自动加载状态
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(0, needNoti); // 设置请求状态为未请求
rpb.setIsAutoLoading(false);
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(0, needNoti);
}
// 根据数据情况显示无数据视图
if (isAll) {
if (noDataView != null) {
if (((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).getItemcount() == 0)
noDataView.setVisibility(VISIBLE); // 如果没有数据,则显示无数据视图
noDataView.setVisibility(VISIBLE);
else
noDataView.setVisibility(GONE); // 否则隐藏无数据视图
noDataView.setVisibility(GONE);
}
if (refreshErrorView != null) {
refreshErrorView.setVisibility(GONE); // 隐藏刷新错误视图
refreshErrorView.setVisibility(GONE);
}
}
}
// 完成加载更多
public void finishLoadMore(Boolean isAll, Boolean needNoti) {
if (isAll) {
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(0, false); // 设置请求状态为未请求
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsAll(isAll, needNoti); // 设置没有更多数据
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(0, false);
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsAll(isAll, needNoti);
} else {
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(0, needNoti); // 设置请求状态为未请求
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(0, needNoti);
}
if (noDataView != null) {
noDataView.setVisibility(GONE); // 隐藏无数据视图
noDataView.setVisibility(GONE);
}
if (refreshErrorView != null) {
refreshErrorView.setVisibility(GONE); // 隐藏刷新错误视图
refreshErrorView.setVisibility(GONE);
}
}
// 设置适配器
public void setRefreshRecyclerViewAdapter(RefreshRecyclerViewAdapter refreshRecyclerViewAdapter, RecyclerView.LayoutManager layoutManager) {
refreshRecyclerViewAdapter.setClickTryAgainListener(new RefreshRecyclerViewAdapter.OnClickTryAgainListener() {
@Override
public void loadMoreErrorTryAgain() {
if (loadMoreListener != null)
loadMoreListener.loadMoreErrorTryAgain(); // 处理加载更多错误重试
loadMoreListener.loadMoreErrorTryAgain();
}
});
recyclerView.setLayoutManager(layoutManager); // 设置布局管理器
recyclerView.setAdapter(refreshRecyclerViewAdapter); // 设置适配器
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(refreshRecyclerViewAdapter);
}
// 加载更多错误
public void loadMoreError() {
rpb.setIsAutoLoading(false); // 设置为非自动加载状态
rpb.clean(); // 清空进度
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setLoadMoreError(true, true); // 设置加载错误状态
rpb.setIsAutoLoading(false);
rpb.clean();
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setLoadMoreError(true, true);
}
// 设置无数据视图和刷新错误视图
public void setNoDataAndrRefreshErrorView(View noData, View refreshError) {
if (noData != null) {
noDataView = noData; // 设置无数据视图
noDataView.setVisibility(GONE); // 隐藏无数据视图
addView(noDataView, getChildCount() - 1); // 将视图添加到 FrameLayout 中
noDataView = noData;
// noDataView.setOnTouchListener(refreshTouchListener);
noDataView.setVisibility(GONE);
addView(noDataView, getChildCount() - 1);
}
if (refreshError != null) {
refreshErrorView = refreshError; // 设置刷新错误视图
addView(refreshErrorView, 2); // 将视图添加到 FrameLayout 中
refreshErrorView.setVisibility(GONE); // 隐藏刷新错误视图
refreshErrorView = refreshError;
// refreshErrorView.setOnTouchListener(refreshTouchListener);
addView(refreshErrorView, 2);
refreshErrorView.setVisibility(GONE);
}
}
private OnTouchListener refreshTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction(); // 获取触摸事件动作
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: // 触摸开始时
durTouchY = event.getY(); // 记录 Y 坐标
case MotionEvent.ACTION_DOWN:
durTouchY = event.getY();
break;
case MotionEvent.ACTION_MOVE: // 触摸移动时
case MotionEvent.ACTION_MOVE:
if (durTouchY == -1000000)
durTouchY = event.getY(); // 初始化 Y 坐标
float dY = event.getY() - durTouchY; // 计算下拉距离
durTouchY = event.getY(); // 更新 Y 坐标
// 判断是否可以刷新
durTouchY = event.getY();
float dY = event.getY() - durTouchY; //>0下拉
durTouchY = event.getY();
if (baseRefreshListener != null && ((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).getIsRequesting() == 0 && rpb.getSecondDurProgress() == rpb.getSecondFinalProgress()) {
if (rpb.getVisibility() != View.VISIBLE) {
rpb.setVisibility(View.VISIBLE); // 显示进度条
rpb.setVisibility(View.VISIBLE);
}
if (recyclerView.getAdapter().getItemCount() > 0) {
if (0 == ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition()) {
rpb.setSecondDurProgress((int) (rpb.getSecondDurProgress() + dY)); // 更新进度
rpb.setSecondDurProgress((int) (rpb.getSecondDurProgress() + dY));
}
} else {
rpb.setSecondDurProgress((int) (rpb.getSecondDurProgress() + dY)); // 更新进度
rpb.setSecondDurProgress((int) (rpb.getSecondDurProgress() + dY));
}
if (rpb.getSecondDurProgress() <= 0) {
return false; // 返回 false 继续处理
return false;
} else {
return true; // 返回 true 终止触摸事件继续处理
return true;
}
}
break;
case MotionEvent.ACTION_UP: // 触摸抬起时
// 判断是否能开始刷新
case MotionEvent.ACTION_UP:
if (baseRefreshListener != null && rpb.getSecondMaxProgress() > 0 && rpb.getSecondDurProgress() > 0) {
if (rpb.getSecondDurProgress() >= rpb.getSecondMaxProgress() && ((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).getIsRequesting() == 0) {
if (baseRefreshListener instanceof OnRefreshWithProgressListener) {
// 带有进度的刷新
//带有进度的
//执行刷新响应
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsAll(false, false);
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(1, true); // 设置为正在刷新状态
rpb.setMaxProgress(((OnRefreshWithProgressListener) baseRefreshListener).getMaxProgress()); // 获取最大进度
baseRefreshListener.startRefresh(); // 执行刷新
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(1, true);
rpb.setMaxProgress(((OnRefreshWithProgressListener) baseRefreshListener).getMaxProgress());
baseRefreshListener.startRefresh();
if (noDataView != null) {
noDataView.setVisibility(GONE); // 隐藏无数据视图
noDataView.setVisibility(GONE);
}
if (refreshErrorView != null) {
refreshErrorView.setVisibility(GONE); // 隐藏刷新错误视图
refreshErrorView.setVisibility(GONE);
}
} else {
// 不带进度的刷新
//不带进度的
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsAll(false, false);
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(1, true); // 设置为正在刷新状态
baseRefreshListener.startRefresh(); // 执行刷新
((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).setIsRequesting(1, true);
baseRefreshListener.startRefresh();
if (noDataView != null) {
noDataView.setVisibility(GONE); // 隐藏无数据视图
noDataView.setVisibility(GONE);
}
if (refreshErrorView != null) {
refreshErrorView.setVisibility(GONE); // 隐藏刷新错误视图
refreshErrorView.setVisibility(GONE);
}
rpb.setIsAutoLoading(true); // 设置为自动加载状态
rpb.setIsAutoLoading(true);
}
} else {
if (((RefreshRecyclerViewAdapter) recyclerView.getAdapter()).getIsRequesting() != 1)
rpb.setSecondDurProgressWithAnim(0); // 动画归零
rpb.setSecondDurProgressWithAnim(0);
}
}
durTouchY = -1000000; // 重置 Y 坐标
durTouchY = -1000000;
break;
}
return false; // 返回 false 继续处理触摸事件
return false;
}
};
}

@ -1,204 +1,177 @@
// 定义包名
package com.monke.monkeybook.widget.refreshview;
import android.os.Handler; // 导入 Handler 类,用于处理线程间通信和执行任务
import android.os.Looper; // 导入 Looper 类,用于控制线程的消息循环
import android.support.v7.widget.RecyclerView; // 导入 RecyclerView 类,支持高效的列表展示
import android.util.Log; // 导入 Log 类,用于调试日志
import android.view.LayoutInflater; // 导入 LayoutInflater 类,用于将布局文件转换为 View 对象
import android.view.View; // 导入 View 类,所有 UI 控件的基类
import android.view.ViewGroup; // 导入 ViewGroup 类,视图的容器类
import android.widget.FrameLayout; // 导入 FrameLayout 类,允许堆叠子视图的布局
import android.widget.TextView; // 导入 TextView 类,用于显示文本
import android.os.Handler;
import android.os.Looper;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.monke.monkeybook.R; // 导入应用资源类,访问资源文件
import com.monke.monkeybook.R;
// 创建抽象类 RefreshRecyclerViewAdapter继承自 RecyclerView.Adapter
public abstract class RefreshRecyclerViewAdapter extends RecyclerView.Adapter {
private final int LOADMORETYPE = 2001; // 加载更多的视图类型
private final int LOADMORETYPE = 2001;
private Handler handler; // 处理线程间通信的 Handler
private int isRequesting = 0; // 当前请求状态: 0-未请求1-正在下拉刷新2-正在加载更多
private Boolean needLoadMore = false; // 是否需要加载更多
private Boolean isAll = false; // 判断是否还有更多数据
private Boolean loadMoreError = false; // 判断是否加载更多时发生错误
private Handler handler;
private int isRequesting = 0; //0是未执行网络请求 1是正在下拉刷新 2是正在加载更多
private Boolean needLoadMore = false;
private Boolean isAll = false; //判断是否还有更多
private Boolean loadMoreError = false;
private OnClickTryAgainListener clickTryAgainListener; // 加载失败重试监听器接口
private OnClickTryAgainListener clickTryAgainListener;
// 点击重试的监听器接口
public interface OnClickTryAgainListener {
public void loadMoreErrorTryAgain();// 加载失败时的重试方法
public void loadMoreErrorTryAgain();
}
// 构造函数
public RefreshRecyclerViewAdapter(Boolean needLoadMore) {
this.needLoadMore = needLoadMore; // 设置是否需要加载更多
handler = new Handler(); // 初始化 Handler
this.needLoadMore = needLoadMore;
handler = new Handler();
}
// 获取请求状态
public int getIsRequesting() {
return isRequesting; // 返回当前请求状态
return isRequesting;
}
// 设置请求状态
public void setIsRequesting(int isRequesting, Boolean needNoti) {
this.isRequesting = isRequesting; // 更新请求状态
if (this.isRequesting == 1) { // 如果正在请求刷新
isAll = false; // 设置为没有更多数据
this.isRequesting = isRequesting;
if (this.isRequesting == 1) {
isAll = false;
}
// 根据需要通知数据变化
if (needNoti) {
if (Looper.myLooper() == Looper.getMainLooper()) {
notifyItemRangeChanged(getItemCount(), getItemCount() - getItemcount()); // 通知数据范围变化
notifyItemRangeChanged(getItemCount(), getItemCount() - getItemcount());
} else {
handler.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged(); // 在主线程中通知数据已更新
notifyDataSetChanged();
}
});
}
}
}
// 创建视图持有者
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == LOADMORETYPE) { // 如果是加载更多类型
if (viewType == LOADMORETYPE) {
return new LoadMoreViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.view_refresh_loadmore, parent, false));
} else {
return onCreateViewholder(parent, viewType); // 否则调用抽象方法创建其他类型的视图持有者
}
} else
return onCreateViewholder(parent, viewType);
}
// 抽象方法,创建其他类型的视图持有者
public abstract RecyclerView.ViewHolder onCreateViewholder(ViewGroup parent, int viewType);
// 绑定视图数据
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == LOADMORETYPE) { // 如果是加载更多视图
if (holder.getItemViewType() == LOADMORETYPE) {
if (!loadMoreError) {
((LoadMoreViewHolder) holder).tvLoadMore.setText("正在加载..."); // 显示加载中
((LoadMoreViewHolder) holder).tvLoadMore.setText("正在加载...");
} else {
((LoadMoreViewHolder) holder).tvLoadMore.setText("加载失败,点击重试"); // 显示加载失败信息
((LoadMoreViewHolder) holder).tvLoadMore.setText("加载失败,点击重试");
}
// 设置点击事件
((LoadMoreViewHolder) holder).tvLoadMore.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != clickTryAgainListener && loadMoreError) {
clickTryAgainListener.loadMoreErrorTryAgain(); // 调用重试方法
loadMoreError = false; // 重置加载错误状态
((LoadMoreViewHolder) holder).tvLoadMore.setText("正在加载..."); // 更新加载状态
clickTryAgainListener.loadMoreErrorTryAgain();
loadMoreError = false;
((LoadMoreViewHolder) holder).tvLoadMore.setText("正在加载...");
}
}
});
} else {
onBindViewholder(holder, position); // 绑定其他类型视图的数据
}
} else
onBindViewholder(holder, position);
}
// 抽象方法,绑定其他类型视图的数据
public abstract void onBindViewholder(RecyclerView.ViewHolder holder, int position);
// 获取视图类型
@Override
public int getItemViewType(int position) {
if (needLoadMore && isRequesting != 1 && !isAll && position == getItemCount() - 1 && getItemcount() > 0) {
return LOADMORETYPE; // 如果满足条件,返回加载更多视图类型
return LOADMORETYPE;
} else {
return getItemViewtype(position); // 否则返回其他视图类型
return getItemViewtype(position);
}
}
// 抽象方法,获取其他类型视图的类型
public abstract int getItemViewtype(int position);
// 获取项计数
@Override
public int getItemCount() {
if (needLoadMore && isRequesting != 1 && !isAll && getItemcount() > 0) {
return getItemcount() + 1; // 如果需要加载更多,返回总数 + 1加载更多视图
} else {
return getItemcount(); // 返回正常的项计数
}
return getItemcount() + 1;
} else
return getItemcount();
}
public abstract int getItemcount(); // 抽象方法,获取正常的项计数
public abstract int getItemcount();
// 设置是否有更多数据
public void setIsAll(Boolean isAll, Boolean needNoti) {
this.isAll = isAll; // 更新是否有更多数据
// 根据需要通知数据变化
this.isAll = isAll;
if (needNoti) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// notifyItemRangeChanged(getItemCount(),getItemCount()-getItemcount());
if (getItemCount() > getItemcount()) {
notifyItemRangeChanged(getItemCount(), getItemCount() - getItemcount()); // 通知数据范围变化
} else {
notifyItemRemoved(getItemCount() + 1); // 通知加载更多视图移除
}
notifyItemRangeChanged(getItemCount(), getItemCount() - getItemcount());
} else
notifyItemRemoved(getItemCount() + 1);
} else {
handler.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged(); // 在主线程中通知数据已更新
notifyDataSetChanged();
}
});
}
}
}
// 加载更多视图持有者类
class LoadMoreViewHolder extends RecyclerView.ViewHolder {
FrameLayout llLoadMore; // 加载更多的容器
TextView tvLoadMore; // 加载更多的文本视图
FrameLayout llLoadMore;
TextView tvLoadMore;
// 构造函数
public LoadMoreViewHolder(View itemView) {
super(itemView);
llLoadMore = (FrameLayout) itemView.findViewById(R.id.ll_loadmore); // 获取加载更多的容器
tvLoadMore = (TextView) itemView.findViewById(R.id.tv_loadmore); // 获取加载更多文本视图
llLoadMore = (FrameLayout) itemView.findViewById(R.id.ll_loadmore);
tvLoadMore = (TextView) itemView.findViewById(R.id.tv_loadmore);
}
}
// 判断是否可以加载更多
public Boolean canLoadMore() {
return needLoadMore && isRequesting == 0 && !isAll && getItemcount() > 0; // 返回是否满足加载更多条件
return needLoadMore && isRequesting == 0 && !isAll && getItemcount() > 0;
}
// 获取重试监听器
public OnClickTryAgainListener getClickTryAgainListener() {
return clickTryAgainListener; // 返回重试监听器
return clickTryAgainListener;
}
// 设置重试监听器
public void setClickTryAgainListener(OnClickTryAgainListener clickTryAgainListener) {
this.clickTryAgainListener = clickTryAgainListener; // 赋值给重试监听器
this.clickTryAgainListener = clickTryAgainListener;
}
// 获取加载更多错误状态
public Boolean getLoadMoreError() {
return loadMoreError; // 返回加载更多错误状态
return loadMoreError;
}
// 设置加载更多错误
public void setLoadMoreError(Boolean loadMoreError, Boolean needNoti) {
this.isRequesting = 0; // 重置请求状态
this.loadMoreError = loadMoreError; // 设置加载错误状态
// 根据需要通知数据变化
this.isRequesting = 0;
this.loadMoreError = loadMoreError;
if (needNoti) {
if (Looper.myLooper() == Looper.getMainLooper()) {
notifyDataSetChanged(); // 在主线程中通知数据已更新
notifyDataSetChanged();
} else {
handler.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged(); // 在主线程中通知数据已更新
notifyDataSetChanged();
}
});
}
}
}
}
}

@ -1,117 +1,101 @@
// 定义包名
package com.monke.monkeybook.widget.refreshview;
import android.annotation.TargetApi; // 导入 TargetApi 注解,标明特定的 API 级别
import android.content.Context; // 导入 Context 类,提供应用的上下文
import android.os.Build; // 导入 Build 类,用于获取当前 Android 版本信息
import android.support.annotation.NonNull; // 导入 NonNull 注解,表示不允许为 null
import android.util.AttributeSet; // 导入 AttributeSet 类,用于访问 XML 布局中的自定义属性
import android.view.MotionEvent; // 导入 MotionEvent 类,处理触摸事件
import android.view.View; // 导入 View 类,所有 UI 控件的基类
import android.widget.ScrollView; // 导入 ScrollView 类,支持滚动的布局
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ScrollView;
// 创建 RefreshScrollView 类,继承自 ScrollView
public class RefreshScrollView extends ScrollView{
private RefreshProgressBar rpb; // 自定义的刷新进度条
private float durTouchY = -1000000; // 记录触摸Y坐标
private BaseRefreshListener baseRefreshListener; // 刷新监听器实例
private Boolean isRefreshing = false; // 是否正在刷新
private OnTouchListener touchListener; // 触摸事件监听器
private RefreshProgressBar rpb;
private float durTouchY = -1000000;
private BaseRefreshListener baseRefreshListener;
private Boolean isRefreshing = false;
private OnTouchListener touchListener;
// 构造函数
public RefreshScrollView(Context context) {
this(context,null);// 调用带有 AttributeSet 的构造函数
this(context,null);
}
public RefreshScrollView(Context context, AttributeSet attrs) {
this(context, attrs,0);// 调用带有风格属性的构造函数
this(context, attrs,0);
}
public RefreshScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);// 调用父类构造函数
super(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)// 指示支持 API 21 及以上
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public RefreshScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);// 调用父类构造函数
super(context, attrs, defStyleAttr, defStyleRes);
}
// 设置刷新进度条
public void setRpb(@NonNull RefreshProgressBar rpb) {
this.rpb = rpb; // 赋值给 rpb
init(); // 初始化
this.rpb = rpb;
init();
}
// 初始化方法
private void init() {
// 设置触摸事件监听器
touchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction(); // 获取当前触摸事件的动作
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: // 触摸开始时
durTouchY = event.getY(); // 记录初始触摸Y坐标
case MotionEvent.ACTION_DOWN:
durTouchY = event.getY();
break;
case MotionEvent.ACTION_MOVE: // 触摸移动时
if (durTouchY == -1000000) // 确保初始触摸坐标已记录
durTouchY = event.getY(); // 更新触摸Y坐标
float dY = event.getY() - durTouchY; // 计算移动的Y距离向下则为正值
durTouchY = event.getY(); // 更新初始触摸坐标
// 如果满足刷新条件
if (baseRefreshListener != null && !isRefreshing && rpb.getSecondDurProgress() == rpb.getSecondFinalProgress() && getScrollY() <= 0) {
// 设置刷新进度条可见
case MotionEvent.ACTION_MOVE:
if (durTouchY == -1000000)
durTouchY = event.getY();
float dY = event.getY() - durTouchY; //>0下拉
durTouchY = event.getY();
if (baseRefreshListener != null && !isRefreshing && rpb.getSecondDurProgress() == rpb.getSecondFinalProgress() && getScrollY()<=0) {
if (rpb.getVisibility() != View.VISIBLE) {
rpb.setVisibility(View.VISIBLE);
}
// 更新进度条的当前进度
rpb.setSecondDurProgress((int) (rpb.getSecondDurProgress() + dY));
// 判断当前进度是否小于等于0
if (rpb.getSecondDurProgress() <= 0) {
return false; // 返回 false 以允许 ScrollView 处理事件
return false;
} else {
return true; // 返回 true 表示事件被处理
return true;
}
}
break;
case MotionEvent.ACTION_UP: // 触摸抬起时
// 如果满足刷新条件且当前进度大于0
case MotionEvent.ACTION_UP:
if (baseRefreshListener != null && rpb.getSecondMaxProgress() > 0 && rpb.getSecondDurProgress() > 0) {
// 如果当前进度达到最大值且不在刷新状态,则开始刷新
if (rpb.getSecondDurProgress() >= rpb.getSecondMaxProgress() && !isRefreshing) {
startRefresh();
} else {
// 否则,将进度条动画归零
rpb.setSecondDurProgressWithAnim(0);
}
}
durTouchY = -1000000; // 重置触摸坐标
durTouchY = -1000000;
break;
}
return false; // 事件未完全处理,允许 ScrollView 继续处理
return false;
}
};
this.setOnTouchListener(touchListener); // 设置触摸监听器
this.setOnTouchListener(touchListener);
}
// 设置刷新监听器
public void setBaseRefreshListener(BaseRefreshListener baseRefreshListener) {
this.baseRefreshListener = baseRefreshListener; // 赋值给监听器
this.baseRefreshListener = baseRefreshListener;
}
// 开始刷新
public void startRefresh() {
if (baseRefreshListener != null) {
isRefreshing = true; // 设置为正在刷新状态
rpb.setIsAutoLoading(true); // 设置进度条为自动加载状态
baseRefreshListener.startRefresh(); // 调用监听器的刷新方法
public void startRefresh(){
if(baseRefreshListener!=null){
isRefreshing = true;
rpb.setIsAutoLoading(true);
baseRefreshListener.startRefresh();
}
}
// 完成刷新
public void finishRefresh() {
isRefreshing = false; // 设置为未刷新状态
rpb.setDurProgress(0); // 重置进度条当前进度
rpb.setIsAutoLoading(false); // 退出自动加载状态
public void finishRefresh(){
isRefreshing = false;
rpb.setDurProgress(0);
rpb.setIsAutoLoading(false);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save