今天在做项目的时候,接口返回的信息是html格式的,一开始我是用textView.setText(Html.fromHtml("html标签字符串",null,null));但是发现html标签里有img标签,里面还有一个图片的链接url,如下所示:
尼玛,这怎么显示呢?于是在网上找了很久资料,改了一下代码,实现的效果如下:
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)