基于rxjava实现的小说练习项目

界面展示

目录界面

目录界面

章节界面

章节界面

使用框架

room访问数据库
navigation界面导航
okhttp网络请求
viewmodel管理数据
jsoup网络爬虫
rxjava线程优雅

使用技术

stream优雅处理流

代码展示

github: https://github.com/15029291643/novel2

项目详解

添加依赖

// room
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// optional - RxJava2 support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// rxjava
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.9'
// retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.2'
// jsoup
implementation 'org.jsoup:jsoup:1.13.1'
// Navigation
implementation "androidx.navigation:navigation-fragment:2.3.5"
implementation "androidx.navigation:navigation-ui:2.3.5"

根据逻辑划分包

在这里插入图片描述

实现logic逻辑层

判断可行性

爬取链接

目录:蛇王诅咒:妈咪要下蛋
章节:解剖双头蛇王

链接数据

目录界面F12进入
在这里插入图片描述
可以从页面源代码中直接获取到对应章节1标题3链接2
章节界面F12进入
在这里插入图片描述
可以很轻松的在源代码中找到章节13对应的标题2内容4
显而易见,数据可以轻松得到,只要我们拥有一个Jsoup爬虫框架
在这之前先生成两个数据类

实现Model层

在这里插入图片描述

Chapter

import java.util.List;

public class Chapter {
    private String title;
    private String url;
    private List<String> content;

    public Chapter() {
    }

