Android TextView加载带有多张图片的HTML,并且解决图片造成的OOM

来源:互联网 发布:linux assert函数 编辑:程序博客网 时间:2024/06/07 09:50

版权声明:本文为博主原创文章,未经博主允许不得转载。

转载请注明本文出自 renxhui 的博客(http://blog.csdn.net/qq_34760508/article/details/70146189),请尊重他人的辛勤劳动成果,谢谢!

相信大家都有过需求需要加载从后台返回的部分HTML代码到我们的Android手机上需求,现有的android 原生控件有WebView 和 TextView 可以去加载HTML,由于现在的需求我们需要记载一段HTML代码在RecyclerView中显示去报,一开始我的思路直接就是直接在Item中镶嵌WebView,这个办法简单粗暴,我试了一下,结果发现不可以因为由于现在RecyclerView在滚动的时候不断地去刷新View ,导致如果在HTML有图片的话,也会不停的加载,这样是不行的,后来就转战TextView了 ,下面我们分析一下TextView。

首先我们要了解TextView加载HTML基本的方法

mTextView.setMovementMethod(ScrollingMovementMethod.getInstance());// 滚动
mTextView.setMovementMethod(LinkMovementMethod.getInstance());//设置超链接可以打开网页
Html.ImageGetter imageGetter = new Html.ImageGetter() {

        public Drawable getDrawable(String source) {            Drawable drawable = null;            URL url;            try {                url = new URL(source);                //Android 4.4 以后这步需要放在子线程中去操作                drawable = Drawable.createFromStream(url.openStream(), ""); // 获取网路图片              } catch (Exception e) {                e.printStackTrace();                return null;            }            //这个后面我们要用到,可以控制显示在Textview上的图片大小            drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),                    drawable.getIntrinsicHeight());            return drawable;        }    };//这个第二个参数imageGetter是加载图片使用的,第三个参数是过滤标签使用的,我们后面要用到,做点击图片的处理    mTextView.setText(Html.fromHtml(htmlforImage, imageGetter, null));上面就是在网上可以百度到的基本操作,下面我们去做其他处理,其实就是加载图片的处理,比如图片的缩放和缓存**重点内容**我相信大部分同学在加载的话都会用webview去直接加载到一个Actiity中,这种的话webview就可以满足我们大本分的需求,可是现在我们需要把Html放在RecyclerView中去显示,webview就不在适用了,二用textview去加载就会出现各种问题,下面我们就去列出会出现的问题然后一一去解决他们

1, 在recyclerview滚动事会不断刷新Textview的内容,这个如果textview中只有文本的话当然不是问题,不过现在我们还包括图片,当然不能重复刷新,浪费流量,而且加载还特别慢,所以我们把加载过一次的图片缓存到本地就可以供下次加载使用了,不多少了,下面上代码

/**
* Created by jh on 2016/10/10.
*/
public class TextToHtmlUtils {

private Context mContext;private Dialog mDialog;private int mPosition;private static TextToHtmlUtils mInstance;private static HashMap<String, Bitmap> sPostDetialHashMap;private static HashMap<String, HashMap<String, Bitmap>> sHashMapHashMap;public static String TEAM_NEWS = "team_news";public static String POST_DETIAL = "post_detial";private static HashMap<String, Bitmap> sTeamNewsHashMap;public TextToHtmlUtils() {    EventBus.getDefault().register(this);}public static TextToHtmlUtils getInstance() {    if (mInstance == null) {        synchronized (OkHttpManager.class) {            if (mInstance == null) {                mInstance = new TextToHtmlUtils();                //用map来存储图片,为之后回收图片做准备                sHashMapHashMap = new HashMap<>();                sPostDetialHashMap = new HashMap<>();                sTeamNewsHashMap = new HashMap<>();            }        }    }    return mInstance;}public void getBitmap(final ToHtml compressBitmap, final String url, final Context context,                      final TextView textView, int position, final String type) {    this.mPosition = position;    this.mContext = context;    //利用Rxjava去处理线程,把耗时线程放在子线程中    Observable.create(new Observable.OnSubscribe<CharSequence>() {        @Override        public void call(Subscriber<? super CharSequence> subscriber) {            //这里去加载图片           // CharSequence test = Html.fromHtml(url);            textView.setText("loading...");            CharSequence getbitmap = getbitmap(url, type);            subscriber.onNext(getbitmap);            subscriber.onCompleted();        }    })            .subscribeOn(Schedulers.io())            .observeOn(AndroidSchedulers.mainThread())            .subscribe(new Observer<CharSequence>() {                @Override                public void onNext(CharSequence bitmap) {                    compressBitmap.toFile(bitmap);                }                @Override                public void onCompleted() {                }                @Override                public void onError(Throwable e) {                }            });}private CharSequence getbitmap(String url, final String type) {    Html.ImageGetter imageGetter = new Html.ImageGetter() {        @Override        public Drawable getDrawable(String source) {            URL url;            Drawable drawable = null;            try {                String filename = checkUrl(source);                String saveFilePaht = Constants.IMAGE + filename;                File file = new File(saveFilePaht);                //判断图片是否已经存储                if (!file.exists()) {                    Response response = OkHttpManager.getInstanceSimaple(mContext).getAsyn1(source);                    byte[] bytes = response.body().bytes();                    //利用BitmapFactory去显小图片避免oom,关于BitmapFactory不会用的自行百度                    BitmapFactory.Options opts = new BitmapFactory.Options();                    opts.inJustDecodeBounds = true;                    //BitmapFactory.decodeStream(inputStream, null, opts);                    BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);                    //根据你想要的图片的大小去自动计算缩小倍数                    int i = computeSampleSize(opts, -1, 1000 * 1000);                    opts.inSampleSize = i;                    opts.inJustDecodeBounds = false;                    opts.inInputShareable = true;                    opts.inPurgeable = true;                    opts.inPreferredConfig = Bitmap.Config.ARGB_4444;                    //把byte转换为bitmap                    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);                    BitmapUtils.compressAndSaveBitmapToSDCard(bitmap, filename, 80);                    drawable = BitmapUtils.bitmaptodrawable(bitmap);                    //吧bitmap存到map中                    if (type.equals(POST_DETIAL)) {                        sPostDetialHashMap.put(filename, bitmap);                        sHashMapHashMap.put(type, sPostDetialHashMap);                    } else if (type.equals(TEAM_NEWS)) {                        sTeamNewsHashMap.put(filename, bitmap);                        sHashMapHashMap.put(type, sTeamNewsHashMap);                    }                    Logger.d("mmmmHtml","不存在");                } else {                    boolean recycle = isRecycle(filename, type);                    //图片已经存在,去内存中或者本地去加载                    if (recycle) {                    //内存中不存在,本地加载                        Bitmap bitmap = BitmapUtils.getBitmapInputStreamFromSDCard(filename);                        drawable = BitmapUtils.bitmaptodrawable(bitmap);                        if (type.equals(POST_DETIAL)) {                            sPostDetialHashMap.put(filename, bitmap);                            sHashMapHashMap.put(type, sPostDetialHashMap);                        } else if (type.equals(TEAM_NEWS)) {                            sTeamNewsHashMap.put(filename, bitmap);                            sHashMapHashMap.put(type, sTeamNewsHashMap);                        }                        Logger.d("mmmmHtml","SD卡");                    } else {                    //内存加载                        HashMap<String, Bitmap> stringBitmapHashMap = sHashMapHashMap.get(type);                        Bitmap bitmap = stringBitmapHashMap.get(filename);                        drawable = BitmapUtils.bitmaptodrawable(bitmap);                        Logger.d("mmmmHtml","Map");                    }                }                //drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());                //这个方法去计算你想要的图片的大小                matchDrawable(drawable);            } catch (MalformedURLException e) {                e.printStackTrace();            } catch (IOException e) {                e.printStackTrace();            }            return drawable;        }    };    CharSequence test = Html.fromHtml(url, imageGetter, new MyTagHandler());    return test;}private boolean isRecycle(String filename, String type) {    if (sHashMapHashMap.containsKey(type)) {        HashMap<String, Bitmap> stringBitmapHashMap = sHashMapHashMap.get(type);        if (stringBitmapHashMap.containsKey(filename)) {            Bitmap bitmap1 = stringBitmapHashMap.get(filename);            boolean recycled = bitmap1.isRecycled();            return recycled;        }    }    return true;}private void matchDrawable(Drawable drawable) {    if (drawable.getIntrinsicHeight() < 300 && drawable.getIntrinsicWidth() < 500 &&            drawable.getIntrinsicHeight() > 10 && drawable.getIntrinsicWidth() > 10) {        float i = 800 / drawable.getIntrinsicWidth();        float j = 400 / drawable.getIntrinsicHeight();        if (i <= j) {            int width = (int) (drawable.getIntrinsicWidth() * j);            int height = (int) (drawable.getIntrinsicHeight() * j);            drawable.setBounds(0, 0, width, height);        } else {            int width = (int) (drawable.getIntrinsicWidth() * i);            int height = (int) (drawable.getIntrinsicHeight() * i);            drawable.setBounds(0, 0, width, height);        }    } else {        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());    }}private String checkUrl(String source) {    String[] split = source.split("/");    for (String sp : split) {        if (sp.equals("attachments")) {            String s = split[split.length - 1];            return s;        }    }    String s = split[split.length - 3];    return s;}public static abstract class ToHtml {    public abstract void toFile(CharSequence bitmap);}/** * 用来通知当解析器遇到无法识别的标签时该作出何种处理 */class MyTagHandler implements Html.TagHandler {    /**     * 参数:     * opening:为true时表示某个标签开始解析,为false时表示该标签解析完     * tag:当前解析的标签     * output:文本中的内容     * xmlReader:xml解析器     */    @Override    public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {        if (tag.toLowerCase().equals("img")) {//解析<img/>标签(注意标签格式不是<img></img>)            //  Logger.e("opening-->", opening + "");            int len = output.length();            ImageSpan[] images = output.getSpans(len - 1, len, ImageSpan.class);            String imgURL = images[0].getSource();            //添加点击事件            output.setSpan(new ImageClickSpan(mContext, imgURL), len - 1, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);        } else if (tag.equalsIgnoreCase("strike")) {//自定义解析<strike></strike>标签            int len = output.length();            Logger.e("opening-->", opening + "");            if (opening) {//开始解析该标签,打一个标记                output.setSpan(new StrikethroughSpan(), len, len, Spannable.SPAN_MARK_MARK);            } else {//解析结束,读出所有标记,取最后一个标记为当前解析的标签的标记(因为解析方式是便读便解析)                StrikethroughSpan[] spans = output.getSpans(0, len, StrikethroughSpan.class);                if (spans.length > 0) {                    for (int i = spans.length - 1; i >= 0; i--) {                        if (output.getSpanFlags(spans[i]) == Spannable.SPAN_MARK_MARK) {                            int start = output.getSpanStart(spans[i]);                            output.removeSpan(spans[i]);                            if (start != len) {                                output.setSpan(new StrikethroughSpan(), start, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);                            }                            break;                        }                    }                }            }        } else {//其他标签不再处理        }    }}class ImageClickSpan extends ClickableSpan {    private Context context;    private String url;    public ImageClickSpan(Context context, String url) {        this.context = context;        this.url = url;    }    @Override    public void onClick(View widget) {        showPicDialog(url);    }}private void showPicDialog(String url) {    String filename = checkUrl(url);    String saveFilePaht = Constants.IMAGE + filename;    Intent intent = new Intent(mContext, BigImageActivity.class);    intent.putExtra("path", saveFilePaht);    mContext.startActivity(intent);}public static int computeSampleSize(BitmapFactory.Options options,                                    int minSideLength, int maxNumOfPixels) {    int initialSize = computeInitialSampleSize(options, minSideLength,            maxNumOfPixels);    int roundedSize;    if (initialSize <= 8) {        roundedSize = 1;        while (roundedSize < initialSize) {            roundedSize <<= 1;        }    } else {        roundedSize = (initialSize + 7) / 8 * 8;    }    return roundedSize;}public static int computeInitialSampleSize(BitmapFactory.Options options,                                           int minSideLength, int maxNumOfPixels) {    double w = options.outWidth;    double h = options.outHeight;    int lowerBound = (maxNumOfPixels == -1) ? 1 :            (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));    int upperBound = (minSideLength == -1) ? 128 :            (int) Math.min(Math.floor(w / minSideLength),                    Math.floor(h / minSideLength));    if (upperBound < lowerBound) {        return lowerBound;    }    if ((maxNumOfPixels == -1) &&            (minSideLength == -1)) {        return 1;    } else if (minSideLength == -1) {        return lowerBound;    } else {        return upperBound;    }//回收bitmappublic void clearBitmap(String type) {    for (String type1 : sHashMapHashMap.keySet()) {        if (type1.equals(type)) {            HashMap<String, Bitmap> stringBitmapHashMap = sHashMapHashMap.get(type1);            for (String path : stringBitmapHashMap.keySet()) {                Bitmap bitmap = stringBitmapHashMap.get(path);                if (!bitmap.isRecycled() && bitmap != null) {                    bitmap.recycle();                    Logger.d("mmmrecyBitmapeventbus", bitmap.toString() + type1);                }            }        }    }}

}
}

经过上面一系列的操作就可以在recyclerview中加载了
在activity调用如下

TextToHtmlUtils instance = getInstance();
instance.getBitmap(new TextToHtmlUtils.ToHtml() {
@Override
public void toFile(CharSequence html) {
holder.mTextView.setText(html);
}
}, Html, context, holder.mtextview, position,TextToHtmlUtils.POST_DETIAL);

最有谢谢大家观看,有什么更好的建议请留言给我

源码已经上传github
网址https://github.com/renxh4/TextViewForHtml
喜欢的话记得点一下关注哦

0 0
原创粉丝点击