android 中使用ImageGetter在TextView中显示网络图片

今天在做项目的时候,接口返回的信息是html格式的,一开始我是用textView.setText(Html.fromHtml("html标签字符串",null,null));但是发现html标签里有img标签,里面还有一个图片的链接url,如下所示:

尼玛,这怎么显示呢?于是在网上找了很久资料,改了一下代码,实现的效果如下:

HtmlGetter.gif
1、思路分析
  • 主要是用Html.ImageGetter来实现

  • 接口返回的html字段中,有可能包含多个网络图片的地址,这时候需要下载图片到本地。

  • 第一次显示的时候只是把文字部分加载出来,等到图片加载完成的时候,再一次赋值给Textview就实现了上面动图的效果。

2、DownLoadHtmlImageUtils 图片下载工具类
/**
 * 项目名:xyjyyth
 * 包名:  com.tecsun.tsb.utils
 * 文件名:DownLoadHtmlImageUtils
 * 创建者:ZhuiMengXiaoLe
 * 日期:  2017/10/31 18:59
 * 描述:  下载Html标签里面的图片
 */
public class DownLoadHtmlImageUtils {
    public volatile boolean isCancle = false;
    // Init Hander
    EventHandler mHandler = new EventHandler(this);

    /**
     * DownLoad the file that must have a url
     *
     * @param url The http url
     * @param savePath The save path
     */
    public void download(final String url, final String savePath) {
        Log.i("DEBUG", url+"  Start!");
        new Thread(new Runnable() {
            public void run() {
                try {
                    Log.i("DEBUG", url+"  Start !!!!");
                    sendMessage(FILE_DOWNLOAD_CONNECT);
                    URL sourceUrl = new URL(url);
                    URLConnection conn = sourceUrl.openConnection();
                    InputStream inputStream = conn.getInputStream();

                    int fileSize = conn.getContentLength();

                    File savefile = new File(savePath);
                    if (savefile.exists()) {
                        savefile.delete();
                    }
                    savefile.createNewFile();

                    FileOutputStream outputStream = new FileOutputStream(savePath);

                    byte[] buffer = new byte[1024];
                    int readCount = 0;
                    int readNum = 0;
                    int prevPercent = 0;
                    while ((readNum = inputStream.read(buffer)) != -1) {
                        if (readNum > -1) {
                            readCount = readCount + readNum;
                            outputStream.write(buffer, 0, readNum);

                            int percent = (readCount * 100 / fileSize);
                            if (percent > prevPercent) {
                                // send the progress
                                sendMessage(FILE_DOWNLOAD_UPDATE, percent, readCount);
                                prevPercent = percent;
                            }

                            if (isCancle) {
                                outputStream.close();
                                sendMessage(FILE_DOWNLOAD_ERROR, new Exception("Stop"));
                                break;
                            }
                        }
                    }

                    outputStream.close();
                    if (!isCancle) {
                        sendMessage(FILE_DOWNLOAD_COMPLETE, url);
                    }

                } catch (Exception e) {
                    sendMessage(FILE_DOWNLOAD_ERROR, e);
                    // Log.e("MyError", e.toString());
                }
            }
        }).start();
    }

    /**
     * send message to handler
     *
     * @param what handler what
     * @param obj handler obj
     */
    private void sendMessage(int what, Object obj) {
        // init the handler message
        Message msg = mHandler.obtainMessage(what, obj);
        // send message
        mHandler.sendMessage(msg);
    }

    private void sendMessage(int what) {
        Message msg = mHandler.obtainMessage(what);
        mHandler.sendMessage(msg);
    }

    private void sendMessage(int what, int arg1, int arg2) {
        Message msg = mHandler.obtainMessage(what, arg1, arg2);
        mHandler.sendMessage(msg);
    }

    public void setCancle() {
        this.isCancle = true;
        Log.v("setCancle", String.valueOf(isCancle));
    }

    private static final int FILE_DOWNLOAD_CONNECT = 0;
    private static final int FILE_DOWNLOAD_UPDATE = 1;
    private static final int FILE_DOWNLOAD_COMPLETE = 2;
    private static final int FILE_DOWNLOAD_ERROR = -1;

    // defined the handler
    private class EventHandler extends Handler {
        private final DownLoadHtmlImageUtils mManager;

        public EventHandler(DownLoadHtmlImageUtils manager) {
            mManager = manager;
        }