    public Chapter(String title, String url) {
        this.title = title;
        this.url = url;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public List<String> getContent() {
        return content;
    }

    public void setContent(List<String> content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Chapter{" +
                "title='" + title + '\'' +
                ", url='" + url + '\'' +
                ", content=" + content +
                '}';
    }
}

Novel

import java.util.List;

public class Novel {
    private String title;
    private String url;
    private List<Chapter> catalog;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public List<Chapter> getCatalog() {
        return catalog;
    }

    public void setCatalog(List<Chapter> catalog) {
        this.catalog = catalog;
    }

    @Override
    public String toString() {
        return "Novel{" +
                "title='" + title + '\'' +
                ", url='" + url + '\'' +
                ", catalog=" + catalog +
                '}';
    }
}

实现网络请求层

在这里插入图片描述

ConstantUtils

public interface ConstantUtils {
    String NOVEL_URL = "https://www.258xswz.com/l/6791.html";
    String CHAPTER_URL = "https://www.258xswz.com/y/6791-2976071.html";
    String BASE_URL = "https://www.258xswz.com/";
}

import com.example.rxjava.logic.model.Chapter;
import com.example.rxjava.logic.model.Novel;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/*
* Jsoup工具类
* 处理html数据
* */
public class JsoupUtils {
    private static final String TAG = "JsoupUtils";

    // 从html中获取Novel对象
    public static Novel getNovel(String html) {
        // 使用xpath解析数据
        Document parse = Jsoup.parse(html);
        String title = parse.selectFirst("body > div.chapterlist > h2 > strong").text();
        Elements elements = parse.select("#myarticle > table > tbody > tr > td > a");
        // 使用流完成对象转换
        List<Chapter> catalog = elements.stream().map(element ->
                new Chapter(element.text(),
                        ConstantUtils.BASE_URL + element.attr("href")))
                .collect(Collectors.toList());
        Novel novel = new Novel();
        novel.setTitle(title);
        novel.setCatalog(catalog);
        return novel;
    }
    
    // 从html中获取Novel对象
    public static Chapter getChapter(String html) {
        // 使用xpath解析数据
        Document parse = Jsoup.parse(html);
        String title = parse.selectFirst("#title").text();
        String content = parse.select("body > div.chapterlist > div.Readarea").text();
        // 解析后是一个字符串,用split进行分割
        ArrayList<String> content2 = new ArrayList<>(Arrays.asList(content.split(" ")));
        Chapter chapter = new Chapter();
        chapter.setTitle(title);
        chapter.setContent(content2);
        return chapter;
    }
}

Okhttp实现网络请求

ClientUtils
import okhttp3.OkHttpClient;
/*
* 网络请求工具类
* 提供Client单例
* */
public class ClientUtils {
    private static volatile OkHttpClient sInstance;

    public static OkHttpClient getInstance() {
        if (sInstance == null) {
            synchronized (ClientUtils.class) {
                if (sInstance == null) {
                    sInstance = new OkHttpClient();
                }
            }
        }
        return sInstance;
    }
}
OkhttpUtils
import com.example.rxjava.logic.model.Chapter;
import com.example.rxjava.logic.model.Novel;

import java.io.IOException;

import okhttp3.Request;
import okhttp3.Response;

/*
* 网络请求工具类
* 访问url链接
* 向JsoupUtils提供数据
* */
public class OkhttpUtils {
    private static String getHtml(String url) {
        Request request = new Request.Builder().url(url).build();
        try {
            Response response = ClientUtils.getInstance().newCall(request).execute();
            return new String(response.body().bytes(), "gbk");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static Novel getNovel(String url) {
        Novel novel = JsoupUtils.getNovel(getHtml(url));
        novel.setUrl(url);
        return novel;
    }

    public static Chapter getChapter(String url) {
        Chapter chapter = JsoupUtils.getChapter(getHtml(url));
        chapter.setUrl(url);
        return chapter;
    }
}
RxjavaUtils
import android.annotation.SuppressLint;

import com.example.rxjava.logic.model.Chapter;
import com.example.rxjava.logic.model.Novel;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;

/*
* Rxjava工具类
* 提供Flowable对象
* */
public class RxjavaUtils {
    @SuppressLint("CheckResult")
    public static Flowable<Novel> getNovel(String url) {
        return Flowable.create(emitter ->
                emitter.onNext(OkhttpUtils.getNovel(url)), BackpressureStrategy.ERROR);
    }

    public static Flowable<Chapter> getChapter(String url) {
        return Flowable.create(emitter ->
                emitter.onNext(OkhttpUtils.getChapter(url)), BackpressureStrategy.ERROR);
    }
}
MainViewModel
import android.annotation.SuppressLint;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;

import com.example.rxjava.logic.model.Chapter;
import com.example.rxjava.logic.model.Novel;
import com.example.rxjava.logic.network.OkhttpUtils;
import com.example.rxjava.logic.network.RxjavaUtils;

import java.util.function.Function;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;

public class MainViewModel extends ViewModel {
    private final CompositeDisposable mDisposable = new CompositeDisposable();
    private final MutableLiveData<Novel> mNovel = new MutableLiveData<>();
    private final MutableLiveData<Chapter> mChapter = new MutableLiveData<>();
    private final MutableLiveData<Integer> mPosition = new MutableLiveData<>(0);
    private final LiveData<String> mTitle = Transformations.map(mChapter, chapter ->
            "第" + (mPosition.getValue() + 1) + "章 " + chapter.getTitle());
    private final LiveData<String> mContent = Transformations.map(mChapter, input ->
            input.getContent()
                    .stream()
                    .reduce("        ", (s1, s2) -> s1 + "\n\n" + "        " + s2));

    public MutableLiveData<Novel> getNovel() {
        return mNovel;
    }

    public LiveData<String> getTitle() {
        return mTitle;
    }

    public void setPosition(int position) {
        mPosition.setValue(position);
        setChapter(mNovel.getValue().getCatalog().get(position).getUrl());
    }

    public void toNext() {
        if (mPosition.getValue() != mNovel.getValue().getCatalog().size() - 1) {
            setPosition(mPosition.getValue() + 1);
        }
    }

    public LiveData<String> getContent() {
        return mContent;
    }

    public void setNovel(String url) {
        mDisposable.add(RxjavaUtils.getNovel(url)
                .subscribeOn(Schedulers.io())
                .subscribe(mNovel::postValue));
    }

    private void setChapter(String url) {
        mDisposable.add(RxjavaUtils.getChapter(url)
                .subscribeOn(Schedulers.io())
                .subscribe(mChapter::postValue));
    }
}

基于此,所有数据层面已经全部完成,但其中应用框架部分还没讲,等先把那部分更新完后先继续讲

UI层后续

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值