Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
Jan 29, 2016
TOYAMA, Yosaku
System Gr.

DeNA Life Science, Inc.
爆速でAndroidアプリを
ビルドするための仕組み
⁃ 氏名: 外山 要作
⁃ 所属: DeNAライフサイエンス システムグループ
⁃ 入社: 2012年5月
• 新規サービスの開発、運用
• Android、iOS
⁃ 好きな言語: Ruby と C#
⁃ 趣味: 競プロ、ショートコーディング
⁃ お酒
自己紹介
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
2
Instant Run など
❌ 爆速でビルドして Android 開発の効率UP
⃝ 爆速でビルドするための仕組みがどうやって成り立っているのか
本日の内容
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
3
⁃ gradle はビルドに時間がかかる
• 上手くやればなんとかできるのでは
• 細々と改良していた
⁃ ところが
• テーマを決めたら → Instant Run !!!
• スライド書いたら → cold swap !!!
経緯
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
4
⁃ 背景
⁃ トライしていた手法
• hot deploy の困難性
• 実現方法
⁃ Instant Run について
• 推測
⁃ まとめ
アジェンダ
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
5
ビルド時間を減らすのが大事な理由
⁃ 無駄な時間が減る
⁃ 開発効率の向上
⁃ 小さな単位で結果を確認
背景
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
6
トライしていた手法
7
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
⁃ Java のソースコードを書き換えた結果を手早く確認したい
• ClassLoader をハックできないか
⁃ 上手く行かなかった
• なぜ?
hot deploy の難しさ
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
8
DexClassLoader
BaseDexClassLoader#findClass
DexPathList#findClass
DexFile#loadClassBinaryName
DexFile.defineClass
(ここから native)
Dalvik_dalvik_system_DexFile_defineClassNative
dvmDefineClass
findClassNoInit
Android の ClassLoader
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
9
※	
  dalvik	
  VM	
  での話
