您的位置:首页 > Web前端 > HTML

TextView异步加载HTML格式数据中的图片(解决4.0以上主线程加载失败)

2015-08-31 08:44 597 查看
4.0以下的系统TextView显示带HTML标签并含有网络图片信息的文本时在主线程调用这条语句textView.setText(Html.fromHtml(txtString,
imageGetter, null))便可以直接正常显示HTML中的图片了;(txtString为带html标签的字符串,imageGetter为自重写的Html.ImageGetter)

但是在4.0以上这是不可以的,因为系统规定在主线程(UI线程)中不可以访问网络,为的是避免主线程阻塞;

因此必须开启子线程加载图片,但是又存在另一个严重问题,子线程中是不可以访问UI控件的(UI控件是在主线程定义、创建的),就是说对textView的操作是不允许的;

因此我的解决方式是:开启子线程加载图片,用主线程的handler变量使用post将加载内容返给UI控件显示,即显示操作压到主线程消息队列中(以下代码在4.0以上手机中测试进行)

下面分几种情况(正确方法以及几种错误方法)进行比较分析

0、子线程加载图片,handler调用控件显示(即正确方法)

new Thread(new Runnable()
{
@Override
public void run()
{
final Spanned text = Html.fromHtml(txtString,imgGetter,null);
handler.post(new Runnable()
{
@Override
public void run()
{
textView.setText(text);
}
});
}
}).start();


private ImageGetter imgGetter = new Html.ImageGetter() //格式语句不一定相同,只要进行网络加载图片即可
{
public Drawable getDrawable(String source)
{
Drawable drawable = null;
try
{
drawable = Drawable.createFromStream(new URL(source).openStream(), "");//加载网络图片资源核心语句
int screenWidth = AndroidUtil.getLayoutWidth(AllianceNewsDetailActivity.this);
int draWidth    = screenWidth - AndroidUtil.dip2px(AllianceNewsDetailActivity.this, 24);
int draHeight   = draWidth *  drawable.getIntrinsicHeight() / drawable.getIntrinsicWidth();
drawable.setBounds(0, 0, draWidth,draHeight);
}
catch (Exception e)
{
return new Drawable()
{
public void setColorFilter(ColorFilter cf) {}
public void setAlpha(int alpha) {}
public int getOpacity() {return 0;}
public void draw(Canvas canvas) {}
};
}
return drawable;
}
};


注意,final Spanned text = Html.fromHtml(txtString,imgGetter,null) 这句是必须放在子线程Thread里面的run方法中的,

其实本质来说imageGetter(自写,以下同)就是访问网络的操作( 核心语句drawable = Drawable.createFromStream(new URL(source).openStream(), ""); )以往抛错的原因之一就是因为这句没有在子线程中进行;

之后调用handler(主线程定义的Handler变量)进行post,即在主线程中去对textView进行操作,就可以正常显示了。

1、错误调用方式一(开启的子线程中刷新显示textView)

new Thread(new Runnable()
{
@Override
public void run()
{
final Spanned text = Html.fromHtml(txtString,imgGetter,null);
//开启新线程刷新显示,抛错崩溃
new Thread(new Runnable() {
@Override
public void run() {
textView.setText(text);
}
}).start();
}
}).start();

程序会直接抛错崩溃,原因是textView.setText(text)放在新的子线程中访问了,其实不管是不是新的子线程,都违反了【子线程中是不可以访问UI控件的(UI控件是在主线程定义、创建的),就是说对textView的操作是不允许的】的原则

2、错误调用方式二(子线程加载后直接显示)

new Thread(new Runnable()
{
@Override
public void run()
{
final Spanned text = Html.fromHtml(txtString,imgGetter,null);
//直接刷新显示,抛错崩溃
textView.setText(text);
}
}).start();


本质上和错误一是一样的,都违反了【子线程中是不可以访问UI控件的(UI控件是在主线程定义、创建的),就是说对textView的操作是不允许的】的原则

3、错误调用方式三(主线程handler压入Runnable线程消息进行加载和显示)

//不抛错,但加载不出图片
handler.post(new Runnable() {
@Override
public void run() {
textView.setText(Html.fromHtml(txtString,imgGetter, null));
}
});
有人会想,我在handler中压入一个线程消息,在里面进行显示和加载不可以么?就是以上,很遗憾,这样加载不出图片,我的理解是:这样默认整体是在主线程框架下进行的网络加载和显示,也是不可以的。

这里注意程序是不会崩溃的,原因我的理解是:毕竟Html.fromHtml(txtString,imgGetter, null)是在主线程handler的Runnable中进行的,并非直接调用,因此不会抛错,这也可能跟线程的深层含义有关,介于我是新手,期待有高手会进行点拨, 或者以后会有深入理解后给出合理解释。

4、错误调用方式四(主线程handler加载和显示)

//不抛错,但加载不出图片
new Thread(new Runnable()
{
@Override
public void run()
{
handler.post(new Runnable() {
@Override
public void run() {
textView.setText(Html.fromHtml(txtString, imgGetter, null));
}
});
}
}).start();
虽然handler在子线程但是handler是主线程的变量,在哪里post都是一样的都是在主线程中;本质和错误三其实一样。

总结一下我进行的错误示范实验:

一是子线程1加载,子线程2显示;

二是子线程1加载,子线程1显示;

三、四是主线程handler的Runnable加载和显示;

当然,直接在主线程中直接写这句textView.setText(Html.fromHtml(txtString, imageGetter, null))直接加载和显示肯定是会抛错的。

可见,这个问题其实就是一层“窗户纸”的问题:

子线程网络图片加载(imageGetter必须在子线程run方法中),主线程显示(需用handler)

PS.网上看见很多人解决这个方法需要重写一个类进行下载、解析,或者着重点在对imageGetter的自写上,代码很多,比较繁琐;而在我的试验中觉得上面的才是核心点,只要抓住,几行代码就能搞定;

当然,可能我的理解不够深,不能窥测大牛们的繁琐步骤可能有更远见性的考虑,还望多多指正、批评!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: