2020-08-04

【Kotlin】以 HttpURLConnection 下載檔案

注意:本篇筆記只適用 Android9(Pie, API28) 以前版本

Environment.getExternalStorageDirectory() 這種寫法,因安全因素在 Android10(Q, API29) 被棄用了!

甚至 AsyncTask 也在 Android11(R, API30) 被棄用!

有用到上述二種寫法的朋友,要趕緊修改您的 APP,不然,會無法通過編譯,或是 APP 在執行時會 crash!!

這篇筆記完全是應急性質,讓老人家的 APPAndroid10 不會 crash  :~(



話說 Android 已經提供了 DownloadManager 這個好用的工具,但個人覺得很難控制,尤其是低階手機/平板,DownloadManager 的參考指南就有提到,當您將要下載的檔案排入佇列後,就只能痴痴的等 DownloadManager 決定何時輪到下載您的檔案;如果想知道下載進度,還要再另外寫一段監看/監聽程式,時時去詢問 DownloadManager 的現況。

所以還是以舊的 ApacheHttpClient 集,自己處理 "下載" 的工作算了。


app 層級build.gradle
  1.  
  2. ...
  3. ...
  4.  
  5. android {
  6. compileSdkVersion 28 // 注意:一定要小於等於 28
  7.  
  8. defaultConfig {
  9. applicationId "com.example.httpdownload"
  10. minSdkVersion 16
  11. targetSdkVersion 28 // 注意:一定要小於等於 28
  12. versionCode 1
  13. versionName "1.0"
  14.  
  15. testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  16. }
  17.  
  18. buildTypes {
  19. ...
  20. }
  21. }
  22.  
  23. // 一定要加這行, 因 AndroidStudio 已停用 httpclient
  24. useLibrary 'org.apache.http.legacy'
  25. }
  26.  



AndroidManifest.xml
  1.  
  2. <manifest
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. package="com.example.httpdownload">
  5.  
  6. <uses-permission android:name="android.permission.INTERNET" />
  7. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  8. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
  9.  
  10. <application
  11. ...
  12. ...
  13.  



activity_main.xml
  1.  
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <androidx.constraintlayout.widget.ConstraintLayout
  4. xmlns:android="http://schemas.android.com/apk/res/android"
  5. xmlns:app="http://schemas.android.com/apk/res-auto"
  6. xmlns:tools="http://schemas.android.com/tools"
  7. android:layout_width="match_parent"
  8. android:layout_height="match_parent"
  9. tools:context=".MainActivity">
  10.  
  11. <!-- 這個檔沒特別的,就是 AndroidStudio 自動產生的版面,加上 onClick 事件 -->
  12. <TextView
  13. android:layout_width="wrap_content"
  14. android:layout_height="wrap_content"
  15. android:text="Hello World!"
  16. android:textSize="30dp"
  17. app:layout_constraintBottom_toBottomOf="parent"
  18. app:layout_constraintLeft_toLeftOf="parent"
  19. app:layout_constraintRight_toRightOf="parent"
  20. app:layout_constraintTop_toTopOf="parent"
  21. android:onClick="DownloadFile"/>
  22.  
  23. </androidx.constraintlayout.widget.ConstraintLayout>
  24.  



MainActivity.kt
  1.  
  2. class MainActivity : AppCompatActivity()
  3. {
  4. private val TAG = "MainActivity"
  5. lateinit var filePath: String
  6. lateinit var myDir: String
  7.  
  8. // 儲存檔案權限
  9. private val PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 0
  10.  
  11. override fun onCreate(savedInstanceState: Bundle?)
  12. {
  13. super.onCreate(savedInstanceState)
  14. setContentView(R.layout.activity_main)
  15.  
  16. // 先建立目錄
  17. // 第一次執行, 因目錄尚不存在, 所以是建立目錄
  18. // 第二次以後執行, 就變成是開啟目錄了
  19. filePath = Environment.getExternalStorageDirectory().path + "/myDownload"
  20. File(filePath).mkdir()
  21. myDir = filePath
  22. }
  23.  
  24. fun DownloadFile(vv: View)
  25. {
  26. if (Build.VERSION.SDK_INT>22)
  27. { // 若是 API23(含) 以上版本, 需請求儲存檔案的權限
  28. if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED)
  29. {
  30. requestStoragePermission();
  31. }
  32. else
  33. {
  34. Toast.makeText(this, "開始下載", Toast.LENGTH_LONG).show()
  35. DownloadFileTask().execute()
  36. }
  37. }
  38. else
  39. {
  40. Toast.makeText(this, "開始下載", Toast.LENGTH_LONG).show()
  41. DownloadFileTask().execute()
  42. }
  43. }
  44.  
  45. private inner class DownloadFileTask : AsyncTask<Any, Any, Any>()
  46. {
  47. override fun onPreExecute()
  48. {
  49. Log.i(TAG, "onPreExecute")
  50. }
  51.  
  52. override fun doInBackground(vararg args: Any)
  53. {
  54. try
  55. {
  56. val url = URL("https://網址/檔名") // 沒記錯的話,因安全性要求,一定得是 https
  57. val urlConn: HttpURLConnection = url.openConnection() as HttpURLConnection
  58.  
  59. val bis = BufferedInputStream(urlConn.inputStream, 8192)
  60. val outfile = File(myDir, "要儲存的檔名")
  61.  
  62. val bos = BufferedOutputStream(FileOutputStream(outfile))
  63. var inByte: Int
  64. inByte = 8192
  65. while (bis.read().also({ inByte = it }) != -1)
  66. bos.write(inByte)
  67. bis.close()
  68. bos.close()
  69. }
  70. catch (err:Exception)
  71. {
  72. Log.i(TAG, "error: "+err.message)
  73. }
  74. }
  75. }
  76.  
  77.  
  78. private fun requestStoragePermission()
  79. {
  80. if (Build.VERSION.SDK_INT > 22)
  81. {
  82. if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE))
  83. {
  84. requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
  85. PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE
  86. )
  87. }
  88. else
  89. {
  90. requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
  91. PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE
  92. )
  93. }
  94. }
  95. }
  96.  
  97. override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray)
  98. {
  99. if (requestCode == PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) {
  100. if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
  101. { // 請求權限獲准
  102. object : Thread()
  103. {
  104. override fun run()
  105. {
  106. // ... 執行獲准權限後要做的事
  107. }
  108. }.start()
  109. }
  110. else
  111. { // 請求權限被拒, 關閉程式
  112. finish()
  113. }
  114. }
  115. }
  116. }
  117.  



沒有留言:

張貼留言