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

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的自写上,代码很多,比较繁琐;而在我的试验中觉得上面的才是核心点,只要抓住,几行代码就能搞定;

当然,可能我的理解不够深,不能窥测大牛们的繁琐步骤可能有更远见性的考虑,还望多多指正、批评!



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值