ActiveAndroidの初期化時間を4分の1にする

Thu Dec 5, 2013

ActiveAndroidとは

ActiveAndroidとは、アクティブレコードパターンのAndroidのORMです。

こういうクエリーがあったら

INSERT INTO Items(id, name) VALUES(NULL, 'My Item');

こう書けます。

Item item = new Item();
item.name = "My Item";
item.save();

セレクトするときにこうしていたのは、

SELECT * FROM Items;

このようになります。

new Select().from(Item.class).execute();

※公式ドキュメントより

DBの接続もテーブルの作成もマイグレーションも、なんでもActiveAndroidが面倒を見てくれます!
ん?今なんでもっていったよね?

初期化と破棄

public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    ActiveAndroid.initialize(this); // ここでなんでもします!
  }
  @Override
  public void onTerminate() {
    super.onTerminate();
    ActiveAndroid.dispose();
  }
}

※公式ドキュメントより

ボトルネックを探す

Traceviewで表示してみる。

Invocation Countが1でInclusive Timeの80.1%を持っていっている ModelInfo#scanForModel が重いのでソースを見る。

private void scanForModel(Context context) throws IOException {
  String packageName = context.getPackageName();
  String sourcePath = context.getApplicationInfo().sourceDir;
  List<String> paths = new ArrayList<String>();

  if (sourcePath != null && !(new File(sourcePath).isDirectory())) {
    DexFile dexfile = new DexFile(sourcePath);
    Enumeration<String> entries = dexfile.entries();

    while (entries.hasMoreElements()) {
      paths.add(entries.nextElement());
    }
  }
  // Robolectric fallback
  else {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    Enumeration<URL> resources = classLoader.getResources("");

    while (resources.hasMoreElements()) {
      String path = resources.nextElement().getFile();
      if (path.contains("bin") || path.contains("classes")) {
        paths.add(path);
      }
    }
  }

  for (String path : paths) {
    File file = new File(path);
    scanForModelClasses(file, packageName, context.getClassLoader());
  }
}

private void scanForModelClasses(File path, String packageName, ClassLoader classLoader) {
  if (path.isDirectory()) {
    for (File file : path.listFiles()) {
      scanForModelClasses(file, packageName, classLoader);
    }
  }
  else {
    String className = path.getName();

    // Robolectric fallback
    if (!path.getPath().equals(className)) {
      className = path.getPath();

      if (className.endsWith(".class")) {
        className = className.substring(0, className.length() - 6);
      }
      else {
        return;
      }

      className = className.replace("/", ".");

      int packageNameIndex = className.lastIndexOf(packageName);
      if (packageNameIndex < 0) {
        return;
      }

      className = className.substring(packageNameIndex);
    }

    try {
      Class<?> discoveredClass = Class.forName(className, false, classLoader);
      if (ReflectionUtils.isModel(discoveredClass)) {
        @SuppressWarnings("unchecked")
        Class<? extends Model> modelClass = (Class<? extends Model>) discoveredClass;
        mTableInfos.put(modelClass, new TableInfo(modelClass));
      }
      else if (ReflectionUtils.isTypeSerializer(discoveredClass)) {
        TypeSerializer instance = (TypeSerializer) discoveredClass.newInstance();
        mTypeSerializers.put(instance.getDeserializedType(), instance);
      }
    }
    catch (ClassNotFoundException e) {
      Log.e("Couldn't create class.", e);
    }
    catch (InstantiationException e) {
      Log.e("Couldn't instantiate TypeSerializer.", e);
    }
    catch (IllegalAccessException e) {
      Log.e("IllegalAccessException", e);
    }
  }
}

まとめると、

  1. DexFileからすべてのクラスパスを抽出して
  2. クラスローダーでクラスをロードして
  3. 対象クラスがModelクラスのサブクラスか判定してTableInfoに渡す

などの処理をしています。 なので、この処理を飛ばすようにします。

初期化時間の比較

Application#onCreate から MainActivity#onCreate まで

パターン1

ActiveAndroidナシ(比較のため)

public class AASampleApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Debug.startMethodTracing("aasample");
    }
}

パターン2

ActiveAndroid.initialize(Context) で初期化

public class AASampleApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Debug.startMethodTracing("aasample.before");
        ActiveAndroid.initialize(this);
    }
}

パターン3

ActiveAndroid#initialize(Configuration) で初期化

public class AASampleApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Debug.startMethodTracing("aasample.after");
        Configuration conf = new Configuration.Builder(this)
                .setModelClasses(User.class)
                .create();
        ActiveAndroid.initialize(conf);
    }
}

測定結果

まとめ

  • 利便性とパフォーマンスはトレードオフの関係になる場合が多い。
  • ライブラリを読むといいことがある。


  « Previous: Next: »