        // do the receive message
        @Override
        public void handleMessage(Message msg) {

            switch (msg.what) {
                case FILE_DOWNLOAD_CONNECT:
                    if (mOnDownloadListener != null)
                        mOnDownloadListener.onDownloadConnect(mManager);
                    break;
                case FILE_DOWNLOAD_UPDATE:
                    if (mOnDownloadListener != null)
                        mOnDownloadListener.onDownloadUpdate(mManager, msg.arg1);
                    break;
                case FILE_DOWNLOAD_COMPLETE:
                    if (mOnDownloadListener != null)
                        mOnDownloadListener.onDownloadComplete(mManager, msg.obj);
                    break;
                case FILE_DOWNLOAD_ERROR:
                    if (mOnDownloadListener != null)
                        mOnDownloadListener.onDownloadError(mManager, (Exception) msg.obj);
                    break;
                default:
                    break;
            }
        }
    }

    // defined connection listener
    private OnDownloadListener mOnDownloadListener;

    public interface OnDownloadListener {
        void onDownloadConnect(DownLoadHtmlImageUtils manager);
        void onDownloadUpdate(DownLoadHtmlImageUtils manager, int percent);
        void onDownloadComplete(DownLoadHtmlImageUtils manager, Object result);
        void onDownloadError(DownLoadHtmlImageUtils manager, Exception e);
    }

    public void setOnDownloadListener(OnDownloadListener listener) {
        mOnDownloadListener = listener;
    }
}
3、核心代码1分析:设置TextView可点击跳转Html里面的链接
    //保存文件路径
    private final String path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/htmlimg/";
        //设置链接中标签是可以点击并且可以跳转到浏览器打开的
        tvJobDes.setClickable(true);
        tvJobDes.setMovementMethod(LinkMovementMethod.getInstance());
        //整个TextView居中显示
//      tvJobDes.setGravity(Gravity.CENTER_HORIZONTAL);

4、核心代码2分析:第一次赋值给 TextView,同时开始下载图片
private void setData() {
        //初始化下载类
        downLoadUtils=new DownLoadHtmlImageUtils();
        //设置下载类监听事件
        downLoadUtils.setOnDownloadListener(onDownloadListener);
        //给Textview赋值
        tvJobDes.setText(Html.fromHtml(mFvalue,imageGetter,null));
    }
    Drawable drawable = null;
    Bitmap bitmap = null;
    Html.ImageGetter imageGetter = new Html.ImageGetter() {
        public Drawable getDrawable(String source) {

            String fileString=path+String.valueOf(source.hashCode());
            Log.i("DEBUG", fileString+"");
            Log.i("DEBUG", source+"");

            if (!new File(path).exists()){
                new File(path).mkdirs();
                LogUtils.d(TAG,"创建文件夹成功========");
            }
            //判断SD卡里面是否存在图片文件
            if (new File(fileString).exists()) {
                Log.i("DEBUG", fileString+"  eixts");
                //获取本地文件返回Drawable
                bitmap = BitmapFactory.decodeFile(fileString);
                drawable = new BitmapDrawable(bitmap);
//              drawable=Drawable.createFromPath(fileString);
                //设置图片边界
                if (drawable != null){
                    drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
                    return drawable;
                }
                return null;

            }else {
                Log.i("DEBUG", fileString+" Do not eixts");
                //启动新线程下载
                downLoadUtils.download(source, path+String.valueOf(source.hashCode()));
                return drawable;
            }

        };

    };
5、核心代码3分析:等待图片下载完成的时候,再一次赋值给TextView
DownLoadHtmlImageUtils.OnDownloadListener onDownloadListener=new DownLoadHtmlImageUtils.OnDownloadListener() {

        //下载进度
        public void onDownloadUpdate(DownLoadHtmlImageUtils manager, int percent) {
            // TODO Auto-generated method stub
            Log.i("DEBUG", percent+"");
        }

        //下载失败
        public void onDownloadError(DownLoadHtmlImageUtils manager, Exception e) {
            // TODO Auto-generated method stub

        }

        //开始下载
        public void onDownloadConnect(DownLoadHtmlImageUtils manager) {
            // TODO Auto-generated method stub
            Log.i("DEBUG", "Start  //////");
        }

        //完成下载
        public void onDownloadComplete(DownLoadHtmlImageUtils manager, Object result) {
            // TODO Auto-generated method stub
            Log.i("DEBUG", result.toString());
            //替换sTExt的值,就是把图片的网络路径换成本地SD卡图片路径(最早想法,可以不需要这样做了)
            //sText.replace(result.toString(), path+String.valueOf(result.hashCode()));
            //再一次赋值给Textview
            tvJobDes.setText(Html.fromHtml(mFvalue, imageGetter, null));
        }
    };
