使用WebView实现离线阅读

1.先看效果

加载动画

这里写图片描述

加载完成,注意当前为飞行模式!

这里写图片描述

2.使用

1.让你的javabean实现OffLineLevelItem接口,因为我的这个离线阅读支持多级下载,比如Demo中的每个频道下面的第一页item都可以缓存。

package com.zgh.offlinereader;

import java.util.List;

/**
 * Created by zhuguohui on 2016/6/7.
 */
public interface OffLineLevelItem  {
    //是否有下一级
    boolean haveNextLevel();
    //内容url
    String getWebUrl();
    //下一级的url
    String getNextLevelListUrl();
    //生成下一级
    List<OffLineLevelItem> getNextLevelList(String jsonStr);
}

public class Channel implements OffLineLevelItem {
    String title;
    String 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;
    }

    @Override
    public boolean haveNextLevel() {
        return true;
    }

    @Override
    public String getWebUrl() {
        return null;
    }

    @Override
    public String getNextLevelListUrl() {
        return url;
    }

    @Override
    public List<OffLineLevelItem> getNextLevelList(String jsonStr) {
        List<OffLineLevelItem> items = GsonUtil.jsonToBeanList(jsonStr, NewsItem.class);
        return items;
    }
}

2.初始化

   OfflineReaderServer.init(this, getCacheDir(), new MyFirstLevel(),new WaterWaveProgressUI(this));

3.启动

 @Override
    public void onClick(View v) {
        Intent intent=new Intent(this, OfflineReaderServer.class);
        startService(intent);
    }

4.记得在你的webview使用前调用

    //设置缓存目录
    WebViewHelper.setWebViewConfig(webView);

就这么简单!

实现

首先我们为什么要使用webview实现离线阅读,因为简单。webview自带的缓存机制可以实现图片,js,css的缓存。不然你自己得实现数据库,html下载,js下载,css保存,html的拼装。下面我将讲解一些webview设置缓存,实现多级下载,webview遍历url,webview显示完成监听。

1.WebView设置缓存

这一部分比较简单,主要是缓存目录的设置,然后设置缓存模式为WebSettings.LOAD_CACHE_ELSE_NETWORK,这种模式下webview会优先加载本地缓存,如果没有缓存的话再加载网络。

        mWebView.getSettings().setRenderPriority(WebSettings.RenderPriority.HIGH);
        // 建议缓存策略为,判断是否有网络,有的话,使用LOAD_DEFAULT,无网络时,使用LOAD_CACHE_ELSE_NETWORK

        mWebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); // 设置缓存模式
        // 开启DOM storage API 功能
        mWebView.getSettings().setDomStorageEnabled(true);
        // 开启database storage API功能
        mWebView.getSettings().setDatabaseEnabled(true);
        // String cacheDirPath = getFilesDir().getAbsolutePath()
        //         + APP_CACHE_DIRNAME;
        String cacheDirPath = ConfigUtil.getCacheDir()
                + APP_CACHE_DIRNAME;
        Log.i(TAG, "cachePath=" + cacheDirPath);
        // 设置数据库缓存路径
        mWebView.getSettings().setDatabasePath(cacheDirPath); // API 19 deprecated
        // 设置Application caches缓存目录
        mWebView.getSettings().setAppCachePath(cacheDirPath);
        // 开启Application Cache功能
        mWebView.getSettings().setAppCacheEnabled(true);
        mWebView.getSettings().setAppCacheMaxSize(MAX_SIZE);

2.多级缓存

我的项目中需要将每个频道的首页中的每个item都缓存下来,所以涉及到多级缓存于是我设计了一个接口在离线阅读的时候最重要的是拿到叶子节点也就是每个item的url地址,如果是每叶子节点也就是haveNextLevel()返回true的时候就调用getNextLevelListUrl获取下一级的url,一般都是Jason字符串,再把json字符串传入getNextLevelList()方法获取下一级,如果到达叶子节点,则调用getWebUrl()获取url地址保存在一个集合中,当所有的url都获取以后,就开始用webview遍历url实现缓存。

public interface OffLineLevelItem  {
    //是否有下一级
    boolean haveNextLevel();
    //内容url
    String getWebUrl();
    //下一级的url
    String getNextLevelListUrl();
    //生成下一级
    List<OffLineLevelItem> getNextLevelList(String jsonStr);
}

频道的javabean

public class Channel implements OffLineLevelItem {
    String title;
    String 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;
    }

    @Override
    public boolean haveNextLevel() {
        return true;
    }

    @Override
    public String getWebUrl() {
        return null;
    }

    @Override
    public String getNextLevelListUrl() {
        return url;
    }

    @Override
    public List<OffLineLevelItem> getNextLevelList(String jsonStr) {
        List<OffLineLevelItem> items = GsonUtil.jsonToBeanList(jsonStr, NewsItem.class);
        return items;
    }
}

item的javabean

public class NewsItem implements OffLineLevelItem{
    String title;
    String 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;
    }

    @Override
    public String toString() {
        return title;
    }

    @Override
    public boolean haveNextLevel() {
        return false;
    }

    @Override
    public String getWebUrl() {
        return url;
    }

    @Override
    public String getNextLevelListUrl() {
        return null;
    }

    @Override
    public List<OffLineLevelItem> getNextLevelList(String jsonStr) {
        return null;
    }
}

