플레이스토어에 배포되지 않은 앱을 다운로드/업데이트
일단 앱의 apk 파일 다운로드 링크(아래 코드의 url)를 서버에 올려두고 진행했고
버전 정보를 체크하는 방법은 생략했다
일단 코드부터 살펴보자
val intent = Intent(requireActivity(), ApkDownloadService::class.java)
intent.putExtra(ApkDownloadService.PATH, filePath)
intent.putExtra(ApkDownloadService.RECEIVER, ProgressReceiver())
requireActivity().startService(intent)
class ApkDownloadService: IntentService(TAG) {
companion object{
const val TAG = "ApkDownloadService"
const val PATH = "apk_download_service"
const val RECEIVER = "apk_result_receiver"
const val RESULT_PROGRESS = "apk_download_progress"
const val RESULT_CODE = 20240104
const val RESULT_CODE_FAIL = -1
}
private val mIoScope = CoroutineScope(Dispatchers.IO)
override fun onHandleIntent(intent: Intent?) {
val url = intent?.getStringExtra(PATH)
val receiver = intent?.getParcelableExtra(RECEIVER) as ResultReceiver?
mIoScope.launch {
getApkFile(url = url, receiver = receiver)
}
}
fun getApkFile(url: String?, receiver: ResultReceiver?){
val client = OkHttpClient.Builder().build()
val request = Request.Builder().url(url!!).build()
// 서버와 통신에 필요한 옵션(Cookie 나 인증 같은 부분)은 위 두줄의 각 Builder()에서 세팅
val response = client.newCall(request).execute()
if (response.isSuccessful){
try{
val body = response.body
val data = body?.byteStream()
val length = body?.contentLength()?.toInt()!!
val byteArray = ByteArray(length)
val wrapper = ContextWrapper(baseContext)
val dirDownload = wrapper.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
val file = File(dirDownload, "update.apk") // 업데이트용 임시 apk 파일
val fos = FileOutputStream(file)
var isRead = true
var total = 0L
while (isRead){
val read = data?.read(byteArray)!!
if (read<=0){
isRead = false
}
else {
total += read
// 이 쪽에서 ResultReceiver 를 사용하여 프로그레스를 UI 스레드에서 전달받을 수 있다
// 추후 Install/Update 코드에서 사용 된다
val resultData = Bundle()
resultData.putInt(RESULT_PROGRESS, (total*100/length).toInt())
receiver?.send(RESULT_CODE, resultData)
fos.write(byteArray, 0, read)
}
}
data?.close()
fos.flush()
fos.close()
response.close()
}
catch(e:Exception){
e.printStackTrace()
}
}
}
}
REST 쪽은 OkHttp 라이브러리를 사용했다.
파일이 다운로드 되는 원리는 다운로드 Url 의 ResponseBody 를 ByteStream 으로 받아온 뒤
FileOutputStream 으로 지정한 임시 파일에 write 하는 형태이다.
여기까지하면 "Download" 까지 진행 된 것이다.
굳이 다운로드라고 강조한 이유는 추후 Install/Update 까지 해 주어야 비로소 앱이 설치 혹은 업데이트 되었다고
할 수 있기 때문이다.
그럼 이 File 을 Install 해달라고 요청해보...기 전에 우선 Manifest 작업이 필요하다.
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.FileProvider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
그리고 provider_paths.xml 은
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_path"
path="."/>
<external-files-path
name="external_files_path"
path="."/>
</paths>
이런 식으로 작성해주면 된다
이제 Download 메서드 실행직후 ~ 완료까지의 작업을 처리해줄 Receiver 를 아래와 같이 작성하면 된다.
class ProgressReceiver(resultHandler: Handler): ResultReceiver(resultHandler){
override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
super.onReceiveResult(resultCode, resultData)
if (resultCode == ApkDownloadService.RESULT_CODE){
val progress = resultData?.getInt(ApkDownloadService.PROGRESS)!!
if (progress<100){
resultHandler.post {
updateProgress(progress)
// 여기서 Percentage 를 UI 에 표기했다.
}
}
if (progress == 100){
val intent = Intent()
val file = ApkDownloadService.getFile(requireContext())
val uri = FileProvider.getUriForFile(
context!!,
AUTHORITY,
file
)
intent.setDataAndType(uri, "application/vnd.android.package-archive")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
resultHandler.removeCallbacksAndMessages(null)
}
}
}
}
위에 Manifest와 xml 작업을 했던 이유가 마지막 코드블록에 있다.
FileProvider 를 통해 해당 파일을 실행시킬 수 있는 Uri 가 필요하다.
이 Uri 를 Intent 에 넣어서 실행시키면 권한 부여에 동의하라는 여러 체크박스들이 나오고 다 넘기면
앱이 비로소 Install / Update 된다.
'Android > Kotlin' 카테고리의 다른 글
Android Room Database 사용하기 (1) | 2024.01.08 |
---|---|
Android ExoPlayer 로 앱에서 동영상 재생하기 (1) | 2024.01.05 |
Android Jackson Library 를 이용한 JSON Parsing (1) | 2024.01.05 |
Android Custom Dialog 만들기 (2) | 2024.01.04 |
[Kotlin] 문자열(String) 유사도 검사하기 - (1) (3) | 2024.01.03 |