协程、AsyncTask、new Thread() 访问网络的对比
使用Kotlin 从网络获取网络图片,需要学习 HttpURLConnection的使用, 多线程(AsyncTask)的使用等 。
先说总结,获取网络图片有几种方式:
1. 直接创建一个线程获取, 会导致显示的图片错乱。
2. 使用AsyncTask , 确保正常显示图片
3. 使用Kotlin 的协程, 用看似同步的代码写异步的操作。
一、 创建根据URL 获取图片的类
第一种方式为直接创建一个线程获取,但是这种方式是明显不可行的。
- // 获取网络图片实现类
- class NetworkUtils {
- private var picture : Bitmap ?= null
- private var context: Context
-
- companion object{
- const val TAG = "NetworkUtils"
- }
-
- constructor(context: Context) {
- this.context = context
- }
-
- // 获取网络图片
- fun loadPicture(url: URL): Bitmap? {
- // 开启一个单独线程进行网络读取
- Thread(Runnable {
- var bitmap: Bitmap ? = null
- try {
- // 根据URL 实例, 获取HttpURLConnection 实例
- var httpURLConnection: HttpURLConnection = url.openConnection() as HttpURLConnection
- // 设置读取 和 连接 time out 时间
- httpURLConnection.readTimeout = 2000
- httpURLConnection.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<URL, Int, Bitmap> {
- private var resultPicture: Bitmap? = null
- private lateinit var context: Context
-
- companion 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? = null
- url?.let {
- try {
- // 根据URL 实例, 获取HttpURLConnection 实例
- var httpURLConnection: HttpURLConnection = url.openConnection() as HttpURLConnection
- // 设置读取 和 连接 time out 时间
- httpURLConnection.readTimeout = 2000
- httpURLConnection.connectTimeout = 2000
- // 获取图片输入流
- var inputStream = httpURLConnection.inputStream
- // 获取网络响应结果
- var responseCode = httpURLConnection.responseCode
- Log.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? = null
- private var context: Context
-
- companion object {
- const val TAG = "NetworkUtilsCoroutines"
- }
-
- constructor(context: Context) {
- this.context = context
- }
-
- // 获取网络图片
- fun loadPicture(url: URL): Bitmap? {
- // 开启一个单独线程进行网络读取
- var bitmapFromNetwork: Bitmap? = null
- try {
- // 根据URL 实例, 获取HttpURLConnection 实例
- var httpURLConnection: HttpURLConnection = url.openConnection() as HttpURLConnection
- // 设置读取 和 连接 time out 时间
- httpURLConnection.readTimeout = 2000
- httpURLConnection.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 = bitmapFromNetwork
- return 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"
- }
三、定义布局, 获取图片的布局
- "1.0" encoding="utf-8"?>
- <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainActivity">
-
- <TextView
- android:id="@+id/text1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textSize="@dimen/text_size_20"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- <TextView
- android:id="@+id/text2"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textSize="@dimen/text_size_20"
- app:layout_constraintStart_toStartOf="@id/text1"
- app:layout_constraintTop_toBottomOf="@id/text1"/>
-
- <Button
- android:id="@+id/get_picture_button1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Get picture1"
- android:onClick="onClick"
- app:layout_constraintStart_toStartOf="@id/text2"
- app:layout_constraintTop_toBottomOf="@id/text2"/>
- <Button
- android:id="@+id/get_picture_button2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Get picture2"
- android:onClick="onClick"
- app:layout_constraintStart_toEndOf="@id/get_picture_button1"
- app:layout_constraintTop_toBottomOf="@id/text2"/>
-
- <ImageView
- android:id="@+id/image_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:layout_constraintStart_toStartOf="@id/get_picture_button1"
- app:layout_constraintTop_toBottomOf="@id/get_picture_button1" />
-
- androidx.constraintlayout.widget.ConstraintLayout>
其中,两个TextView 只是方便调试用的,例如显示当前点击的Button等。
两个Button 分别对应获取两个不同的图片资源。
ImageView 为把从网络获取到的图片显示出来
四、MainActivity 主要调用操作
- class MainActivity : AppCompatActivity(), View.OnClickListener {
- private lateinit var context: Context
- private lateinit var networkUtils: NetworkUtils
- private lateinit var networkUtilsAsyncTask: NetworkUtilsAsyncTask
- private lateinit var networkUtilsCoroutines: NetworkUtilsCoroutines
-
- companion object {
- const val TAG = "MainActivity"
- }
-
- //可以变数组, 添加图片URL
- private val urlList = mutableListOf(
- URL(CommonConstants.Address1), URL(CommonConstants.Address2)
- )
- //根据Button Id 获取对应的图片URL
- private var urlMap = mutableMapOf<Int, URL>()
- // 根据Button Id 获取对应是第几个Button
- private var buttonIndexMap = mutableMapOf<Int, Int>()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
-
- context = this
-
- networkUtils = NetworkUtils(this)
- networkUtilsAsyncTask = NetworkUtilsAsyncTask(this)
- networkUtilsCoroutines = NetworkUtilsCoroutines(this)
-
-
- //button1, button2 ...
- buttonIndexMap[get_picture_button1.id] = 1
- buttonIndexMap[get_picture_button2.id] = 2
-
- urlMap[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 线程,更新UI
- text2.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?)
"android.permission.INTERNET" />
六、使用http 网址,还需要在配置文件中设置 usesCleartextTraffic ,否则会报错
AndroidRuntime: java.io.IOException: Cleartext HTTP traffic to XXX not permitted
- ...
- android:usesCleartextTraffic="true"
- ...
-
七, 若需要使用旧版的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.<init>(ManagedHttpClientConnectionFactory.java:66)
- 09-02 10:09:10.888 E 21378 26514 AndroidRuntime: at org.apache.http.impl.conn.ManagedHttpClientConnectionFactory.<init>(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.<init>(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
- "1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"/> -
- //...
-
"org.apache.http.legacy" android:required="false" /> -
2. 所在模块 的build.gradle
- android {
- useLibrary 'org.apache.http.legacy'
- }
-
- dependencies {
- ...
- implementation group: 'org.apache.httpcomponents', name:'httpclient-android', version: '4.3.5'
- }
-
相关阅读:
信息系统项目管理-组织级项目管理-十八
sql---慢查询和语句耗时
前端-防止多次点击
基于JAVA婚纱租赁系统的设计与实现
飞行堡垒系列_键盘灯开关
MMRotate 全面升级,新增 BoxType 设计
线性代数学习笔记4-4:求解非齐次线性方程组Ax=b,从秩的角度看方程
发挥数字化平台优势,电子元器件采购商城系统助力企业改变固有低效流程
JDK API
Jmeter常用函数用法详解
-
原文地址:https://blog.csdn.net/Goals1989/article/details/127983363