浏览器保存网页的四种方式
博客专区 > SuShine 的博客 > 博客详情
浏览器保存网页的四种方式
SuShine 发表于2年前
浏览器保存网页的四种方式
  • 发表于 2年前
  • 阅读 8
  • 收藏 1
  • 点赞 0
  • 评论 0
原创作品,允许转载,转载时请务必以超链接形式标明文章  原始出处 、作者信息和本声明。否则将追究法律责任。 http://mikewang.blog.51cto.com/3826268/1206701

当我们使用浏览器浏览网页时,常常想保存内容,目的可能是离线阅读或者是收藏。之前的一个项目用到一些,一并总结。


方式一,Snapshot

4.0支持此方法saveViewState(),方法源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
      * Saves the view data to the output stream. The output is highly
      * version specific, and may not be able to be loaded by newer versions
      * of WebView.
      * @param stream The {@link OutputStream} to save to
      * @return True if saved successfully
      * @hide
      */
     public  boolean  saveViewState(OutputStream stream) {
         try  {
             return  ViewStateSerializer.serializeViewState(stream,  this );
         catch  (IOException e) {
             Log.w(LOGTAG,  "Failed to saveViewState" , e);
         }
         return  false ;
     }

4.0原生浏览器其实是把网页的内容存入到BLOB中,缺陷很明显,网页一大就保存不了,查了下源码,BLOB保存的数据有大小限制。4.0上,超过2M大小的网页就没法保存,源码截图如下:




但是4.1上,saveViewState() 更新了参数,增加了回调。方法源码如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
      * Saves the view data to the output stream. The output is highly
      * version specific, and may not be able to be loaded by newer versions
      * of WebView.
      * @param stream The {@link OutputStream} to save to
      * @param callback The {@link ValueCallback} to call with the result
      */
     public  void  saveViewState(OutputStream stream, ValueCallback<Boolean> callback) {
         if  (mWebViewCore ==  null ) {
             callback.onReceiveValue( false );
             return ;
         }
         mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SAVE_VIEW_STATE,
                 new  WebViewCore.SaveViewStateRequest(stream, callback));
     }

4.1原生浏览器去snapshot的处理更好了,明白了这个限制之后,网页的data不存数据库了,改存文件了,所以网页太大不能保存的问题就解决了。

考虑到大部分的网页都比较小的情况下,且实现上快速,采用4.0的方法。

