Seize the day

POST : Android Dev Study

개발중에 빌드된 app-debug.apk를 다운받아 설치하기

USB에 연결해서 개발을 잘 하지 않는 편이다. 개발장비에 http 서버를 띄워서 폰에서 apk를 내려받아서 설치하는 것을 반복하다보니 이걸 좀 쉽게 해보고자 찾아봤다. 어떤 앱은 Google Play가 아닌 외부 서버에서 apk를 내려받아 업데이트를 하는 원리를 그대로 이용하는 것이다. 다만 여기서는 개발 중인 버전에 대해서만 이 기능을 이용하기 때문에 리얼 버전에서는 적절히 권한 정리가 필요하다. (TODO: srcDir를 이용해서 분리 필요)

미리 권한 설정을 좀 하고..  
AndroidManifest.xml

    <!-- for install app-debug.apk -->
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

    <application
        android:name="com.mdiwebma.leetzsche.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:networkSecurityConfig="@xml/network_security_config"
        android:theme="@style/AppTheme"
        tools:replace="android:icon">
        <activity
@@ -94,6 +98,16 @@
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
            </intent-filter>
        </receiver>

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/fileprovider" />
        </provider>
    </application>


res/xml/fileprovider.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <!-- internal -->
    <cache-path
        name="cache"
        path="." /> <!--Context.getCacheDir() 내부 저장소-->
    <files-path
        name="files"
        path="." /> <!--Context.getFilesDir() 내부 저장소-->

    <!-- external -->
    <external-path
        name="external"
        path="." /> <!-- Environment.getExternalStorageDirectory() 외부 저장소-->
    <external-cache-path
        name="external-cache"
        path="." /> <!--  Context.getExternalCacheDir() 외부 저장소-->
    <external-files-path
        name="external-files"
        path="." /> <!--  Context.getExternalFilesDir() 외부 저장소-->

    <!--Second sd-card-->
    <root-path
        name="root_path"
        path="/storage/" />
</paths>

 

res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>


외부 앱 설치 권한이 없다면 권한을 주도록 한다.

if (BuildConfig.DEBUG) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        if (!getPackageManager().canRequestPackageInstalls()) {
            startActivity(new Intent(android.provider.Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
                    .setData(Uri.parse(String.format("package:%s", getPackageName()))));
        }
    }
}

 

메뉴를 누르면 다운로드 받아서 설치한다.

f (id == R.id.install_test_apk) {
            new InstallTestApk(this).install();
            return true;
        }

 

InstallTestkApk.kt
리얼 버전에 적용되는 기능이 아니므로, 안드로이드 오레오 이상에서만 동작하는 코드로도 충분하다. 

import android.content.Intent
import androidx.annotation.WorkerThread
import androidx.core.content.FileProvider
import com.mdiwebma.base.BaseActivity
import com.mdiwebma.base.debug.DUtils
import com.mdiwebma.base.task.Tasks
import com.mdiwebma.leetzsche.BuildConfig
import com.squareup.okhttp.OkHttpClient
import com.squareup.okhttp.Request
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.TimeUnit

class InstallTestApk(private val activity: BaseActivity) {

    companion object {
        private const val TEST_APK_URL = "http://172.30.1.4/LT_debug/app-debug.apk"
        private val httpClient = OkHttpClient().apply { setReadTimeout(30, TimeUnit.SECONDS) }
    }

    fun install() {
        Tasks.onBackground {
            File(activity.cacheDir, "app-debug.apk").apply {
                delete()
                download(this)
            }
        }
            .onAvailable(activity)
            .onSuccess(::installApkFile)
            .onError(DUtils::notReached)
            .onPreExecute { activity.showBlockProgressDialog() }
            .onComplete { activity.dismissProgressDialog() }
            .execute()
    }

    @WorkerThread
    @Throws(Exception::class)
    private fun download(file: File) {
        val request = Request.Builder()
            .url(TEST_APK_URL)
            .build()

        try {
            val inputStream = httpClient.newCall(request).execute().body()?.byteStream()
                ?: throw RuntimeException("Download failed: $TEST_APK_URL")
            if (file.exists()) file.delete()
            val outputStream = FileOutputStream(file)
            val data = ByteArray(2048)
            while (true) {
                val count = inputStream.read(data)
                if (count == -1) break
                outputStream.write(data, 0, count)
            }
            outputStream.flush()
            outputStream.close()
            inputStream.close()
        } catch (ex: Exception) {
            ex.printStackTrace()
            throw ex
        }
    }

    private fun installApkFile(file: File) {
        val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
        val apkUri = FileProvider.getUriForFile(
            activity.applicationContext,
            BuildConfig.APPLICATION_ID + ".fileprovider",
            file
        )
        intent.data = apkUri
        intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
        intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
        intent.putExtra(
            Intent.EXTRA_INSTALLER_PACKAGE_NAME,
            activity.applicationInfo.packageName
        )
        try {
            activity.startActivity(intent)
        } catch (ex: Exception) {
            DUtils.notReached(ex)
        }
    }
}

 

참고 사이트 :  https://androidwave.com/download-and-install-apk-programmatically/

top

posted at

2020. 4. 27. 21:28


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크