LoginSignup
1
1

Retrofit 2.11.0 に依存しているアプリに最適化&難読化を適用する

Posted at

はじめに

本ドキュメントでは、 Retrofit 2.11.0 に依存している Android プロジェクトにおいて、最適化&難読化を有効にして生成したバイナリで発生する実行エラーを回避する方法について記載する。尚、最適化&難読化は R8 コンパイラによって実施されることを想定している。

最適化&難読化の詳細については以下の公式ドキュメントを参照されたい。

ビルド環境

モジュール構成

  • app: アプリ本体。 UI 層。
  • domain: ドメイン層
  • data: データ層。 Retrofit を使用。

ビルドツール

AGP: 8.4.1

build.gradle.kt
plugins {
    id("com.android.application") version "8.4.1" apply false
    id("com.android.library") version "8.4.1" apply false
}
data/build.gradle.kt
plugins {
    id("com.android.library")
}

Kotlin: 1.9.0

plugins {
    id("org.jetbrains.kotlin.android") version "1.9.0" apply false
}
data/build.gradle.kt
plugins {
    id("org.jetbrains.kotlin.android")
}

Retrofit 本体と関連パッケージへの依存を定義

data/build.gradle.kt
dependencies {
    (省略)
    // Retrofit with Moshi Converter
    val retrofitVersion = "2.11.0"
    implementation("com.squareup.retrofit2:converter-moshi:$retrofitVersion")

    // Moshi
    val moshiVersion = "1.15.0"
    implementation("com.squareup.moshi:moshi-kotlin:$moshiVersion")
}

Retrofit を使った通信プログラム

data/datasource/GithubApi.kt
object GithubApi {
    private const val baseUrl = "https://api.github.com/"

    private val moshi: Moshi = Moshi.Builder()
        .add(KotlinJsonAdapterFactory())
        .build()

    private val retrofit: Retrofit = Retrofit.Builder()
        .baseUrl(baseUrl)
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .build()

    val githubApiService: GithubApiService by lazy {
        retrofit.create(GithubApiService::class.java)
    }
}
data/datasource/GithubApiService.kt
interface GithubApiService {
    @GET("search/repositories")
    fun getAllRepo(
        @Query("q") q: String,
        @Query("page") page: Int?,
        @Query("per_page") perPage: Int,
    ): Call<GetAllRepoResponse>
}
data/pagesource/RepositoryPagingSource.kt
class RepositoryPagingSource(
    private val query: String,
    private val endpoint: GithubApiService
) : PagingSource<Int, RepositoryEntity>() {
(省略)
    private suspend fun fetchRepos(query: String, startKey: Int, page: Int?): RepositoryListEntity? {
        //NOTE: 同期方式の場合はメインスレッド以外で通信する必要あり
        return withContext(Dispatchers.IO) {
            val response = try {
                // 同期方式で HTTP 通信を行う
                endpoint.getAllRepo(query, page, PAGE_SIZE).execute()
            } catch (e: Exception) { // 通信自体が失敗した場合
                val exception = AppException.convertTo(e as Throwable)
                throw exception
            }

            val repositories = if (response.isSuccessful) {
                val responseBody = response.body()
                responseBody?.items?.let { items ->
                    convertToEntity(startKey, items)
                }
            } else {
                val exception = GithubExceptionConverter.convertTo(
                    response.code(),
                    response.errorBody()?.string()
                )
                throw exception
            }

            repositories
        }
    }
(省略)
data/datasource/model/GetAllRepoResponse.kt
data class GetAllRepoResponse(val items: List<Repository>)
data/datasource/model/Repository.kt
data class Repository(
    val name: String,
    @Json(name = "full_name") val fullName: String,
    @Json(name = "html_url") val htmlUrl: String,
    val description: String?,
    @Json(name = "created_at") val createdAt: String,
    val owner: Owner
)

手順1: release ビルドで最適化と難読化を有効にする。

まずは app/build.gradle.kt で release ビルドの最適化と難読化を有効にする。

app/build.gradle.kt
     buildTypes {
         getByName("release") {
-            isMinifyEnabled = false
+            isMinifyEnabled = true
             proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+            isShrinkResources = true
         }
     }

この時点では proguard-rules.pro 等の ProGuard の設定ファイルには何も設定していない状態であり、この状態でリリースビルドを実行し、生成された .apk でアプリを動かすと以下のステップで ClassCastException が発生する。

val responseBody = response.body()

手順2: Retrofit API に設定する Generics をProGuard ルールに追加する

ClassCastException が発生しているのは retrofit2.Response<GetAllRepoResponse> クラスの body() メソッドである。よってGetAllRepoResponse が難読化されてしまっているのが Exception の原因だと考えられる。
そこで、GetAllRepoResponse および Repository を配置しているdev.seabat.android.pagingarchitectureretrofit.data.datasource.model パッケージ全てを難読化対象から除外する。( もしかすると *; によるフル指定でなくても良いかもしれない。)

data/consumer-rules.pro
-keep class dev.seabat.android.pagingarchitectureretrofit.data.datasource.model.* {
    *;
}

再度リリースビルドを実行し、生成された .apk でアプリを起動すると、ClassCastException が解消され、最適化&難読化による実行エラーが発生しなくなる。

MEMO

data モジュールを Android Studio のウィザードで作った場合は data/buidl.gradle.kt に以下が含まれていることがある。 data/buidl.gradle.kt には consumerProguardFiles() だけ存在すれば良いのでこの記述を削除し、data/proguard-rules.pro ファイルも削除する。

data/build.gradle.kt
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1