Kotlin_获取网络图片(HttpURLConnection, AsyncTask,协程)
创始人
2024-02-08 02:32:16

协程、AsyncTask、new Thread() 访问网络的对比

使用Kotlin 从网络获取网络图片,需要学习 HttpURLConnection的使用, 多线程(AsyncTask)的使用等 。

先说总结,获取网络图片有几种方式:

1. 直接创建一个线程获取, 会导致显示的图片错乱。

2. 使用AsyncTask , 确保正常显示图片

3. 使用Kotlin 的协程, 用看似同步的代码写异步的操作。

一、 创建根据URL 获取图片的类

第一种方式为直接创建一个线程获取,但是这种方式是明显不可行的。

// 获取网络图片实现类
class NetworkUtils {private var picture : Bitmap ?= nullprivate var context: Contextcompanion object{const val TAG = "NetworkUtils"}constructor(context: Context) {this.context = context}// 获取网络图片fun loadPicture(url: URL): Bitmap? {// 开启一个单独线程进行网络读取Thread(Runnable {var bitmap: Bitmap ? = nulltry {// 根据URL 实例, 获取HttpURLConnection 实例var httpURLConnection: HttpURLConnection = url.openConnection() as HttpURLConnection// 设置读取 和 连接 time out 时间httpURLConnection.readTimeout = 2000httpURLConnection.connectTimeout = 2000// 获取图片输入流var inputStream = httpURLConnection.inputStream// 获取网络响应结果var responseCode = httpURLConnection.responseCode// 获取正常if (responseCode == HttpURLConnection.HTTP_OK) {// 解析图片bitmap = BitmapFactory.decodeStream(inputStream)}} catch(e: IOException) { // 捕获异常 (例如网络异常)Log.d(TAG, "loadPicture - error: ${e?.toString()}")}this.picture = bitmap}).start()// 返回的图片可能为空- 多线程 - 上面的线程还没跑完,已经返回 结果了return picture}
}

第二种是使用AsyncTask.

// 三个泛型参数, 第一个为执行,第二个进度,第三个返回
class NetworkUtilsAsyncTask : AsyncTask {private var resultPicture: Bitmap? = nullprivate lateinit var context: Contextcompanion object {const val TAG = "NetworkUtilsAsyncTask"}constructor(context: Context) {this.context = context}override fun doInBackground(vararg params: URL?): Bitmap? {return loadPicture(params[0])}// 获取网络图片private fun loadPicture(url: URL?): Bitmap? {// 开启一个单独线程进行网络读取var bitmapFromNetwork: Bitmap? = nullurl?.let {try {// 根据URL 实例, 获取HttpURLConnection 实例var httpURLConnection: HttpURLConnection = url.openConnection() as HttpURLConnection// 设置读取 和 连接 time out 时间httpURLConnection.readTimeout = 2000httpURLConnection.connectTimeout = 2000// 获取图片输入流var inputStream = httpURLConnection.inputStream// 获取网络响应结果var responseCode = httpURLConnection.responseCodeLog.d(TAG, "loadPicture - responseCode: $responseCode")// 获取正常if (responseCode == HttpURLConnection.HTTP_OK) {// 解析图片bitmapFromNetwork = BitmapFactory.decodeStream(inputStream)}} catch (e: IOException) { // 捕获异常 (例如网络异常)Log.d(TAG, "loadPicture - error: ${e?.toString()}")//printErrorMessage(e?.toString())}Log.d(TAG, "loadPicture - bitmapFromNetwork: $bitmapFromNetwork")this.resultPicture = bitmapFromNetwork// 返回的图片可能为空}Log.d(TAG, "loadPicture - resultPicture: $resultPicture")return resultPicture}// 调用UI线程的更新UI操作override fun onPostExecute(result: Bitmap?) {super.onPostExecute(result)Log.d(TAG, "onPostExecute - result: $result")if (context is MainActivity) {(context as MainActivity).setResult(result)}}
}

使用AsyncTask需要注意几点:
1. 三个泛型参数  AsyncTask

Params: 为你在UI线程启动执行该任务时,需要传递进来的参数

Result: 为你在想在执行任务后,返回什么类型的结果

Progress: 进度条, 一般为Int

2. 每个任务仅能被执行一次,执行多次会报错,记得cancel

AndroidRuntime:Caused by: java.lang.IllegalStateException:Cannot execute task: the task has already been executed (a task can be executed only once)

3. 任务执行完成后,可以在 onPostExecute 调用UI 逻辑 进行更新UI

第三种是使用Kotlin的协程,其实从网络获取图片的逻辑是一样,区别是怎样调用这个逻辑

class NetworkUtilsCoroutines {private var resultPicture: Bitmap? = nullprivate var context: Contextcompanion object {const val TAG = "NetworkUtilsCoroutines"}constructor(context: Context) {this.context = context}// 获取网络图片fun loadPicture(url: URL): Bitmap? {// 开启一个单独线程进行网络读取var bitmapFromNetwork: Bitmap? = nulltry {// 根据URL 实例, 获取HttpURLConnection 实例var httpURLConnection: HttpURLConnection = url.openConnection() as HttpURLConnection// 设置读取 和 连接 time out 时间httpURLConnection.readTimeout = 2000httpURLConnection.connectTimeout = 2000// 获取图片输入流var inputStream = httpURLConnection.inputStream// 获取网络响应结果var responseCode = httpURLConnection.responseCode// 获取正常if (responseCode == HttpURLConnection.HTTP_OK) {// 解析图片bitmapFromNetwork = BitmapFactory.decodeStream(inputStream)}} catch (e: IOException) { // 捕获异常 (例如网络异常)Log.d(TAG, "loadPicture - error: ${e?.toString()}")//printErrorMessage(e?.toString())}this.resultPicture = bitmapFromNetworkreturn resultPicture}}

需要注意的是,要在gradle文件配置Kotlin 协程库:

项目根目录的build.gradle文件:

ext.kotlin_coroutines = '1.3.1'

APP目录的build.gradle:   

 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines"implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines"

其中 kotlinx-coroutines-core 为核心库, kotlinx-coroutines-android 为平台库

版本参考: 

Kotlin releases | Kotlin
https://kotlinlang.org/docs/releases.html#release-details

二、定义网络图片的地址 (单独创建一个常量类,方便管理而已,也可以在用到的地方定义)

object CommonConstants {const val Address1 ="http://e.hiphotos.baidu.com/zhidao/pic/item/8cb1cb1349540923f12939199458d109b3de4910.jpg"const val Address2 ="http://e.hiphotos.baidu.com/zhidao/pic/item/aec379310a55b31907d3ba3c41a98226cffc1754.jpg"
}

三、定义布局, 获取图片的布局



其中,两个TextView 只是方便调试用的,例如显示当前点击的Button等。

两个Button 分别对应获取两个不同的图片资源。

ImageView 为把从网络获取到的图片显示出来

四、MainActivity 主要调用操作

class MainActivity : AppCompatActivity(), View.OnClickListener {private lateinit var context: Contextprivate lateinit var networkUtils: NetworkUtilsprivate lateinit var networkUtilsAsyncTask: NetworkUtilsAsyncTaskprivate lateinit var networkUtilsCoroutines: NetworkUtilsCoroutinescompanion object {const val TAG = "MainActivity"}//可以变数组, 添加图片URLprivate val urlList = mutableListOf(URL(CommonConstants.Address1), URL(CommonConstants.Address2))//根据Button Id 获取对应的图片URLprivate var urlMap = mutableMapOf()// 根据Button Id 获取对应是第几个Buttonprivate var buttonIndexMap = mutableMapOf()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)context = thisnetworkUtils = NetworkUtils(this)networkUtilsAsyncTask = NetworkUtilsAsyncTask(this)networkUtilsCoroutines = NetworkUtilsCoroutines(this)//button1, button2 ...buttonIndexMap[get_picture_button1.id] = 1buttonIndexMap[get_picture_button2.id] = 2urlMap[get_picture_button1.id] = urlList[0]urlMap[get_picture_button2.id] = urlList[1]}override fun onClick(v: View?) {when (v?.id) {get_picture_button1.id, get_picture_button2.id -> {text1.text = "Button : " + buttonIndexMap[v.id] + " is clicked!!!"//loadPictureDirectly(v.id)//loadPictureAsyncTask(v.id)loadPictureCoroutines(v.id)}}}fun setResult(bitmap: Bitmap?) {if (bitmap != null) {Toast.makeText(context, "Load picture success!!!", Toast.LENGTH_SHORT).show()image_view.setImageBitmap(bitmap)} else {Toast.makeText(context, "Can not load picture !!!", Toast.LENGTH_SHORT).show()}}// 1. 使用Thread - 此方法获取的图片存在错误可能,// 例如第一次点击,获取不到图片; 第二次点击,显示的却是第一次点击的获取的图片?// --> 多线程问题private fun loadPictureDirectly(id: Int) {var bitmap = urlMap[id]?.let { networkUtils.loadPicture(it) }setResult(bitmap)}//2. 使用AsyncTask -  一个AsyncTask 仅能被执行一次//AndroidRuntime:              Caused by: java.lang.IllegalStateException:// Cannot execute task: the task has already been executed (a task can be executed only once)private fun loadPictureAsyncTask(id: Int) {if (networkUtilsAsyncTask != null) {networkUtilsAsyncTask.cancel(true)networkUtilsAsyncTask = NetworkUtilsAsyncTask(this)}urlMap[id]?.let { networkUtilsAsyncTask.execute(it) }}//3. 使用协程 - 看似同步的代码实现异步效果,private fun loadPictureCoroutines(id: Int) {// 在主线程开启一个协程CoroutineScope(Dispatchers.Main).launch {// 切换到IO 线程 - withContext 能在指定IO 线程执行完成后,切换原来的线程var bitmap = withContext(Dispatchers.IO) {text2.text = Thread.currentThread().name.toString()urlMap[id]?.let { networkUtilsCoroutines.loadPicture(it) }}// 切换了UI 线程,更新UItext2.text = Thread.currentThread().name.toString()setResult(bitmap)}}/*    private suspend fun loadPictureCoroutinesInner(id: Int): Bitmap? {return withContext(Dispatchers.IO) {urlMap[id]?.let { networkUtilsCoroutines.loadPicture(it) }}}*/
}


其中,两个图片地址定义为:

object CommonConstants {const val Address1 ="http://e.hiphotos.baidu.com/zhidao/pic/item/8cb1cb1349540923f12939199458d109b3de4910.jpg"const val Address2 ="http://e.hiphotos.baidu.com/zhidao/pic/item/aec379310a55b31907d3ba3c41a98226cffc1754.jpg"
}


五、需要在AndroidManifest.xml中添加网络权限,否则会报错(缺少网络权限)

AndroidRuntime: java.lang.SecurityException: Permission denied (missing INTERNET permission?)

六、使用http 网址,还需要在配置文件中设置 usesCleartextTraffic ,否则会报错

AndroidRuntime: java.io.IOException: Cleartext HTTP traffic to XXX not permitted   

七, 若需要使用旧版的HttpClient ,则需在官网下载jar 包,然后导入项目中 ----》 这种方法不好....

Apache HttpComponents – HttpComponents Downloads

若有如下错误,可能是因为使用导入的HttpClient相关的,和手机系统F/W上的上冲突了

09-02 10:09:10.888 E 21378    26514    AndroidRuntime:                                                         java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/message/LineFormatter; 
09-02 10:09:10.888 E 21378    26514    AndroidRuntime:                                                             at org.apache.http.impl.conn.ManagedHttpClientConnectionFactory.(ManagedHttpClientConnectionFactory.java:66) 
09-02 10:09:10.888 E 21378    26514    AndroidRuntime:                                                             at org.apache.http.impl.conn.ManagedHttpClientConnectionFactory.(ManagedHttpClientConnectionFactory.java:78) 
09-02 10:09:10.888 E 21378    26514    AndroidRuntime:                                                             at org.apache.http.impl.conn.ManagedHttpClientConnectionFactory.(ManagedHttpClientConnectionFactory.java:57) 
09-02 10:09:10.888 E 21378    26514    AndroidRuntime:                                                             at org.apache.http.impl.conn.PoolingHttpClientConnectionManager$InternalConnectionFactory.(PoolingHttpClientConnectionManager.java:499)  09-02 09:44:31.569 E 20772    21629    AndroidRuntime:                                                         java.lang.NoSuchFieldError: No static field INSTANCE of type Lorg/apache/http/conn/ssl/AllowAllHostnameVerifier; in class Lorg/apache/http/conn/ssl/AllowAllHostnameVerifier; or its superclasses (declaration of org.apache.http.conn.ssl.AllowAllHostnameVerifier appears in /system/framework/framework.jar!classes4.dex) 
09-02 09:44:31.569 E 20772    21629    AndroidRuntime:                                                             at org.apache.http.conn.ssl.SSLConnectionSocketFactory.(SSLConnectionSocketFactory.java:151) 
09-02 09:44:31.569 E 20772    21629    AndroidRuntime:                                                             at org.apache.http.conn.ssl.SSLConnectionSocketFactory.getSocketFactory(SSLConnectionSocketFactory.java:194) 
09-02 09:44:31.569 E 20772    21629    AndroidRuntime:                                                             at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.getDefaultRegistry(PoolingHttpClientConnectionMa

可以使用以下方法解决(删掉导入的所有Http 包)

1. AndroidManifest.xml 



2. 所在模块 的build.gradle

android {useLibrary 'org.apache.http.legacy'
}dependencies {...implementation group: 'org.apache.httpcomponents', name:'httpclient-android', version: '4.3.5'
}

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...