当然为了获取到频道列表需要一个第一级的目录,而这个目录在初始化的时候就设置进去了。

public class MyFirstLevel implements OffLineLevelItem {
    @Override
    public boolean haveNextLevel() {
        return true;
    }

    @Override
    public String getWebUrl() {
        return null;
    }

    @Override
    public String getNextLevelListUrl() {
        return "raw://news_list";
    }

    @Override
    public List<OffLineLevelItem> getNextLevelList(String jsonStr) {
        List<OffLineLevelItem> items = GsonUtil.jsonToBeanList(jsonStr, Channel.class);
        return items;
    }
}

3.使用WebView遍历URL,我原来的思路是给webview设置WebViewClient然后重写onPageFinished方法,在这个方法中获取下一个需要换成的url,然后再调用webview.loadurl()结果是很多页面加载出来是空的。而且在Android4.4以上onPageFinished会调用两次

这里写图片描述

于是乎,我重写了WebView的OnDraw()方法,在OnDraw()方法里设置了一个监听回调,但是由于我的WebView是在Service中创建的所以ondraw方法根本不会调用,但是这难得的我吗?,呵呵,于是我在service的onCreat方法中使用WindowManger将webview添加到屏幕,长宽都是一个像素

   @Override
    public void onCreate() {
        super.onCreate();
        if (!haveInit) {
            throw new RuntimeException("请先调用init()方法,初始化OfflineReaderServer");
        }
        windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        params=new WindowManager.LayoutParams();
        params.type = WindowManager.LayoutParams.TYPE_TOAST;
        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        params.gravity = Gravity.LEFT | Gravity.TOP;
        params.width = 1;
        params.height = 1;
        initWebView();
        windowManager.addView(mWebView,params);
    }

结果还是很明显的大部分的页面都能缓存下来,但是任然有部分页面是空白的,后来发现webview的OnDraw()方法会多次持续,webview的页面加载时间隙的,于是参考这篇文章如何监听WebView显示事件,我通过getContentHeight()判断内容高度来实现显示完成的监听,结果任然不理想。于是我最终版是这样的

/**
 *  可以监听显示完成的webview
 * Created by zhuguohui on 2016/6/24.
 */
public class LoadWebView extends WebView {
    private boolean isRendered = false;
    private static final int MSG_FINISH=1;
    private static final int MIN_CONTENT_HEIGHT=1000;
    public LoadWebView(Context context) {
        this(context, null);
    }
    public LoadWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    private int contentHeight=MIN_CONTENT_HEIGHT;
    Handler handler=new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            if(msg.what==MSG_FINISH) {
                if (finishListenter != null) {
                    finishListenter.onFinish();
                    contentHeight=MIN_CONTENT_HEIGHT;
                }
            }
        }
    };
    @Override
    protected void onDraw(Canvas canvas) {
        //与上一次的contentHeight比较,如果比上一次大,说明还在加载
        if(getContentHeight()>=contentHeight){
            //更新contentHeight
            contentHeight=getContentHeight();
            //取消消息
            handler.removeMessages(MSG_FINISH);
            //延迟200ms发送,如果在200ms内webview又加载了则这条消息会被取消,知道webview加载完成,
            //这条消息会被发送,所以每离线一个页面有200ms的延迟,但是与功能相比这点是可以接受的。
            handler.sendEmptyMessageDelayed(MSG_FINISH,200);
        }
    }

    public interface OnLoadFinishListenter{
        void onFinish();
    }
    
    private OnLoadFinishListenter finishListenter;
    public void setFinishListenter(OnLoadFinishListenter listenter){
        finishListenter=listenter;
    }
}

3.进度提示

为了让用户知道离线的进度我抽取出了一个接口

/**
 * Created by zhuguohui on 2016/6/8.
 */
public interface OffLineProgressUI {
    void showProgress();

    void closeProgress();

    void updateProgress(int progress);

}

并默认实现了一个水波纹的进度球

这里写图片描述

设置进度提示有两种方式,一种是在初始化的时候设置

        OfflineReaderServer.init(this, getCacheDir(), new MyFirstLevel(),new WaterWaveProgressUI(this));

还有一种是调用OfflineReaderServer的setProgressUI方法

   public static void setProgressUI(@NonNull OffLineProgressUI progressUI) {
        sProgressUI = progressUI;
    }

更多内容请看我的Demo。

Demo

https://github.com/zhuguohui/OffLineReaderDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • WebView·开车指南 目录 WebView简介 WebView基本使用 WebView常用方法 WebSett...
    小庄bb阅读 3,432评论 3 25
  • Tips 由于WebView的用法实在太多,如果您只是想查询某个功能的使用——建议Ctrl+F(Commad+F)...
    BugDev阅读 7,690评论 11 109
  • WebView·开车指南 目录 WebView简介 WebView基本使用 WebView常用方法 WebSett...
    南城的人阅读 4,710评论 0 19
  • 最近总想写点什么却怎么也写不出来,之前每天日更的时候,一句话,一个灵感都可以让我长篇大论的写一通,至少是能写出来,...
    高藝菲Sophia阅读 855评论 0 6