웹뷰 관련 포스팅
- Webview내 window.open 대응 하기 ( 새창 열기 에러 )
- Webview 이미지 업로드 구현하기
- Webview내 카카오톡 공유하기 에러 / 해결방안
- Webview 쿠키 매니저 다루기
- Webview 설정값
웹뷰에서 다운로드 기능을 별도로 구현해야 합니다.
webview.setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
//.. 구현
}
위 코드 처럼 웹뷰에 리스너를 추가해 다운로드 타입으로 설정된 버튼등을 클릭했을때 별도 처리를 해야합니다.
엑티비티에서 사용중인 webview에 설정해도 되지만 웹뷰를 커스텀뷰로 생성해서 공통적으로 사용하면 설정 값이 변경되거나 추가할때 편리합니다. ( 유지보수성 용이 )
커스텀뷰 생성은 제 포스팅을 참고해주세요
본론으로 돌아가서 다운로드를 클릭했을때 호출된 리스너의 파라미터들의 정보들은 각
- url : 다운로드 url
- userAgent : 웹뷰에 설정된 기기 정보 및 브라우저 정보들이 동일하게 담겨 있습니다.
- contentDispostion : 실질적인 파일 이름인데, 한글일 경우 URL 인코딩 되어 있기 때문에 디코딩을 해서 사용해야 합니다.
- 그리고 파일 명들에 부수적으로 붙어 있는 텍스트들이 있는데 그런 부분들을 replace등으로 제거 후 순수 파일 이름만으로 다운로드 해야 합니다.
- mimetype: 다운로드 하는 파일의 타입으로 pdf, jpg등 입니다.
| 권한 설정
웹뷰의 인터넷 권한과 더불어 파일 저장에 필요한 권한을 추가합니다.
Androidmanifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="packageName">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
...
</manifest>
| 다운로드 리스너
파일을 다운로드 하기전에 파일 읽기 쓰기 권한이 체크를 하지 않으면 에러가 발생합니다.
그리고 contentDisposition으로 들어온 파일명을 로그로 찍어보고 앞뒤로 붙은 불필요한 텍스트 제거를 위한 코드들을 추가합니다.
( 아래 코드에서는 ";" 가 포함됐거나, "\"가 포함 된 경우 제거 합니다. )
setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
if (activity.isGrantDownloadPermission()) {
// 파일 읽기 쓰기 권한이 허용된 경우
var downloadManager = context.getSystemService(DOWNLOAD_SERVICE) as DownloadManager
var _contentDisposition = URLDecoder.decode(contentDisposition, "UTF-8")
var _mimetype = mimetype
// 파일명 정제 시작
var fileName = _contentDisposition.replace("attachment; filename=", "")
if (!TextUtils.isEmpty(fileName)) {
_mimetype = MimeTypeMap.getSingleton().getMimeTypeFromExtension(mimetype)
if (fileName.endsWith(";")) {
fileName = fileName.substring(0, fileName.length - 1)
}
if (fileName.startsWith("\"") && fileName.endsWith("\"")) {
fileName = fileName.substring(1, fileName.length - 1)
}
}
// 파일명 정제 끝
var request = DownloadManager.Request(Uri.parse(url)).apply {
setMimeType(_mimetype)
addRequestHeader("User-Agent", userAgent)
setDescription("Downloading File")
setAllowedOverMetered(true)
setAllowedOverRoaming(true)
setTitle(fileName)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setRequiresCharging(false)
}
allowScanningByMediaScanner()
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
}
registerDownloadReceiver(downloadManager, activity)
downloadId = downloadManager.enqueue(request)
activity.showToast("다운로드 시작중..")
}
}
| 다운로드 노티 설정
다운로드 진행상황이 노티에 표시되는 모습은 안드로이드 사용 유저라면 많이 접해보셨을겁니다. DownloadManager.Request를 사용해 간편하게 설정할 수 있습니다.
위 코드에서 'setNotificationVisibillty(int)' 를 참고하시면 되는데, 파라미터 값들로는 아래 4가지 입니다.
- VISIBILITY_VISIBLE_NOTIFY_COMPLETED : 다운로드 진행 과정과 완료 됐을때 모두 보여줍니다.
- VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : 다운로드가 완료 됐을때만 보여줍니다.
- VISIBILITY_VISIBLE : 다운로드가 진행중일 때만 보여줍니다.
- VISIBILITY_HIDDEN : 모두 비노출합니다.
| 다운로드 파일 바로 열기
노티 설정에 따라 다운로드가 완료됐다는 노티를 클릭한 경우 다운로드된 파일을 바로 열어볼 수 있습니다. 근데 파일 타입을 잘못 설정한 경우 '파일을 열수 없습니다.' 라는 토스트가 노출될겁니다. 다운로드 받은 파일과, 노티에 설정된 파일 타입이 일치하지 않을때 발생하는 현상입니다.
리스너로 전달받은 'mineType' 인자를 코딩해보시면 파일과 다를거고 저 같은 경우는 application/zip으로 들어오는데 실제 파일은 엑셀인 경우였습니다.
파일명에 있는 타입을 추출해서 하나하나 대입해줘야하나 했는데, 타입 추출하는 방법을 검색해서 적용했을땐 실제 결과값이 null 이 였고 정상적으로 작동했습니다.
이로서 추측 해볼수 있는 상황으론, 타입이 널일 경우 파일명에서 파일 타입을 스스로 설정하는 느낌 같았습니다.
( 제대로된 정보가 있을 경우 댓글로 남겨 주시면 반영하도록 하겠습니다. )
| 다운로드 완료 콜백
위 코드중 'downloadManager.enqueue'로 받은 리턴 값은 다운로드를 관리하는 아이디로, 완료됐을 경우 아이디를 비교해서 보다 정확하게 사용자에게 어떤 파일이 완료됐는지 알려줄 수 있습니다.
private fun registerDownloadReceiver(downloadManager: DownloadManager, activity: BaseActivity<*>) {
var downloadReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
var id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) ?: -1
when (intent?.action) {
DownloadManager.ACTION_DOWNLOAD_COMPLETE -> {
if(downloadId == id){
val query: DownloadManager.Query = DownloadManager.Query()
query.setFilterById(id)
var cursor = downloadManager.query(query)
if (!cursor.moveToFirst()) {
return
}
var columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
var status = cursor.getInt(columnIndex)
if (status == DownloadManager.STATUS_SUCCESSFUL) {
activity.showToast("다운로드가 완료됐습니다.")
} else if (status == DownloadManager.STATUS_FAILED) {
activity.showToast("다운로드가 실패했습니다.")
}
}
}
}
}
}
IntentFilter().run {
addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
activity.registerReceiver(downloadReceiver, this)
}
}
위 내용에서 잘못된 내용이나 궁금하신 부분은 댓글로 남겨주시면 답변 드리도록 하겠습니다.
감사합니다.
'안드로이드 > 코틀린' 카테고리의 다른 글
[AOS] Mopub Migration MAX ( 광고 SDK ) (0) | 2022.02.23 |
---|---|
[AOS] 하단 네비게이션 배경색 변경 (0) | 2021.11.25 |
Webview 이미지 업로드 구현하기 ( 카메라, 갤러리 ) (7) | 2021.05.27 |
[Android/Kotlin] 커스텀 뷰로 생산성 증가시키기 (0) | 2021.03.02 |
[안드로이드/Kotlin] WorkManager를 활용한 백그라운드 작업 (0) | 2021.02.16 |