Kotlin實現(xiàn)網(wǎng)絡(luò)圖片下載和保存功能
一、理論基礎(chǔ)
- 掌握Kotlin面向?qū)ο蟮能浖_發(fā)方面的基礎(chǔ)知識。
- 鞏固前期Activity、UI控件的使用。
- 掌握Handler和Http請求的特點及用法。
二、實驗?zāi)康?/h2>
根據(jù)Android多線程和網(wǎng)絡(luò)編程的知識講解和案例使用,使用Handler消息機(jī)制實現(xiàn)網(wǎng)絡(luò)圖片下載,并且保存到模擬器中,強(qiáng)化對Android多線程編程、網(wǎng)絡(luò)編程和文件讀寫的理解。要求:
- 鞏固Android應(yīng)用開發(fā)工具(Android Studio)的常規(guī)用法;
- 鞏固Activity、UI控件的常規(guī)用法;
- 掌握Handler的編程要點;
- 掌握HTTP獲取網(wǎng)絡(luò)資源的方法。
- 掌握文件輸入輸出流的寫法。
三、實驗步驟
1、新建工程文件
首先打開Android Studio,新建Project,命名為WebDownload,Language為Kotlin,Minimum SDK選擇API 22,然后包名就是com.android.webdownload,回車創(chuàng)建成功,等待下載依賴進(jìn)行build。

2、引入布局管理
首先在模塊的build.gradle中加上下面的閉包,然后同步
buildFeatures {
viewBinding true
}在MainActivity里面先定義變量,
private lateinit var binding: ActivityMainBinding
在onCreate()方法中,添加如下代碼:
binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root)
這下,我們想訪問某個控件,直接通過binding對象獲取即可,比如:
binding.tvShow.text = "下載成功!"
3、創(chuàng)建布局
外層父容器選擇LinearLayout,內(nèi)部元素對齊方式選擇vertical,從上至下放了一個ProgressBar,設(shè)置了max為100,進(jìn)度條顯示下載進(jìn)度。接著是一個TextView,顯示下載信息;ImageView是圖片框,顯示圖片。還有下載圖片和終止下載兩個按鈕在最底下。里面的textSize和textColor這些屬性就自定義設(shè)置,沒什么可講的。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical">
<ProgressBar
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:layout_marginBottom="20dp"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"/>
<TextView
android:id="@+id/tv_show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:layout_margin="10dp"
android:gravity="center" />
<ImageView
android:id="@+id/iv_show"
android:layout_width="400dp"
android:layout_height="400dp"
android:layout_gravity="center"
android:background="@mipmap/ic_launcher"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_download"
android:layout_width="0dp"
android:text="下載圖片"
android:layout_margin="10dp"
android:textSize="20sp"
android:padding="10dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<Button
android:id="@+id/btn_stop"
android:layout_width="0dp"
android:text="終止下載"
android:layout_margin="10dp"
android:textSize="20sp"
android:padding="10dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
</LinearLayout>
來看下簡單的布局,實現(xiàn)效果就是點擊下載,開始下載網(wǎng)絡(luò)圖片,下載好后顯示在圖片框上,而且保存到模擬器的存儲空間中,下載過程中可以停止下載(如果你的手速比網(wǎng)速快的前提):