主代码采用反射调用4.0 的saveViewState()方法,方法名改为mySaveViewState(),代码如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public  boolean  mySaveViewState(OutputStream stream) {
         boolean  ret =  false ;
         if  (mMethodSaveViewState ==  null )
             try  {
                 mMethodSaveViewState = WebView. class .getDeclaredMethod(
                         "saveViewState" , OutputStream. class );
                 mMethodSaveViewState.setAccessible( true );
             catch  (NoSuchMethodException e) {
             }
         if  (mMethodSaveViewState !=  null ) {
             try  {
                 ret = (Boolean) mMethodSaveViewState.invoke( this , stream);
             catch  (IllegalArgumentException e) {
                 e.printStackTrace();
             catch  (IllegalAccessException e) {
                 e.printStackTrace();
             catch  (InvocationTargetException e) {
                 e.printStackTrace();
             }
         }
         return  ret;
     }


方式二,Stream(file)

因为2.3上不支持saveViewState()方法,所以采用SavePicture(),SavePicture()方法其实来自SaveState(),在webview前进后退后者其他操作时,会保存相应的状态。

方法源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
     * Save the state of this WebView used in
     * {@link android.app.Activity#onSaveInstanceState}. Please note that this
     * method no longer stores the display data for this WebView. The previous
     * behavior could potentially leak files if {@link #restoreState} was never
     * called. See {@link #savePicture} and {@link #restorePicture} for saving
     * and restoring the display data.
     * @param outState The Bundle to store the WebView state.
     * @return The same copy of the back/forward list used to save the state. If
     *         saveState fails, the returned list will be null.
     * @see #savePicture
     * @see #restorePicture
     */
    public  WebBackForwardList saveState(Bundle outState) {
        if  (outState ==  null ) {
            return  null ;
        }
        // We grab a copy of the back/forward list because a client of WebView
        // may have invalidated the history list by calling clearHistory.
        WebBackForwardList list = copyBackForwardList();
        final  int  currentIndex = list.getCurrentIndex();
        final  int  size = list.getSize();
        // We should fail saving the state if the list is empty or the index is
        // not in a valid range.
        if  (currentIndex <  0  || currentIndex >= size || size ==  0 ) {
            return  null ;
        }
        outState.putInt( "index" , currentIndex);
        // FIXME: This should just be a byte[][] instead of ArrayList but
        // Parcel.java does not have the code to handle multi-dimensional
        // arrays.
        ArrayList< byte []> history =  new  ArrayList< byte []>(size);
        for  ( int  i =  0 ; i < size; i++) {
            WebHistoryItem item = list.getItemAtIndex(i);
            if  ( null  == item) {
                // FIXME: this shouldn't happen
                // need to determine how item got set to null
                Log.w(LOGTAG,  "saveState: Unexpected null history item." );
                return  null ;
            }
            byte [] data = item.getFlattenedData();
            if  (data ==  null ) {
                // It would be very odd to not have any data for a given history
                // item. And we will fail to rebuild the history list without
                // flattened data.
                return  null ;
            }
            history.add(data);
        }
        outState.putSerializable( "history" , history);
        if  (mCertificate !=  null ) {
            outState.putBundle( "certificate" ,
                               SslCertificate.saveState(mCertificate));
        }
        return  list;
    }
    /**
     * Save the current display data to the Bundle given. Used in conjunction
     * with {@link #saveState}.
     * @param b A Bundle to store the display data.
     * @param dest The file to store the serialized picture data. Will be
     *             overwritten with this WebView's picture data.
     * @return True if the picture was successfully saved.
     */
    public  boolean  savePicture(Bundle b,  final  File dest) {
        if  (dest ==  null  || b ==  null ) {
            return  false ;
        }
        final  Picture p = capturePicture();
        // Use a temporary file while writing to ensure the destination file
        // contains valid data.
        final  File temp =  new  File(dest.getPath() +  ".writing" );
        new  Thread( new  Runnable() {
            public  void  run() {
                FileOutputStream out =  null ;
                try  {
                    out =  new  FileOutputStream(temp);
                    p.writeToStream(out);
                    // Writing the picture succeeded, rename the temporary file
                    // to the destination.
                    temp.renameTo(dest);
                catch  (Exception e) {
                    // too late to do anything about it.
                finally  {
                    if  (out !=  null ) {
                        try  {
                            out.close();
                        catch  (Exception e) {
                            // Can't do anything about that
                        }
                    }
                    temp.delete();
                }
            }
        }).start();
        // now update the bundle
        b.putInt( "scrollX" , mScrollX);
        b.putInt( "scrollY" , mScrollY);
        b.putFloat( "scale" , mActualScale);
        b.putFloat( "textwrapScale" , mTextWrapScale);
        b.putBoolean( "overview" , mInZoomOverview);
        return  true ;
    }

具体实现上,我们需要传入一个bundle,保存相应的状态。


方式三: png

保存成图片的缺陷很大,放缩的失真,不能编辑。Webview有capturePicture()方法支持。源码中此方法的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
     * Return a new picture that captures the current display of the webview.
     * This is a copy of the display, and will be unaffected if the webview
     * later loads a different URL.
     *
     * @return a picture containing the current contents of the view. Note this
     *         picture is of the entire document, and is not restricted to the
     *         bounds of the view.
     */
    public  Picture capturePicture() {
        if  ( null  == mWebViewCore)  return  null // check for out of memory tab
        return  mWebViewCore.copyContentPicture();
    }

传入picture,具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//FIXME draw on canvas can not run in a non-ui thread
         boolean  saveAsPng(Context ctx, ContentValues values, Picture picture){
             String path = UUID.randomUUID().toString();
             OutputStream outs =  null ;
             try  {
                 if  ( null  == picture)
                     return  false ;
                 String png = path +  ".png" ;
                 outs = ctx.openFileOutput(png, Context.MODE_PRIVATE);
                 PictureDrawable pictureDrawable =  new  PictureDrawable(
                         picture);
                 Bitmap bitmap =  null ;
                 bitmap = Bitmap.createBitmap(
                         pictureDrawable.getIntrinsicWidth(),
                         pictureDrawable.getIntrinsicHeight(),
                         Config.ARGB_8888);
                 Canvas canvas =  new  Canvas(bitmap);
                 canvas.drawPicture(pictureDrawable.getPicture());
                 bitmap.compress(Bitmap.CompressFormat.PNG,  100 , outs);
                 outs.flush();
                 outs.close();
                 bitmap.recycle();
                 bitmap =  null ;
                 values.put(SavedPageColumns.TYPE, SavedPage.TYPE_PNG);
                 values.put(SavedPageColumns.PATH, png);
             catch  (Exception e) {
                 Log.w(LOGTAG,  "Failed to save page " , e);
                 if  (outs !=  null ) {
                     try  {
                         outs.close();
                     catch  (IOException ignore) {
                     }
                 }
                 File file = ctx.getFileStreamPath(path);
                 if  (file !=  null  && file.exists() && !file.delete()) {
                     file.deleteOnExit();
                 }
                 return  false ;
             }
             return  true ;
         }


方法四: 保存html网页

直接将url给下载管理器即可。

本文出自 “小新专栏” 博客,请务必保留此出处http://mikewang.blog.51cto.com/3826268/1206701

共有 人打赏支持
粉丝 118
博文 312
码字总数 79453
×
SuShine
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: