• Kotlin_获取网络图片(HttpURLConnection, AsyncTask,协程)


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

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

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

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

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

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

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

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

    1. // 获取网络图片实现类
    2. class NetworkUtils {
    3.     private var picture : Bitmap ?= null
    4.     private var context: Context
    5.  
    6.     companion object{
    7.         const val TAG = "NetworkUtils"
    8.     }
    9.  
    10.     constructor(context: Context) {
    11.         this.context = context
    12.     }
    13.  
    14.     // 获取网络图片
    15.     fun loadPicture(url: URL): Bitmap? {
    16.         // 开启一个单独线程进行网络读取
    17.         Thread(Runnable {
    18.             var bitmap: Bitmap ? = null
    19.             try {
    20.                 // 根据URL 实例, 获取HttpURLConnection 实例
    21.                 var httpURLConnection: HttpURLConnection = url.openConnection() as HttpURLConnection
    22.                 // 设置读取 和 连接 time out 时间
    23.                 httpURLConnection.readTimeout = 2000
    24.                 httpURLConnection.connectTimeout = 2000
    25.                 // 获取图片输入流
    26.                 var inputStream = httpURLConnection.inputStream
    27.                 // 获取网络响应结果
    28.                 var responseCode = httpURLConnection.responseCode
    29.  
    30.                 // 获取正常
    31.                 if (responseCode == HttpURLConnection.HTTP_OK) {
    32.                     // 解析图片
    33.                     bitmap = BitmapFactory.decodeStream(inputStream)
    34.                 }
    35.             } catch(e: IOException) { // 捕获异常 (例如网络异常)
    36.                 Log.d(TAG, "loadPicture - error: ${e?.toString()}")
    37.             }
    38.  
    39.             this.picture = bitmap
    40.         }).start()
    41.         // 返回的图片可能为空- 多线程 - 上面的线程还没跑完,已经返回 结果了
    42.         return picture
    43.     }
    44. }

    第二种是使用AsyncTask.

    1. // 三个泛型参数, 第一个为执行,第二个进度,第三个返回
    2. class NetworkUtilsAsyncTask : AsyncTask<URL, Int, Bitmap> {
    3.     private var resultPicture: Bitmap? = null
    4.     private lateinit var context: Context
    5.  
    6.     companion object {
    7.         const val TAG = "NetworkUtilsAsyncTask"
    8.     }
    9.  
    10.     constructor(context: Context) {
    11.         this.context = context
    12.     }
    13.  
    14.     override fun doInBackground(vararg params: URL?): Bitmap? {
    15.         return loadPicture(params[0])
    16.     }
    17.  
    18.     // 获取网络图片
    19.     private fun loadPicture(url: URL?): Bitmap? {
    20.         // 开启一个单独线程进行网络读取
    21.         var bitmapFromNetwork: Bitmap? = null
    22.         url?.let {
    23.             try {
    24.                 // 根据URL 实例, 获取HttpURLConnection 实例
    25.                 var httpURLConnection: HttpURLConnection = url.openConnection() as HttpURLConnection
    26.                 // 设置读取 和 连接 time out 时间
    27.                 httpURLConnection.readTimeout = 2000
    28.                 httpURLConnection.connectTimeout = 2000
    29.                 // 获取图片输入流
    30.                 var inputStream = httpURLConnection.inputStream
    31.                 // 获取网络响应结果
    32.                 var responseCode = httpURLConnection.responseCode
    33.                 Log.d(TAG, "loadPicture - responseCode: $responseCode")
    34.  
    35.                 // 获取正常
    36.                 if (responseCode == HttpURLConnection.HTTP_OK) {
    37.                     // 解析图片
    38.                     bitmapFromNetwork = BitmapFactory.decodeStream(inputStream)
    39.                 }
    40.             } catch (e: IOException) { // 捕获异常 (例如网络异常)
    41.                 Log.d(TAG, "loadPicture - error: ${e?.toString()}")
    42.                 //printErrorMessage(e?.toString())
    43.             }
    44.             Log.d(TAG, "loadPicture - bitmapFromNetwork: $bitmapFromNetwork")
    45.             this.resultPicture = bitmapFromNetwork
    46.             // 返回的图片可能为空
    47.         }
    48.         Log.d(TAG, "loadPicture - resultPicture: $resultPicture")
    49.         return resultPicture
    50.     }
    51.  
    52.     // 调用UI线程的更新UI操作
    53.     override fun onPostExecute(result: Bitmap?) {
    54.         super.onPostExecute(result)
    55.         Log.d(TAG, "onPostExecute - result: $result")
    56.         if (context is MainActivity) {
    57.             (context as MainActivity).setResult(result)
    58.         }
    59.     }
    60. }

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

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

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

    Progress: 进度条, 一般为Int

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

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

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

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

    1. class NetworkUtilsCoroutines {
    2.  
    3.     private var resultPicture: Bitmap? = null
    4.     private var context: Context
    5.  
    6.     companion object {
    7.         const val TAG = "NetworkUtilsCoroutines"
    8.     }
    9.  
    10.     constructor(context: Context) {
    11.         this.context = context
    12.     }
    13.  
    14.     // 获取网络图片
    15.     fun loadPicture(url: URL): Bitmap? {
    16.         // 开启一个单独线程进行网络读取
    17.         var bitmapFromNetwork: Bitmap? = null
    18.         try {
    19.             // 根据URL 实例, 获取HttpURLConnection 实例
    20.             var httpURLConnection: HttpURLConnection = url.openConnection() as HttpURLConnection
    21.             // 设置读取 和 连接 time out 时间
    22.             httpURLConnection.readTimeout = 2000
    23.             httpURLConnection.connectTimeout = 2000
    24.             // 获取图片输入流
    25.             var inputStream = httpURLConnection.inputStream
    26.             // 获取网络响应结果
    27.             var responseCode = httpURLConnection.responseCode
    28.  
    29.             // 获取正常
    30.             if (responseCode == HttpURLConnection.HTTP_OK) {
    31.                 // 解析图片
    32.                 bitmapFromNetwork = BitmapFactory.decodeStream(inputStream)
    33.             }
    34.         } catch (e: IOException) { // 捕获异常 (例如网络异常)
    35.             Log.d(TAG, "loadPicture - error: ${e?.toString()}")
    36.             //printErrorMessage(e?.toString())
    37.         }
    38.  
    39.         this.resultPicture = bitmapFromNetwork
    40.         return resultPicture
    41.     }
    42.  
    43. }

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

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

    ext.kotlin_coroutines = '1.3.1'

    APP目录的build.gradle:   

    1. implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines"
    2.  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

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

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

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

    1. "1.0" encoding="utf-8"?>
    2. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3.     xmlns:app="http://schemas.android.com/apk/res-auto"
    4.     xmlns:tools="http://schemas.android.com/tools"
    5.     android:layout_width="match_parent"
    6.     android:layout_height="match_parent"
    7.     tools:context=".MainActivity">
    8.  
    9.     <TextView
    10.         android:id="@+id/text1"
    11.         android:layout_width="match_parent"
    12.         android:layout_height="wrap_content"
    13.         android:textSize="@dimen/text_size_20"
    14.         app:layout_constraintStart_toStartOf="parent"
    15.         app:layout_constraintTop_toTopOf="parent" />
    16.     <TextView
    17.         android:id="@+id/text2"
    18.         android:layout_width="match_parent"
    19.         android:layout_height="wrap_content"
    20.         android:textSize="@dimen/text_size_20"
    21.         app:layout_constraintStart_toStartOf="@id/text1"
    22.         app:layout_constraintTop_toBottomOf="@id/text1"/>
    23.  
    24.     <Button
    25.         android:id="@+id/get_picture_button1"
    26.         android:layout_width="wrap_content"
    27.         android:layout_height="wrap_content"
    28.         android:text="Get picture1"
    29.         android:onClick="onClick"
    30.         app:layout_constraintStart_toStartOf="@id/text2"
    31.         app:layout_constraintTop_toBottomOf="@id/text2"/>
    32.     <Button
    33.         android:id="@+id/get_picture_button2"
    34.         android:layout_width="wrap_content"
    35.         android:layout_height="wrap_content"
    36.         android:text="Get picture2"
    37.         android:onClick="onClick"
    38.         app:layout_constraintStart_toEndOf="@id/get_picture_button1"
    39.         app:layout_constraintTop_toBottomOf="@id/text2"/>
    40.  
    41.     <ImageView
    42.         android:id="@+id/image_view"
    43.         android:layout_width="wrap_content"
    44.         android:layout_height="wrap_content"
    45.         app:layout_constraintStart_toStartOf="@id/get_picture_button1"
    46.         app:layout_constraintTop_toBottomOf="@id/get_picture_button1" />
    47.  
    48. androidx.constraintlayout.widget.ConstraintLayout>


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

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

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

    四、MainActivity 主要调用操作

    1. class MainActivity : AppCompatActivity(), View.OnClickListener {
    2.     private lateinit var context: Context
    3.     private lateinit var networkUtils: NetworkUtils
    4.     private lateinit var networkUtilsAsyncTask: NetworkUtilsAsyncTask
    5.     private lateinit var networkUtilsCoroutines: NetworkUtilsCoroutines
    6.  
    7.     companion object {
    8.         const val TAG = "MainActivity"
    9.     }
    10.  
    11.     //可以变数组, 添加图片URL
    12.     private val urlList = mutableListOf(
    13.         URL(CommonConstants.Address1), URL(CommonConstants.Address2)
    14.     )
    15.     //根据Button Id 获取对应的图片URL
    16.     private var urlMap = mutableMapOf<Int, URL>()
    17.     // 根据Button Id 获取对应是第几个Button
    18.     private var buttonIndexMap = mutableMapOf<Int, Int>()
    19.  
    20.     override fun onCreate(savedInstanceState: Bundle?) {
    21.         super.onCreate(savedInstanceState)
    22.         setContentView(R.layout.activity_main)
    23.  
    24.         context = this
    25.  
    26.         networkUtils = NetworkUtils(this)
    27.         networkUtilsAsyncTask = NetworkUtilsAsyncTask(this)
    28.         networkUtilsCoroutines = NetworkUtilsCoroutines(this)
    29.  
    30.  
    31.         //button1, button2 ...
    32.         buttonIndexMap[get_picture_button1.id] = 1
    33.         buttonIndexMap[get_picture_button2.id] = 2
    34.  
    35.         urlMap[get_picture_button1.id] = urlList[0]
    36.         urlMap[get_picture_button2.id] = urlList[1]
    37.     }
    38.  
    39.     override fun onClick(v: View?) {
    40.         when (v?.id) {
    41.             get_picture_button1.id, get_picture_button2.id -> {
    42.                 text1.text = "Button : " + buttonIndexMap[v.id] + " is clicked!!!"
    43.  
    44.                 //loadPictureDirectly(v.id)
    45.                 //loadPictureAsyncTask(v.id)
    46.                 loadPictureCoroutines(v.id)
    47.             }
    48.         }
    49.     }
    50.  
    51.     fun setResult(bitmap: Bitmap?) {
    52.         if (bitmap != null) {
    53.             Toast.makeText(context, "Load picture success!!!", Toast.LENGTH_SHORT).show()
    54.             image_view.setImageBitmap(bitmap)
    55.         } else {
    56.             Toast.makeText(context, "Can not load picture !!!", Toast.LENGTH_SHORT).show()
    57.         }
    58.     }
    59.  
    60.     // 1. 使用Thread - 此方法获取的图片存在错误可能,
    61.     // 例如第一次点击,获取不到图片; 第二次点击,显示的却是第一次点击的获取的图片?
    62.     // --> 多线程问题
    63.     private fun loadPictureDirectly(id: Int) {
    64.         var bitmap = urlMap[id]?.let { networkUtils.loadPicture(it) }
    65.         setResult(bitmap)
    66.     }
    67.  
    68.     //2. 使用AsyncTask -  一个AsyncTask 仅能被执行一次
    69.     //AndroidRuntime:              Caused by: java.lang.IllegalStateException:
    70.     // Cannot execute task: the task has already been executed (a task can be executed only once)
    71.     private fun loadPictureAsyncTask(id: Int) {
    72.         if (networkUtilsAsyncTask != null) {
    73.             networkUtilsAsyncTask.cancel(true)
    74.             networkUtilsAsyncTask = NetworkUtilsAsyncTask(this)
    75.         }
    76.         urlMap[id]?.let { networkUtilsAsyncTask.execute(it) }
    77.     }
    78.  
    79.     //3. 使用协程 - 看似同步的代码实现异步效果,
    80.     private fun loadPictureCoroutines(id: Int) {
    81.         // 在主线程开启一个协程
    82.         CoroutineScope(Dispatchers.Main).launch {
    83.             // 切换到IO 线程 - withContext 能在指定IO 线程执行完成后,切换原来的线程
    84.             var bitmap = withContext(Dispatchers.IO) {
    85.                 text2.text = Thread.currentThread().name.toString()
    86.                 urlMap[id]?.let { networkUtilsCoroutines.loadPicture(it) }
    87.             }
    88.             // 切换了UI 线程,更新UI
    89.             text2.text = Thread.currentThread().name.toString()
    90.             setResult(bitmap)
    91.         }
    92.     }
    93.  
    94. /*    private suspend fun loadPictureCoroutinesInner(id: Int): Bitmap? {
    95.         return withContext(Dispatchers.IO) {
    96.             urlMap[id]?.let { networkUtilsCoroutines.loadPicture(it) }
    97.         }
    98.     }*/
    99. }


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

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


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

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

    "android.permission.INTERNET" />

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

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

    1.         ...
    2.         android:usesCleartextTraffic="true"
    3.         ...
    4.    

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

    Apache HttpComponents – HttpComponents Downloads

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

    1. 09-02 10:09:10.888 E 21378    26514    AndroidRuntime:                                                         java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/message/LineFormatter; 
    2. 09-02 10:09:10.888 E 21378    26514    AndroidRuntime:                                                             at org.apache.http.impl.conn.ManagedHttpClientConnectionFactory.<init>(ManagedHttpClientConnectionFactory.java:66
    3. 09-02 10:09:10.888 E 21378    26514    AndroidRuntime:                                                             at org.apache.http.impl.conn.ManagedHttpClientConnectionFactory.<init>(ManagedHttpClientConnectionFactory.java:78
    4. 09-02 10:09:10.888 E 21378    26514    AndroidRuntime:                                                             at org.apache.http.impl.conn.ManagedHttpClientConnectionFactory.(ManagedHttpClientConnectionFactory.java:57
    5. 09-02 10:09:10.888 E 21378    26514    AndroidRuntime:                                                             at org.apache.http.impl.conn.PoolingHttpClientConnectionManager$InternalConnectionFactory.<init>(PoolingHttpClientConnectionManager.java:499)  
    6.  
    7. 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) 
    8. 09-02 09:44:31.569 E 20772    21629    AndroidRuntime:                                                             at org.apache.http.conn.ssl.SSLConnectionSocketFactory.(SSLConnectionSocketFactory.java:151
    9. 09-02 09:44:31.569 E 20772    21629    AndroidRuntime:                                                             at org.apache.http.conn.ssl.SSLConnectionSocketFactory.getSocketFactory(SSLConnectionSocketFactory.java:194
    10. 09-02 09:44:31.569 E 20772    21629    AndroidRuntime:                                                             at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.getDefaultRegistry(PoolingHttpClientConnectionMa

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

    1. AndroidManifest.xml 

    1. "1.0" encoding="utf-8"?>
    2. "http://schemas.android.com/apk/res/android"/>
    3.    
    4.         //...
    5.         "org.apache.http.legacy" android:required="false" />
    6.    

    2. 所在模块 的build.gradle

    1. android {
    2.     useLibrary 'org.apache.http.legacy'
    3. }
    4.  
    5. dependencies {
    6.     ...
    7.     implementation group: 'org.apache.httpcomponents', name:'httpclient-android', version: '4.3.5'
    8. }

  • 相关阅读:
    信息系统项目管理-组织级项目管理-十八
    sql---慢查询和语句耗时
    前端-防止多次点击
    基于JAVA婚纱租赁系统的设计与实现
    飞行堡垒系列_键盘灯开关
    MMRotate 全面升级,新增 BoxType 设计
    线性代数学习笔记4-4:求解非齐次线性方程组Ax=b,从秩的角度看方程
    发挥数字化平台优势,电子元器件采购商城系统助力企业改变固有低效流程
    JDK API
    Jmeter常用函数用法详解
  • 原文地址:https://blog.csdn.net/Goals1989/article/details/127983363