Kotlin實現(xiàn)網(wǎng)絡(luò)圖片下載和保存功能
一、理論基礎(chǔ)
- 掌握Kotlin面向?qū)ο蟮能浖_發(fā)方面的基礎(chǔ)知識。
- 鞏固前期Activity、UI控件的使用。
- 掌握Handler和Http請求的特點及用法。
二、實驗?zāi)康?/h2>
根據(jù)Android多線程和網(wǎng)絡(luò)編程的知識講解和案例使用,使用Handler消息機制實現(xiàn)網(wǎng)絡(luò)圖片下載,并且保存到模擬器中,強化對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)建成功,等待下載依賴進行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,進度條顯示下載進度。接著是一個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按照指定圖像格式進行壓縮,最后關(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消息了,這段邏輯反而是最簡單的部分??梢钥吹綔蕚湎螺d前和下載完成后都發(fā)送message。what分別是0和1。
// 創(chuàng)建下載圖片的子線程,準備下載前和下載完成后都發(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那就模擬進度條下載進度(因為網(wǎng)絡(luò)圖片的進度無法通過子線程預(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)建下載圖片的子線程,準備下載前和下載完成后都發(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、首先看到啟動頁面就是下載頁面,頂部的進度條和TextView并沒有顯示出來,因為沒有內(nèi)容。
2、點擊下載圖片按鈕,圖片很快下載完成,并且頂部進度條也加載完畢了,圖片下載好后顯示在屏幕中央。在下載過程中可以停止下載,不過由于下載過快,根本來不及停止。
3、打開文件,選擇Android SDK built for x86
選擇Pictures
可以看到剛剛下載好的圖片
點擊全圖查看
五、實驗總結(jié)
總體上來說還是非常基礎(chǔ)的內(nèi)容,考察的點不多,可以作為學(xué)習Kotlin的點心食用,中間需要注意的小地方還是有的。越是簡單的東西遇到的問題越多,多練習才是王道。
到此這篇關(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-02android實現(xiàn)簡單進度條ProgressBar效果
這篇文章主要為大家詳細介紹了android實現(xiàn)簡單進度條ProgressBar效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07android TextView多行文本(超過3行)使用ellipsize屬性無效問題的解決方法
這篇文章介紹了android TextView多行文本(超過3行)使用ellipsize屬性無效問題的解決方法,有需要的朋友可以參考一下2013-09-09Android隨手筆記44之JSON數(shù)據(jù)解析
本文將主要介紹在Android開發(fā)中,如何在服務(wù)器端創(chuàng)建JSON數(shù)據(jù),以及如何在Android客戶端對JSON數(shù)據(jù)進行解析,對android json解析 相關(guān)知識感興趣的朋友一起學(xué)習吧2015-12-12