Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Retrofit2の仕組み 〜CallAdapterを理解する〜 / How Retrofit...

Retrofit2の仕組み 〜CallAdapterを理解する〜 / How Retrofit2 works ~ Understand CallAdapter ~

Android研究&発表会#3で発表した資料です!
https://arap.connpass.com/event/153097/

サンプル
https://github.com/kwmt/RetrofitSample

Yasutaka Kawamoto

November 30, 2019
Tweet

More Decks by Yasutaka Kawamoto

Other Decks in Programming

Transcript

  1. • Տຊ ହ޹(͔Θ΋ͱ ΍͔ͨ͢) • ॴଐɿגࣜձࣾ tech vein 
 (େࡕࢢதԝ۠ຊொ)

    • ϞόΠϧΞϓϦΤϯδχΞ
 (AndroidଟΊɺiOS΋ŧŔŕŪũƄŝſ) • ࢖ͬͨ͜ͱ͋Δ
 HTTPΫϥΠΞϯτϥΠϒϥϦ
 - AndroidQuery,Volley,OkHttp,Retrofit 2 ࣗݾ঺հ
  2. • Squareࣾ੡ • HTTPΫϥΠΞϯτ • 2016೥ʹv2ϦϦʔε • 2019/11/30 ݱࡏ2.6.1 •

    JavaϥΠϒϥϦ • Android or JavaͰॻ͔ΕͨαʔόʔͰಈ͘ • GitHubελʔ34.3k Retrofitͱ͸
  3. class GitHubServiceImpl : GitHubServiceWithoutRetrofit { override suspend fun listRepos(user: String):

    List<Repo> { return withContext(Dispatchers.IO) { val url = URL("https://api.github.com/users/$user/repos") val urlConnection = url.openConnection() as HttpURLConnection val result: List<Repo> try { val inputStream = BufferedInputStream(urlConnection.inputStream) result = readStream(inputStream) } finally { urlConnection.disconnect() } return@withContext result } } private fun readStream(inputStream: BufferedInputStream) = inputStream.bufferedReader().use { convert(it) } private fun convert(reader: BufferedReader): List<Repo> { val content = StringBuilder() reader.use { var line = it.readLine() while (line != null) { content.append(line) line = it.readLine() } } return Json { strictMode = false } .parse(Repo.serializer().list, content.toString()) } }
  4. class GitHubServiceImpl : GitHubServiceWithoutRetrofit { override suspend fun listRepos(user: String):

    List<Repo> { return withContext(Dispatchers.IO) { val url = URL("https://api.github.com/users/$user/repos") val urlConnection = url.openConnection() as HttpURLConnection val result: List<Repo> try { val inputStream = BufferedInputStream(urlConnection.inputStream) result = readStream(inputStream) } finally { urlConnection.disconnect() } return@withContext result } } private fun readStream(inputStream: BufferedInputStream) = inputStream.bufferedReader().use { convert(it) } private fun convert(reader: BufferedReader): List<Repo> { val content = StringBuilder() reader.use { var line = it.readLine() while (line != null) { content.append(line) line = it.readLine() } } return Json { strictMode = false } .parse(Repo.serializer().list, content.toString()) } } URL Λࢦఆ
  5. class GitHubServiceImpl : GitHubServiceWithoutRetrofit { override suspend fun listRepos(user: String):

    List<Repo> { return withContext(Dispatchers.IO) { val url = URL("https://api.github.com/users/$user/repos") val urlConnection = url.openConnection() as HttpURLConnection val result: List<Repo> try { val inputStream = BufferedInputStream(urlConnection.inputStream) result = readStream(inputStream) } finally { urlConnection.disconnect() } return@withContext result } } private fun readStream(inputStream: BufferedInputStream) = inputStream.bufferedReader().use { convert(it) } private fun convert(reader: BufferedReader): List<Repo> { val content = StringBuilder() reader.use { var line = it.readLine() while (line != null) { content.append(line) line = it.readLine() } } return Json { strictMode = false } .parse(Repo.serializer().list, content.toString()) } } URL Λࢦఆ openConnneciton ͨ͠Γ
  6. class GitHubServiceImpl : GitHubServiceWithoutRetrofit { override suspend fun listRepos(user: String):

    List<Repo> { return withContext(Dispatchers.IO) { val url = URL("https://api.github.com/users/$user/repos") val urlConnection = url.openConnection() as HttpURLConnection val result: List<Repo> try { val inputStream = BufferedInputStream(urlConnection.inputStream) result = readStream(inputStream) } finally { urlConnection.disconnect() } return@withContext result } } private fun readStream(inputStream: BufferedInputStream) = inputStream.bufferedReader().use { convert(it) } private fun convert(reader: BufferedReader): List<Repo> { val content = StringBuilder() reader.use { var line = it.readLine() while (line != null) { content.append(line) line = it.readLine() } } return Json { strictMode = false } .parse(Repo.serializer().list, content.toString()) } } URL Λࢦఆ openConnneciton ͨ͠Γ InputStream ΛѻͬͨΓ
  7. class GitHubServiceImpl : GitHubServiceWithoutRetrofit { override suspend fun listRepos(user: String):

    List<Repo> { return withContext(Dispatchers.IO) { val url = URL("https://api.github.com/users/$user/repos") val urlConnection = url.openConnection() as HttpURLConnection val result: List<Repo> try { val inputStream = BufferedInputStream(urlConnection.inputStream) result = readStream(inputStream) } finally { urlConnection.disconnect() } return@withContext result } } private fun readStream(inputStream: BufferedInputStream) = inputStream.bufferedReader().use { convert(it) } private fun convert(reader: BufferedReader): List<Repo> { val content = StringBuilder() reader.use { var line = it.readLine() while (line != null) { content.append(line) line = it.readLine() } } return Json { strictMode = false } .parse(Repo.serializer().list, content.toString()) } } URL Λࢦఆ openConnneciton ͨ͠Γ Ϩεϙϯε݁ՌΛࢦ ఆͨ͠ܕʹม׵͢ΔͨΊ ͷ࣮૷ͨ͠Γ InputStream ΛѻͬͨΓ
  8. class GitHubServiceImpl : GitHubServiceWithoutRetrofit { override suspend fun listRepos(user: String):

    List<Repo> { return withContext(Dispatchers.IO) { val url = URL("https://api.github.com/users/$user/repos") val urlConnection = url.openConnection() as HttpURLConnection val result: List<Repo> try { val inputStream = BufferedInputStream(urlConnection.inputStream) result = readStream(inputStream) } finally { urlConnection.disconnect() } return@withContext result } } private fun readStream(inputStream: BufferedInputStream) = inputStream.bufferedReader().use { convert(it) } private fun convert(reader: BufferedReader): List<Repo> { val content = StringBuilder() reader.use { var line = it.readLine() while (line != null) { content.append(line) line = it.readLine() } } return Json { strictMode = false } .parse(Repo.serializer().list, content.toString()) } } URL Λࢦఆ openConnneciton ͨ͠Γ Ϩεϙϯε݁ՌΛࢦ ఆͨ͠ܕʹม׵͢ΔͨΊ ͷ࣮૷ͨ͠Γ connectionΛ ੾அͨ͠Γ InputStream ΛѻͬͨΓ
  9. গ͠ͷઃఆͱ͍͏ͷ͸ interface GitHubServiceWithoutRetrofit { suspend fun listRepos(user: String): List<Repo> }

    interface GitHubServiceWithRetrofit { @GET("users/{user}/repos") fun listRepos(@Path("user") user: String): Call<List<Repo>> }
  10. গ͠ͷઃఆͱ͍͏ͷ͸ interface GitHubServiceWithoutRetrofit { suspend fun listRepos(user: String): List<Repo> }

    interface GitHubServiceWithRetrofit { @GET("users/{user}/repos") fun listRepos(@Path("user") user: String): Call<List<Repo>> }
  11. গ͠ͷઃఆͱ͍͏ͷ͸ interface GitHubServiceWithoutRetrofit { suspend fun listRepos(user: String): List<Repo> }

    interface GitHubServiceWithRetrofit { @GET("users/{user}/repos") fun listRepos(@Path("user") user: String): Call<List<Repo>> }
  12. গ͠ͷઃఆͱ͍͏ͷ͸ class GitHubServiceImpl : GitHubServiceWithoutRetrofit { override suspend fun listRepos(user:

    String): List<Repo> { … } } val gitHubServiceWithRetrofit: GitHubServiceWithRetrofit = Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(MyConverterFactory()) .build() .create(GitHubServiceWithRetrofit::class.java)
  13. ඞཁͳͷ͸͜Ε͚ͩ interface GitHubServiceWithRetrofit { @GET("users/{user}/repos") fun listRepos(@Path("user") user: String): Call<List<Repo>>

    } val gitHubServiceWithRetrofit: GitHubServiceWithRetrofit = Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(MyConverterFactory()) .addCallAdapterFactory(MyCallAdapterFactory()) .build() .create(GitHubServiceWithRetrofit::class.java)
  14. ඞཁͳͷ͸͜Ε͚ͩ interface GitHubServiceWithRetrofit { @GET("users/{user}/repos") fun listRepos(@Path("user") user: String): Call<List<Repo>>

    } val gitHubServiceWithRetrofit: GitHubServiceWithRetrofit = Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(MyConverterFactory()) .addCallAdapterFactory(MyCallAdapterFactory()) .build() .create(GitHubServiceWithRetrofit::class.java)
  15. ඞཁͳͷ͸͜Ε͚ͩ interface GitHubServiceWithRetrofit { @GET("users/{user}/repos") fun listRepos(@Path("user") user: String): Call<List<Repo>>

    } val gitHubServiceWithRetrofit: GitHubServiceWithRetrofit = Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(MyConverterFactory()) .addCallAdapterFactory(MyCallAdapterFactory()) .build() .create(GitHubServiceWithRetrofit::class.java)
  16. interface GitHubService { @GET("users/{user}/repos") fun listRepos(@Path("user") user: String): Call<List<Repo>> }

    val gitHubService: GitHubService = Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(MyConverterFactory()) .addCallAdapterFactory(MyCallAdapterFactory()) .build() .create(GitHubService::class.java)
  17. interface GitHubService { @GET("users/{user}/repos") fun listRepos(@Path("user") user: String): Call<List<Repo>> }

    val gitHubService: GitHubService = Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(MyConverterFactory()) .addCallAdapterFactory(MyCallAdapterFactory()) .build() .create(GitHubService::class.java)
  18. retrofit.create(GitHubService::class.java) public <T> T create(final Class<T> service) { ɹ// লུ

    return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { ɹɹɹɹɹ // লུ return loadServiceMethod(method) .invoke(args != null ? args : emptyArgs); } }); }
  19. retrofit.create(GitHubService::class.java) public <T> T create(final Class<T> service) { ɹ// লུ

    return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { ɹɹɹɹɹ // লུ return loadServiceMethod(method) .invoke(args != null ? args : emptyArgs); } }); }
  20. retrofit.create(GitHubService::class.java) public <T> T create(final Class<T> service) { ɹ// লུ

    return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { ɹɹɹɹɹ // লུ return loadServiceMethod(method) .invoke(args != null ? args : emptyArgs); } }); }
  21. retrofit.create(GitHubService::class.java) public <T> T create(final Class<T> service) { ɹ// লུ

    return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { ɹɹɹɹɹ // লུ return loadServiceMethod(method) .invoke(args != null ? args : emptyArgs); } }); }
  22. Proxy.newProxyInstanceͷྫ interface Service { fun doSomething(value: Int) } fun <T>

    create(service: Class<T>): T { return Proxy.newProxyInstance( service.classLoader, Array(1) { service }, InvocationHandler { proxy, method, args -> Log.d(TAG, "start") Log.d(TAG, method.name + if (args.isNotEmpty()) args[0] else "") Log.d(TAG, "end") } ) as T }
  23. Proxy.newProxyInstanceͷྫ interface Service { fun doSomething(value: Int) } fun <T>

    create(service: Class<T>): T { return Proxy.newProxyInstance( service.classLoader, Array(1) { service }, InvocationHandler { proxy, method, args -> Log.d(TAG, "start") Log.d(TAG, method.name + if (args.isNotEmpty()) args[0] else "") Log.d(TAG, "end") } ) as T } val service = create(Service::class.java) service.doSomething(1000)
  24. Proxy.newProxyInstanceͷྫ interface Service { fun doSomething(value: Int) } fun <T>

    create(service: Class<T>): T { return Proxy.newProxyInstance( service.classLoader, Array(1) { service }, InvocationHandler { proxy, method, args -> Log.d(TAG, "start") Log.d(TAG, method.name + if (args.isNotEmpty()) args[0] else "") Log.d(TAG, "end") } ) as T } val service = create(Service::class.java) service.doSomething(1000) ϩάग़ྗ start doSomething1000 end
  25. Proxy.newProxyInstanceͷྫ interface Service { fun doSomething(value: Int) } fun <T>

    create(service: Class<T>): T { return Proxy.newProxyInstance( service.classLoader, Array(1) { service }, InvocationHandler { proxy, method, args -> Log.d(TAG, "start") Log.d(TAG, method.name + if (args.isNotEmpty()) args[0] else "") Log.d(TAG, "end") } ) as T } val service = create(Service::class.java) service.doSomething(1000) ϩάग़ྗ start doSomething1000 end
  26. gitHubService.listRepos("kwmt") val gitHubService: GitHubService = Retrofit.Builder() ɹɹɹɹɹ// লུ .create(GitHubService::class.java) public

    <T> T create(final Class<T> service) { ɹ// লུ return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { ɹɹɹɹɹ // লུ return loadServiceMethod(method) .invoke(args != null ? args : emptyArgs); } }); }
  27. gitHubService.listRepos("kwmt") val gitHubService: GitHubService = Retrofit.Builder() ɹɹɹɹɹ// লུ .create(GitHubService::class.java) public

    <T> T create(final Class<T> service) { ɹ// লུ return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { ɹɹɹɹɹ // লུ return loadServiceMethod(method) .invoke(args != null ? args : emptyArgs); } }); }
  28. Retrofit#loadServiceMethod ServiceMethod<?> loadServiceMethod(Method method) { ServiceMethod<?> result = serviceMethodCache.get(method); if

    (result != null) return result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); if (result == null) { result = ServiceMethod.parseAnnotations(this, method); serviceMethodCache.put(method, result); } } return result; }
  29. Factory#getɺCallAdapter#adapt
 ͕ͲͷΑ͏ʹݺ͹Ε͍ͯΔ͔ʁ ServiceMethod<?> loadServiceMethod(Method method) { ServiceMethod<?> result = serviceMethodCache.get(method);

    if (result != null) return result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); if (result == null) { result = ServiceMethod.parseAnnotations(this, method); serviceMethodCache.put(method, result); } } return result; }
  30. public <T> T create(final Class<T> service) { ɹ// লུ return

    (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { ɹɹɹɹɹ // লུ return loadServiceMethod(method) .invoke(args != null ? args : emptyArgs); } }); }
  31. public <T> T create(final Class<T> service) { ɹ// লུ return

    (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { ɹɹɹɹɹ // লུ return loadServiceMethod(method) .invoke(args != null ? args : emptyArgs); } }); }
  32. ServiceMethod#invoke @Override invoke() { // লུ return adapt(call, args); }

    @Override adapt(): T { return callAdapter.adapt(call); }
  33. ServiceMethod#invoke @Override invoke() { // লུ return adapt(call, args); }

    @Override adapt(): T { return callAdapter.adapt(call); }
  34. ServiceMethod#invoke @Override invoke() { // লུ return adapt(call, args); }

    @Override adapt(): T { return callAdapter.adapt(call); }
  35. CallAdapter.Factory class MyCallAdapterFactory : CallAdapter.Factory() { override fun get( returnType:

    Type, annotations: Array<Annotation>, retrofit: Retrofit ): CallAdapter<*, *>? { val responseType = getParameterUpperBound(0, returnType as ParameterizedType) return MyCallAdapter<Any>(responseType) } }
  36. CallAdapter.Factory class MyCallAdapterFactory : CallAdapter.Factory() { override fun get( returnType:

    Type, annotations: Array<Annotation>, retrofit: Retrofit ): CallAdapter<*, *>? { val responseType = getParameterUpperBound(0, returnType as ParameterizedType) return MyCallAdapter<Any>(responseType) } }
  37. CallAdapter.Factory class MyCallAdapterFactory : CallAdapter.Factory() { override fun get( returnType:

    Type, annotations: Array<Annotation>, retrofit: Retrofit ): CallAdapter<*, *>? { val responseType = getParameterUpperBound(0, returnType as ParameterizedType) return MyCallAdapter<Any>(responseType) } }
  38. CallAdapter Call<R> → T = interface MyCall<R> { fun execute():

    Response<R> } class MyCallAdapter<R>( private val responseType: Type ) : CallAdapter<R, MyCall<R>> { override fun responseType(): Type = responseType override fun adapt(call: Call<R>): MyCall<R> { return object : MyCall<R> { override fun execute(): Response<R> { return call.execute() } } } }
  39. CallAdapter Call<R> → T = interface MyCall<R> { fun execute():

    Response<R> } class MyCallAdapter<R>( private val responseType: Type ) : CallAdapter<R, MyCall<R>> { override fun responseType(): Type = responseType override fun adapt(call: Call<R>): MyCall<R> { return object : MyCall<R> { override fun execute(): Response<R> { return call.execute() } } } }
  40. CallAdapter Call<R> → T = interface MyCall<R> { fun execute():

    Response<R> } class MyCallAdapter<R>( private val responseType: Type ) : CallAdapter<R, MyCall<R>> { override fun responseType(): Type = responseType override fun adapt(call: Call<R>): MyCall<R> { return object : MyCall<R> { override fun execute(): Response<R> { return call.execute() } } } }
  41. CallAdapter Call<R> → T = interface MyCall<R> { fun execute():

    Response<R> } class MyCallAdapter<R>( private val responseType: Type ) : CallAdapter<R, MyCall<R>> { override fun responseType(): Type = responseType override fun adapt(call: Call<R>): MyCall<R> { return object : MyCall<R> { override fun execute(): Response<R> { return call.execute() } } } }
  42. CallAdapterͰΤϥʔϋϯυϦϯά class MyCallAdapter<R>( private val responseType: Type ) : CallAdapter<R,

    MyCall<R>> { override fun responseType(): Type = responseType override fun adapt(call: Call<R>): MyCall<R> { return object : MyCall<R> { override fun execute(): Response<R> { val response = call.execute() val code = response.code() // 401ͳΒϩάΠϯࣦഊ if(code == 401) { throw AuthException() } return response } } } }