6、核心代码4分析:退出Activity或者Fragment的时候,回收Bitmap
@Override
    protected void onDestroy() {
        super.onDestroy();
        drawable = null;

//因为本应用要下载图片太多,所以每次退出的时候把下载好的本地图片删除
        FileUtils.delDir(path);
        if (bitmap != null && !bitmap.isRecycled()){
            bitmap.recycle();
            bitmap = null;
            drawable = null;
        }

        if (imageGetter != null){
            imageGetter = null;
        }
    }
8、FileUtils工具类核心方法:为了删除下载好的本地图片
 /**
     * 删除方法 这里只会删除某个文件夹下的文件
* 支持两级目录删除 */ public static void delDir(String path) { File directory = new File(path); if (directory != null && directory.exists() && directory.isDirectory()) { for (File item : directory.listFiles()) { if (item.isDirectory()) { for (File img : item.listFiles()) { img.delete(); } } item.delete(); } } }
8、FileUtils工具类完整代码
public class FileUtils {
    private static Context mContext;
    private static final int DELAY_TIME = 10000;

    public final static String CACHE_DIR = "CNMusic";
    public final static String GLIDE_CACHE_DIR = "glide";
    public final static String SONG_CACHE_DIR = "songs";

    public final static String APK_NAME = "cnmusic.apk";


    /**
     * 应用关联的图片存储空间
     *
     * @param context
     * @return
     */
    public static String getAppPictureDir(Context context) {
        File file = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        return file.getPath();
    }

