注意:本篇筆記只適用 Android9(Pie, API28) 以前版本
Environment.getExternalStorageDirectory() 這種寫法,因安全因素在 Android10(Q, API29) 被棄用了!
甚至 AsyncTask 也在 Android11(R, API30) 被棄用!
有用到上述二種寫法的朋友,要趕緊修改您的 APP,不然,會無法通過編譯,或是 APP 在執行時會 crash!!
這篇筆記完全是應急性質,讓老人家的 APP 在 Android10 不會 crash :~(
話說 Android 已經提供了 DownloadManager 這個好用的工具,但個人覺得很難控制,尤其是低階手機/平板,DownloadManager 的參考指南就有提到,當您將要下載的檔案排入佇列後,就只能痴痴的等 DownloadManager 決定何時輪到下載您的檔案;如果想知道下載進度,還要再另外寫一段監看/監聽程式,時時去詢問 DownloadManager 的現況。
所以還是以舊的 Apache 的 HttpClient 集,自己處理 "下載" 的工作算了。
app 層級的 build.gradle
...
...
android {
compileSdkVersion 28 // 注意:一定要小於等於 28
defaultConfig {
applicationId "com.example.httpdownload"
minSdkVersion 16
targetSdkVersion 28 // 注意:一定要小於等於 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
...
}
}
// 一定要加這行, 因 AndroidStudio 已停用 httpclient
useLibrary 'org.apache.http.legacy'
}
AndroidManifest.xml
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.httpdownload">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
...
...
activity_main.xml
<?xml version="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">
<!-- 這個檔沒特別的,就是 AndroidStudio 自動產生的版面,加上 onClick 事件 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:onClick="DownloadFile"/>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
class MainActivity : AppCompatActivity()
{
private val TAG = "MainActivity"
lateinit var filePath: String
lateinit var myDir: String
// 儲存檔案權限
private val PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 0
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 先建立目錄
// 第一次執行, 因目錄尚不存在, 所以是建立目錄
// 第二次以後執行, 就變成是開啟目錄了
filePath = Environment.getExternalStorageDirectory().path + "/myDownload"
File(filePath).mkdir()
myDir = filePath
}
fun DownloadFile(vv: View)
{
if (Build.VERSION.SDK_INT>22)
{ // 若是 API23(含) 以上版本, 需請求儲存檔案的權限
if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED)
{
requestStoragePermission();
}
else
{
Toast.makeText(this, "開始下載", Toast.LENGTH_LONG).show()
DownloadFileTask().execute()
}
}
else
{
Toast.makeText(this, "開始下載", Toast.LENGTH_LONG).show()
DownloadFileTask().execute()
}
}
private inner class DownloadFileTask : AsyncTask<Any, Any, Any>()
{
override fun onPreExecute()
{
Log.i(TAG, "onPreExecute")
}
override fun doInBackground(vararg args: Any)
{
try
{
val url = URL("https://網址/檔名") // 沒記錯的話,因安全性要求,一定得是 https
val urlConn: HttpURLConnection = url.openConnection() as HttpURLConnection
val bis = BufferedInputStream(urlConn.inputStream, 8192)
val outfile = File(myDir, "要儲存的檔名")
val bos = BufferedOutputStream(FileOutputStream(outfile))
var inByte: Int
inByte = 8192
while (bis.read().also({ inByte = it }) != -1)
bos.write(inByte)
bis.close()
bos.close()
}
catch (err:Exception)
{
Log.i(TAG, "error: "+err.message)
}
}
}
private fun requestStoragePermission()
{
if (Build.VERSION.SDK_INT > 22)
{
if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE))
{
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE
)
}
else
{
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE
)
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray)
{
if (requestCode == PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) {
if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
{ // 請求權限獲准
object : Thread()
{
override fun run()
{
// ... 執行獲准權限後要做的事
}
}.start()
}
else
{ // 請求權限被拒, 關閉程式
finish()
}
}
}
}
沒有留言:
張貼留言