注意:本篇筆記只適用 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() } } } }
沒有留言:
張貼留言