// ...
clazz = dvmLookupClass(descriptor, loader, true);
if (clazz == NULL) {
// ...
if (!dvmAddClassToHash(clazz)) {
// ...
bool dvmAddClassToHash(ClassObject* clazz)
{
// ...
found = dvmHashTableLookup(
gDvm.loadedClasses,
hash,
clazz,
hashcmpClassByClass,
true
);
// ...
}
どこにキャッシュされるか
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
10
⁃ gDvm はグローバルな変数を保持している
• つまり gDvm.loadedClasses はプロセスとライフサイクルが同じ
アプリのプロセスを再起動すればなんとかなるのでは!
じゃあどうする?
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
11
⁃ コンパイルした class ごとに dex 化
⁃ 起動時に dex からクラスをロード
• cf. Multidex
⁃ 変更された class のみ転送
具体的な方法
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
12
⁃ 転送するファイルの特定方法
• Java のコンパイラの性質
⁃ コンパイラに任せてしまえる
優れている点
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
13
⁃ 通常の gradle そのまま
• クリーン後にビルド+インストール → 26 秒
⁃ cf. daemon=false だと 40 秒
• 変更してビルド + インストール → 4.5 秒
⁃ cf. daemon=false だと 18 秒
⁃ トライしていた手法
• クリーン後にビルド + インストール → 27.5 秒
• 変更してビルド + インストール + 再起動 → 2.5 秒
⁃ Instant Run
• 変更してビルド+インストール → 3 秒 (Activity の再起動なし)
実演
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
14
Instant Run
15
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
⁃ メソッドの実装の変更、クラスの追加削除は hot swap 可能
⁃ それ以外は大体 cold swap
ふむ。
Instant Run の仕様
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
16
⁃ メソッドの実装の変更には対応している
⁃ シグニチャの変更やフィールドの変更に対応していない
⁃ java.lang.reflect.Proxy
• 「動的プロキシのクラスおよびインスタンスを作成するstaticメソッドを提供」
⁃ ふむ。
Proxy っぽい?
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
17
interface Some {
String method_1();
String method_2();
String method_3();
String method_4();
String method_5();
}
このインターフェイスに対して
説明
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
18
class QuadSome implements Some {
public String method_1() {
return String.valueOf(1 * 1);
}
public String method_2() {
return String.valueOf(2 * 2);
}
public String method_3() {
return String.valueOf(3 * 3);
}
public String method_4() {
return String.valueOf(4 * 4);
}
public String method_5() {
return String.valueOf(5 * 5);
}
}
Some instance = new QuadSome();
こういう実装をしたい
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
19
Some instance = (Some) Proxy.newProxyInstance(
Some.class.getClassLoader(),
new Class<?>[]{ Some.class },
(proxy, method, param) -> {
int num = Integer.parseInt(method.getName().substring(7));
return String.valueOf(num * num);
}
);
動的にメソッドの実装ができる。
Proxy を使うことで
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
20
⁃ Proxy は interface に対してでしか使えない
⁃ あるクラスXに対して
• メソッドの実装部分を X_0 として切り出す
• X に対するメソッドの呼び出しは X_0 を参照するようにする
• 変更したメソッドの実装部分を X_1 として切り出す
• X は最新の X_n を参照するようにしておく
もう一工夫
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
21
class Foo {
public int someField = 123;
public int someMethod() {
return someField + 456;
}
}
模擬コード例
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
22
class Foo_0 {
private Foo proxy;
Foo_0(Foo proxy) {
this.proxy = proxy;
}
public int someMethod() {
return ivar.someField + 456;
}
}
class Foo {
public int someField = 123;
private Class getDelegate() {
// return Foo_0 instance
}
public int someMethod() {
return getDelegate().someMethod();
}
}
こう変換してみる
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
23
public int someMethod() {
return someField + 456;
}
↓↓↓

public int someMethod() {
return someField + 789;
}
メソッドの内容を変更
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
24
class Foo_1 extends Foo {
private Foo proxy;
Foo_1(Foo proxy) {
this.proxy = proxy;
}
public int someMethod() {
return proxy.someField + 789;
}
}
もう一度変換すると
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
25
private Class getDelegate() {
File dex = findLatestDex();
if (dex != loadedDex) {
delegate = loadDex(dex);
loadedDex = dex;
}
return delegate;
}
⁃ Foo_0 と Foo_1 は別クラス扱い
• クラスがキャッシュされる問題を回避できる
委譲先を決める箇所の模擬コード
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
26
※	
  ここまで推測
考え得る hot deploy の実現方法
⁃ Jack and Jill
⁃ デバッグ時は Class Loading の制限を解除
余談
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
27
⁃ トライしていた手法
• 転送するファイルの特定方法がシンプル
• プロセスの再起動が必要
• Instant Runェ…
⁃ Instant Run
• hot swap は Activity の再起動すら不要
• (開発時とリリース時の同一性)
• (オーバーヘッド)
⁃ クラスがキャッシュされる問題さえなければ……
• さらなる改良に期待
まとめ
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
28
⁃ Bazel
• http://bazel.io/docs/mobile-install.html
• mobile-install —incremental
⁃ Buck
• https://buckbuild.com/article/exopackage.html
• exopackage
⁃ LayoutCast
• https://github.com/mmin18/LayoutCast
• IntelliJ と eclipse に対応
類似ツール
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
29
Thanks!
30
Copyright (C) DeNA Co.,Ltd. All Rights Reserved.

爆速でAndroidアプリを ビルドするための仕組み DeNA TechCon #denatechcon

  • 1.
    Copyright (C) DeNACo.,Ltd. All Rights Reserved. Jan 29, 2016 TOYAMA, Yosaku System Gr.
 DeNA Life Science, Inc. 爆速でAndroidアプリを ビルドするための仕組み
  • 2.
    ⁃ 氏名: 外山要作 ⁃ 所属: DeNAライフサイエンス システムグループ ⁃ 入社: 2012年5月 • 新規サービスの開発、運用 • Android、iOS ⁃ 好きな言語: Ruby と C# ⁃ 趣味: 競プロ、ショートコーディング ⁃ お酒 自己紹介 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 2
  • 3.
    Instant Run など ❌爆速でビルドして Android 開発の効率UP ⃝ 爆速でビルドするための仕組みがどうやって成り立っているのか 本日の内容 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 3
  • 4.
    ⁃ gradle はビルドに時間がかかる •上手くやればなんとかできるのでは • 細々と改良していた ⁃ ところが • テーマを決めたら → Instant Run !!! • スライド書いたら → cold swap !!! 経緯 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 4
  • 5.
    ⁃ 背景 ⁃ トライしていた手法 •hot deploy の困難性 • 実現方法 ⁃ Instant Run について • 推測 ⁃ まとめ アジェンダ Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 5
  • 6.
    ビルド時間を減らすのが大事な理由 ⁃ 無駄な時間が減る ⁃ 開発効率の向上 ⁃小さな単位で結果を確認 背景 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 6
  • 7.
  • 8.
    ⁃ Java のソースコードを書き換えた結果を手早く確認したい •ClassLoader をハックできないか ⁃ 上手く行かなかった • なぜ? hot deploy の難しさ Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 8
  • 9.
    DexClassLoader BaseDexClassLoader#findClass DexPathList#findClass DexFile#loadClassBinaryName DexFile.defineClass (ここから native) Dalvik_dalvik_system_DexFile_defineClassNative dvmDefineClass findClassNoInit Android のClassLoader Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 9 ※  dalvik  VM  での話 // ... clazz = dvmLookupClass(descriptor, loader, true); if (clazz == NULL) { // ... if (!dvmAddClassToHash(clazz)) { // ...
  • 10.
    bool dvmAddClassToHash(ClassObject* clazz) { //... found = dvmHashTableLookup( gDvm.loadedClasses, hash, clazz, hashcmpClassByClass, true ); // ... } どこにキャッシュされるか Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 10 ⁃ gDvm はグローバルな変数を保持している • つまり gDvm.loadedClasses はプロセスとライフサイクルが同じ
  • 11.
  • 12.
    ⁃ コンパイルした classごとに dex 化 ⁃ 起動時に dex からクラスをロード • cf. Multidex ⁃ 変更された class のみ転送 具体的な方法 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 12
  • 13.
    ⁃ 転送するファイルの特定方法 • Javaのコンパイラの性質 ⁃ コンパイラに任せてしまえる 優れている点 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 13
  • 14.
    ⁃ 通常の gradleそのまま • クリーン後にビルド+インストール → 26 秒 ⁃ cf. daemon=false だと 40 秒 • 変更してビルド + インストール → 4.5 秒 ⁃ cf. daemon=false だと 18 秒 ⁃ トライしていた手法 • クリーン後にビルド + インストール → 27.5 秒 • 変更してビルド + インストール + 再起動 → 2.5 秒 ⁃ Instant Run • 変更してビルド+インストール → 3 秒 (Activity の再起動なし) 実演 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 14
  • 15.
    Instant Run 15 Copyright (C)DeNA Co.,Ltd. All Rights Reserved.
  • 16.
    ⁃ メソッドの実装の変更、クラスの追加削除は hotswap 可能 ⁃ それ以外は大体 cold swap ふむ。 Instant Run の仕様 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 16
  • 17.
    ⁃ メソッドの実装の変更には対応している ⁃ シグニチャの変更やフィールドの変更に対応していない ⁃java.lang.reflect.Proxy • 「動的プロキシのクラスおよびインスタンスを作成するstaticメソッドを提供」 ⁃ ふむ。 Proxy っぽい? Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 17
  • 18.
    interface Some { Stringmethod_1(); String method_2(); String method_3(); String method_4(); String method_5(); } このインターフェイスに対して 説明 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 18
  • 19.
    class QuadSome implementsSome { public String method_1() { return String.valueOf(1 * 1); } public String method_2() { return String.valueOf(2 * 2); } public String method_3() { return String.valueOf(3 * 3); } public String method_4() { return String.valueOf(4 * 4); } public String method_5() { return String.valueOf(5 * 5); } } Some instance = new QuadSome(); こういう実装をしたい Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 19
  • 20.
    Some instance =(Some) Proxy.newProxyInstance( Some.class.getClassLoader(), new Class<?>[]{ Some.class }, (proxy, method, param) -> { int num = Integer.parseInt(method.getName().substring(7)); return String.valueOf(num * num); } ); 動的にメソッドの実装ができる。 Proxy を使うことで Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 20
  • 21.
    ⁃ Proxy はinterface に対してでしか使えない ⁃ あるクラスXに対して • メソッドの実装部分を X_0 として切り出す • X に対するメソッドの呼び出しは X_0 を参照するようにする • 変更したメソッドの実装部分を X_1 として切り出す • X は最新の X_n を参照するようにしておく もう一工夫 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 21
  • 22.
    class Foo { publicint someField = 123; public int someMethod() { return someField + 456; } } 模擬コード例 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 22
  • 23.
    class Foo_0 { privateFoo proxy; Foo_0(Foo proxy) { this.proxy = proxy; } public int someMethod() { return ivar.someField + 456; } } class Foo { public int someField = 123; private Class getDelegate() { // return Foo_0 instance } public int someMethod() { return getDelegate().someMethod(); } } こう変換してみる Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 23
  • 24.
    public int someMethod(){ return someField + 456; } ↓↓↓ public int someMethod() { return someField + 789; } メソッドの内容を変更 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 24
  • 25.
    class Foo_1 extendsFoo { private Foo proxy; Foo_1(Foo proxy) { this.proxy = proxy; } public int someMethod() { return proxy.someField + 789; } } もう一度変換すると Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 25
  • 26.
    private Class getDelegate(){ File dex = findLatestDex(); if (dex != loadedDex) { delegate = loadDex(dex); loadedDex = dex; } return delegate; } ⁃ Foo_0 と Foo_1 は別クラス扱い • クラスがキャッシュされる問題を回避できる 委譲先を決める箇所の模擬コード Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 26 ※  ここまで推測
  • 27.
    考え得る hot deployの実現方法 ⁃ Jack and Jill ⁃ デバッグ時は Class Loading の制限を解除 余談 Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 27
  • 28.
    ⁃ トライしていた手法 • 転送するファイルの特定方法がシンプル •プロセスの再起動が必要 • Instant Runェ… ⁃ Instant Run • hot swap は Activity の再起動すら不要 • (開発時とリリース時の同一性) • (オーバーヘッド) ⁃ クラスがキャッシュされる問題さえなければ…… • さらなる改良に期待 まとめ Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 28
  • 29.
    ⁃ Bazel • http://bazel.io/docs/mobile-install.html •mobile-install —incremental ⁃ Buck • https://buckbuild.com/article/exopackage.html • exopackage ⁃ LayoutCast • https://github.com/mmin18/LayoutCast • IntelliJ と eclipse に対応 類似ツール Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 29
  • 30.
    Thanks! 30 Copyright (C) DeNACo.,Ltd. All Rights Reserved.