4、訪問權(quán)限
因為要發(fā)送網(wǎng)絡(luò)請求,所以需要訪問網(wǎng)絡(luò),因為要保持圖片到模擬器,所以要文件讀取,還要有向SD卡中創(chuàng)建或者刪除的權(quán)限。網(wǎng)絡(luò)是不需要動態(tài)申請的,但后面兩個權(quán)限需要。
<!-- 訪問網(wǎng)絡(luò)的權(quán)限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 文件讀取的權(quán)限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 向SD卡中創(chuàng)建或者刪除的權(quán)限。 -->
<uses-permission android:name="andorid.permission.MONUN_UNMOUNT_FILESYSTEMS"/>動態(tài)申請權(quán)限的代碼:
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
}在彈出對話框用戶操作之后,返回應(yīng)用權(quán)限請求的結(jié)果。requestCode是請求碼,permissions是權(quán)限列表,grantResults是允許的結(jié)果數(shù)組。運用if-else分支完成同意權(quán)限和不同意的邏輯。
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when(requestCode) {
1-> {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "你可以正常使用app", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "你拒絕了權(quán)限,無法正常保存圖片", Toast.LENGTH_SHORT).show()
}
}
}
}5、實現(xiàn)邏輯
首先也是最核心的就是從網(wǎng)絡(luò)上下載圖片,顯然可以用HttpURLConnection或者OkHttp或者其他更好的網(wǎng)絡(luò)框架,總之就是和url地址創(chuàng)建輸入流,讀取文件,保存為bitmap格式返回,然后關(guān)閉輸入流。
// 下載圖片,轉(zhuǎn)為位圖
fun getImage(imageUrl: String): Bitmap? {
var myBitmap:Bitmap? = null
var connection:HttpURLConnection
try {
var url = URL(imageUrl)
connection = url.openConnection() as HttpURLConnection
connection.connectTimeout = 8000
connection.doInput = true
connection.useCaches = false
val myInput = connection.inputStream
myBitmap = BitmapFactory.decodeStream(myInput)
myInput.close()
} catch (e:Exception) {
e.printStackTrace()
}
return myBitmap;
}接下來是保存圖片到模擬器外部存儲,這一點就是文件的IO流,Java中流的知識太常用了。先定義目錄,如果不存在則創(chuàng)建目錄。try-catch包圍圈里面創(chuàng)建輸出流,保存的地址就是在之前的文件夾路徑基礎(chǔ)上加上了文件名和后綴,然后bitmap按照指定圖像格式進(jìn)行壓縮,最后關(guān)閉輸出流。
// 保存位圖到本地路徑
private fun saveImage(bitmap:Bitmap?) {
var file = File(saveDirs)
// 如果文件不存在則創(chuàng)建目錄
if (!file.exists()) {
if (file.mkdir()) {
Log.d("test", "mkdir")
} else {
Log.d("test", "failed")
}
}
try {
val fileOutputStream = FileOutputStream(savePath)
bitmap?.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)
fileOutputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
}這里的saveImage和savePath要著重講下,不然很容易運行后圖片無法保存到本地,原來都是路徑?jīng)]有搞清楚的問題。var file = File(saveDirs)是創(chuàng)建文件夾。savePath才是圖片的保存路徑,如果savePath寫的是"\android\scared\0\storage之類的"那是怎么也訪問不了存儲空間的。
private val saveDirs:String = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() private val savePath:String = saveDirs + File.separator + System.currentTimeMillis() + ".png"
最后就是在子線程中下載網(wǎng)絡(luò)圖片并使用Handler發(fā)送message消息了,這段邏輯反而是最簡單的部分??梢钥吹綔?zhǔn)備下載前和下載完成后都發(fā)送message。what分別是0和1。
// 創(chuàng)建下載圖片的子線程,準(zhǔn)備下載前和下載完成后都發(fā)送message
val thread1 = Thread {
val message = Message()
message.what = 0
handler.sendMessage(message)
bitmap = getImage(url)
val message2 = Message()
message2.what = 1
handler.sendMessage(message2)
}定義全局變量handler來接收自己發(fā)給自己的消息,0那就模擬進(jìn)度條下載進(jìn)度(因為網(wǎng)絡(luò)圖片的進(jìn)度無法通過子線程預(yù)先獲取到,這是后驗的)。1就是binding.ivShow.setImageBitmap(bitmap)設(shè)置位圖,文本為"下載成功!",提示消息也為"下載已完成"。
val handler:Handler = Handler {
when(it.what) {
0-> {
for (i in 0..100) binding.progress.progress = i
}
1 -> {
binding.ivShow.background = null
binding.ivShow.setImageBitmap(bitmap)
saveImage(bitmap)
binding.tvShow.text = "下載成功!"
Toast.makeText(this, "下載已完成", Toast.LENGTH_SHORT).show()
}
else -> Log.d("Test", "else")
}
false
};開始下載按鈕就是開啟線程,停止下載按鈕就是終止線程。
binding.btnDownload.setOnClickListener({
try {
thread1.start()
} catch (e:Exception) {
e.printStackTrace()
}
})
binding.btnStop.setOnClickListener({
if (thread1.isAlive) {
thread1.interrupt()
Toast.makeText(this, "interrupt()", Toast.LENGTH_SHORT).show()
}
if (!thread1.isAlive) {
binding.tvShow.text = "下載終止!"
Toast.makeText(this, "下載已終止", Toast.LENGTH_SHORT).show()
}
})完整的MainActivity代碼如下:
package com.android.webdownload
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.*
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.android.webdownload.databinding.ActivityMainBinding
import java.io.File
import java.io.FileOutputStream
import java.lang.Exception
import java.lang.ref.WeakReference
import java.net.HttpURLConnection
import java.net.URL
import java.util.jar.Manifest
class MainActivity : AppCompatActivity() {
private var bitmap:Bitmap? = null
private lateinit var binding: ActivityMainBinding
private val url:String = "http://e.hiphotos.baidu.com/image/pic/item/4e4a20a4462309f7e41f5cfe760e0cf3d6cad6ee.jpg"
private val saveDirs:String = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString()
private val savePath:String = saveDirs + File.separator + System.currentTimeMillis() + ".png"
val handler:Handler = Handler {
when(it.what) {
0-> {
for (i in 0..100) binding.progress.progress = i
}
1 -> {
binding.ivShow.background = null
binding.ivShow.setImageBitmap(bitmap)
saveImage(bitmap)
binding.tvShow.text = "下載成功!"
Toast.makeText(this, "下載已完成", Toast.LENGTH_SHORT).show()
}
else -> Log.d("Test", "else")
}
false
};
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
}
binding.btnDownload.setOnClickListener({
try {
thread1.start()
} catch (e:Exception) {
e.printStackTrace()
}
})
binding.btnStop.setOnClickListener({
if (thread1.isAlive) {
thread1.interrupt()
Toast.makeText(this, "interrupt()", Toast.LENGTH_SHORT).show()
}
if (!thread1.isAlive) {
binding.tvShow.text = "下載終止!"
Toast.makeText(this, "下載已終止", Toast.LENGTH_SHORT).show()
}
})
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when(requestCode) {
1-> {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "你可以正常使用app", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "你拒絕了權(quán)限,無法正常保存圖片", Toast.LENGTH_SHORT).show()
}
}
}
}
// 創(chuàng)建下載圖片的子線程,準(zhǔn)備下載前和下載完成后都發(fā)送message
val thread1 = Thread {
val message = Message()
message.what = 0
handler.sendMessage(message)
bitmap = getImage(url)
val message2 = Message()
message2.what = 1
handler.sendMessage(message2)
}
// 下載圖片,轉(zhuǎn)為位圖
fun getImage(imageUrl: String): Bitmap? {
var myBitmap:Bitmap? = null
var connection:HttpURLConnection
try {
var url = URL(imageUrl)
connection = url.openConnection() as HttpURLConnection
connection.connectTimeout = 8000
connection.doInput = true
connection.useCaches = false
val myInput = connection.inputStream
myBitmap = BitmapFactory.decodeStream(myInput)
myInput.close()
} catch (e:Exception) {
e.printStackTrace()
}
return myBitmap;
}
// 保存位圖到本地路徑
private fun saveImage(bitmap:Bitmap?) {
var file = File(saveDirs)
// 如果文件不存在則創(chuàng)建目錄
if (!file.exists()) {
if (file.mkdir()) {
Log.d("test", "mkdir")
} else {
Log.d("test", "failed")
}
}
try {
val fileOutputStream = FileOutputStream(savePath)
bitmap?.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)
fileOutputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
四、實驗演示
1、首先看到啟動頁面就是下載頁面,頂部的進(jìn)度條和TextView并沒有顯示出來,因為沒有內(nèi)容。

2、點擊下載圖片按鈕,圖片很快下載完成,并且頂部進(jìn)度條也加載完畢了,圖片下載好后顯示在屏幕中央。在下載過程中可以停止下載,不過由于下載過快,根本來不及停止。

3、打開文件,選擇Android SDK built for x86

選擇Pictures

可以看到剛剛下載好的圖片

點擊全圖查看

五、實驗總結(jié)
總體上來說還是非常基礎(chǔ)的內(nèi)容,考察的點不多,可以作為學(xué)習(xí)Kotlin的點心食用,中間需要注意的小地方還是有的。越是簡單的東西遇到的問題越多,多練習(xí)才是王道。
到此這篇關(guān)于Kotlin實現(xiàn)網(wǎng)絡(luò)圖片下載和保存功能的文章就介紹到這了,更多相關(guān)Kotlin網(wǎng)絡(luò)圖片下載和保存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Compose自定義View實現(xiàn)繪制Rainbow運動三環(huán)效果
這篇文章主要為大家介紹了一個基于Compose自定義的一個Rainbow彩虹運動三環(huán),業(yè)務(wù)上類似于iWatch上的那個運動三環(huán),感興趣的小伙伴可以了解一下2023-02-02
android實現(xiàn)簡單進(jìn)度條ProgressBar效果
這篇文章主要為大家詳細(xì)介紹了android實現(xiàn)簡單進(jìn)度條ProgressBar效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07
android手機(jī)端與PC端使用adb forword通信
這篇文章主要介紹了android手機(jī)端與PC端使用adb forword通信的相關(guān)資料,需要的朋友可以參考下2017-04-04
android TextView多行文本(超過3行)使用ellipsize屬性無效問題的解決方法
這篇文章介紹了android TextView多行文本(超過3行)使用ellipsize屬性無效問題的解決方法,有需要的朋友可以參考一下2013-09-09
Android隨手筆記44之JSON數(shù)據(jù)解析
本文將主要介紹在Android開發(fā)中,如何在服務(wù)器端創(chuàng)建JSON數(shù)據(jù),以及如何在Android客戶端對JSON數(shù)據(jù)進(jìn)行解析,對android json解析 相關(guān)知識感興趣的朋友一起學(xué)習(xí)吧2015-12-12