    /**
     * 获取缓存主目录
     *
     * @return
     */
    public static String getMountedCacheDir() {
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            // 创建一个文件夹对象,赋值为外部存储器的目录
            File sdcardDir = Environment.getExternalStorageDirectory();
            //得到一个路径,内容是sdcard的文件夹路径和名字
            String path = sdcardDir.getPath() + File.separator + CACHE_DIR;
            File path1 = new File(path);
            if (!path1.exists()) {
                path1.mkdirs();
            }
            return path1.getPath();
        }
        return null;
    }

    /**
     * 获取存放歌曲的目录
     *
     * @return
     */
    public static String getSongDir() {
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            String path = getMountedCacheDir() + File.separator + SONG_CACHE_DIR;
            File path1 = new File(path);
            if (!path1.exists()) {
                path1.mkdirs();
            }
            return path1.getPath();
        }
        return null;
    }

    /**
     * 获取存放缓存的目录
     *
     * @return
     */
    public static String getCacheDir() {
        File file = mContext.getExternalCacheDir();
        return file.getPath();
    }

    /**
     * 删除方法 这里只会删除某个文件夹下的文件
* 支持两级目录删除 */ public static void cleanCacheDir() { if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { File directory = mContext.getExternalCacheDir(); if (directory != null && directory.exists() && directory.isDirectory()) { for (File item : directory.listFiles()) { if (item.isDirectory()) { for (File img : item.listFiles()) { img.delete(); } } item.delete(); } } } } /** * 删除方法 这里只会删除某个文件夹下的文件
* 支持两级目录删除 */ public static void delDir(String path) { File directory = new File(path); if (directory != null && directory.exists() && directory.isDirectory()) { for (File item : directory.listFiles()) { if (item.isDirectory()) { for (File img : item.listFiles()) { img.delete(); } } item.delete(); } } } /** * 获取apk放置的地址 * * @return */ public static String getApkPath() { String apkPath = getCacheDir() + File.separator + APK_NAME; File file = new File(apkPath); if (file.exists()) { file.delete(); } return file.getPath(); } /** * 读取Assets目录下的文件 * * @param context * @param name * @return */ public static String getAssets(Context context, String name) { String result = null; try { InputStream in = context.getAssets().open(name); //获得AssetManger 对象, 调用其open 方法取得 对应的inputStream对象 int size = in.available();//取得数据流的数据大小 byte[] buffer = new byte[size]; in.read(buffer); in.close(); result = new String(buffer); } catch (Exception e) { } return result; } /** * 媒体扫描,防止下载后在sdcard中获取不到歌曲的信息 * * @param path */ public static void mp3Scanner(String path) { MediaScannerConnection.scanFile(mContext.getApplicationContext(), new String[]{path}, null, null); } public static boolean existFile(String path) { File path1 = new File(path); return path1.exists(); } public static boolean deleteFile(String path) { File path1 = new File(path); if (path1.exists()) { return path1.delete(); } return false; } /** * open apk * * @param context * @param apk */ public static void openApk(Context context, File apk) { Intent intent = new Intent(); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(apk), "application/vnd.android.package-archive"); context.startActivity(intent); } /** * 获取下载文件的大小 * * @param soFarBytes 已下载字节 * @param totalBytes 总共的字节 * @return */ public static String getProgressSize(long soFarBytes, long totalBytes) { float progress = soFarBytes * 1.0f / 1024 / 1024; float total = totalBytes * 1.0f / 1024 / 1024; String format = "%.1fM/%.1fM"; String str = String.format(Locale.CHINA, format, progress, total); return str; } /** * 获取下载进度 * * @param soFarBytes 已下载字节 * @param totalBytes 总共的字节 * @return */ public static int getProgress(long soFarBytes, long totalBytes) { if (totalBytes != 0) { long progress = soFarBytes * 100 / totalBytes; return (int) progress; } return 0; } /** * download apk from server * * @param path the apk path * @param fp file progress listener * @return apk file * @throws Exception */ public static File getFilefromServerToProgress(String path, FileProgress fp) throws Exception { //如果相等的话表示当前的sdcard挂载在手机上并且是可用的 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(DELAY_TIME); int max = conn.getContentLength(); InputStream is = conn.getInputStream(); File file = new File(Environment.getExternalStorageDirectory(), "beats.apk"); FileOutputStream fos = new FileOutputStream(file); BufferedInputStream bis = new BufferedInputStream(is); byte[] buffer = new byte[1024]; int len; int total = 0; while ((len = bis.read(buffer)) != -1) { fos.write(buffer, 0, len); total += len; //获取当前下载量 if (fp != null) fp.getProgress(total, max); } fos.close(); bis.close(); is.close(); return file; } else { return null; } } private static Pattern FilePattern = Pattern.compile("[\\\\/:*?\"<>|]"); public static String filenameFilter(String str) { return str == null ? null : FilePattern.matcher(str).replaceAll(""); } public interface FileProgress { void getProgress(int total, int max); } }
9、主Actiivity或者Fragment完整代码实现
**
 * 发布信息详情查询
 * @author zlc
 *
 */
public class JobFbxxxqcxActivity extends BaseActivity {

    private static final String TAG = JobFbxxxqcxActivity.class.getSimpleName();
    private Context mContext;
    private TextView tvJobName;
    private TextView tvJobDes;
    private TextView tvJobTask;
    private TextView tvJobRequest;
    private ImageButton mImbBack;
    private JobIntentInfoBean mInfoBean;
    private String mFinfoid;
    private String mFvalue;
    private DownLoadHtmlImageUtils downLoadUtils;
    //保存文件路径
    private final String path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/htmlimg/";


    //  String sText = "测试图片信息:
"; @Override protected void onCreate(Bundle arg0) { super.onCreate(arg0); setContentView(R.layout.activity_job_fbxxcx_32); AppApplication.getInstance().addActivity(this); initView(); initListener(); initData(); getListData(); } /** * 初始化数据的方法 */ private void initData() { mInfoBean = (JobIntentInfoBean) getIntent().getSerializableExtra (Constant.INTENT_CONTENT); if (mInfoBean != null){ mFinfoid = mInfoBean.finfoid; LogUtils.d(TAG,"mFinfoid===="+mFinfoid); } } /** * 初始化监听的方法 */ private void initListener() { mImbBack.setOnClickListener(this); } @Override protected void initView() { mContext = this; loadingDialog = new LoadingDialog(this, R.string.tip_loading_msg); TextView tvTitle = (TextView) this.findViewById(R.id.tv_base_title_content); tvTitle.setText(R.string.title_fbxxxq); mImbBack = (ImageButton) this.findViewById(R.id.imgb_back); tvJobName = (TextView) findViewById(R.id.tv_job_name); tvJobDes = (TextView) findViewById(tv_job_des); tvJobTask = (TextView) findViewById(R.id.tv_job_task); tvJobRequest = (TextView) findViewById(R.id.tv_job_request); //设置链接中标签是可以点击并且可以跳转到浏览器打开的 tvJobDes.setClickable(true); tvJobDes.setMovementMethod(LinkMovementMethod.getInstance()); //整个TextView居中显示 // tvJobDes.setGravity(Gravity.CENTER_HORIZONTAL); } @Override public void onClick(View v) { super.onClick(v); switch (v.getId()) { case R.id.imgb_back: finish(); EventBus.getDefault().post(true); } } /** * 查询个人求职列表信息 */ private void getListData() { showLoadingDialog(); StringEntity entity = new JobParam().getCmsInfoParam(mFinfoid); HttpUtil.post(this, APICommon.API_GETCMSINFO, entity, new AsyncHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { LogUtils.d(new String(responseBody)); Gson gson = new Gson(); FbxxxqDataListResponse bean = gson.fromJson(new String (responseBody), FbxxxqDataListResponse.class); loadPersionData(bean); dismissLoadingDialog(); } @Override public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { dismissLoadingDialog(); } }); } /** * 界面加载数据 * @param bean */ @SuppressWarnings("unchecked") private void loadPersionData(FbxxxqDataListResponse bean) { if (!bean.isSuccess()) { return; } if (bean != null) { List infoidDataResponseList = bean.data; if (infoidDataResponseList.size() > 0){ InfoidDataResponse infoidDataResponse = infoidDataResponseList.get(0); mFvalue = infoidDataResponse.getFvalue(); if (!TextUtils.isEmpty(mFvalue)){ setData(); }else { showWarnStrDialog("发布信息详情为空"); } } } } private void setData() { //初始化下载类 downLoadUtils=new DownLoadHtmlImageUtils(); //设置下载类监听事件 downLoadUtils.setOnDownloadListener(onDownloadListener); //给Textview赋值 tvJobDes.setText(Html.fromHtml(mFvalue,imageGetter,null)); } Drawable drawable = null; Bitmap bitmap = null; Html.ImageGetter imageGetter = new Html.ImageGetter() { public Drawable getDrawable(String source) { String fileString=path+String.valueOf(source.hashCode()); Log.i("DEBUG", fileString+""); Log.i("DEBUG", source+""); if (!new File(path).exists()){ new File(path).mkdirs(); LogUtils.d(TAG,"创建文件夹成功========"); } //判断SD卡里面是否存在图片文件 if (new File(fileString).exists()) { Log.i("DEBUG", fileString+" eixts"); //获取本地文件返回Drawable bitmap = BitmapFactory.decodeFile(fileString); drawable = new BitmapDrawable(bitmap); // drawable=Drawable.createFromPath(fileString); //设置图片边界 if (drawable != null){ drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); return drawable; } return null; }else { Log.i("DEBUG", fileString+" Do not eixts"); //启动新线程下载 downLoadUtils.download(source, path+String.valueOf(source.hashCode())); return drawable; } }; }; @Override protected void onDestroy() { super.onDestroy(); drawable = null; FileUtils.delDir(path); if (bitmap != null && !bitmap.isRecycled()){ bitmap.recycle(); bitmap = null; drawable = null; } if (imageGetter != null){ imageGetter = null; } } Html.TagHandler MyTagHandler = new Html.TagHandler() { @Override public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) { } }; DownLoadHtmlImageUtils.OnDownloadListener onDownloadListener=new DownLoadHtmlImageUtils.OnDownloadListener() { //下载进度 public void onDownloadUpdate(DownLoadHtmlImageUtils manager, int percent) { // TODO Auto-generated method stub Log.i("DEBUG", percent+""); } //下载失败 public void onDownloadError(DownLoadHtmlImageUtils manager, Exception e) { // TODO Auto-generated method stub } //开始下载 public void onDownloadConnect(DownLoadHtmlImageUtils manager) { // TODO Auto-generated method stub Log.i("DEBUG", "Start //////"); } //完成下载 public void onDownloadComplete(DownLoadHtmlImageUtils manager, Object result) { // TODO Auto-generated method stub Log.i("DEBUG", result.toString()); //替换sTExt的值,就是把图片的网络路径换成本地SD卡图片路径(最早想法,可以不需要这样做了) //sText.replace(result.toString(), path+String.valueOf(result.hashCode())); //再一次赋值给Textview tvJobDes.setText(Html.fromHtml(mFvalue, imageGetter, null)); } }; }
11、存在的问题
  • 当接口返回里面包含很多标签,也就是说要加载很多张图片,或许这些图片都是大图,那么如何做到更好的缓存呢?我尝试过先判断本地有没有对应的图片,没有的话就去下载,有的话沿用本地图片,不过还是会报内存溢出。(这里我没有对图片进行压缩,因为我看到下载的图片都是几十kb或者100多kb)

你可能感兴趣的:(android 中使用ImageGetter在TextView中显示网络图片)