2012年4月6日金曜日

続・AndroidのボタンをSVGで描画するには?(ローテクだけど高速版)

簡単に高速化する方法は?

昨日、AndroidのボタンをSVGで描画するには?という記事を書いた。

琴線探査: AndroidのボタンをSVGで描画するには?

結論として、パフォーマンスの問題が残った。

DOM4Jを使えば少し速くなるけど十分ではなかった。

そこで、自ら高速だと宣言する「SJXP」というXMLパーサーを試してみたが、今回の用途には合わないようだった。(Attributeパース時のイベントがどうもうまく取れない)

根本的解決のためにはsvg-androidライブラリの改造が必要だと思う。しかし、もっと簡単に速くするワークアラウンドはないものか・・・(^^);

そこで非常にローテクだけど超高速化する方法を思いついた゜∀゜!!

要するに、XMLの処理が重いなら、XMLの処理をしなければいいんだ。

つまり、SVGのXMLをDOMではなくテキストとして取得し、String.replaceAll()を使って変更したい色の文字列を変更しちゃえばいい!


コードを書き換える

今回、SVGの中で変更したい部分はここ。

<path id="inner" fill="#4D4D4D" d="M8.334,35C4.29,35,1,31.298,1,26.747V9.253C1,4.703,4.29,1,8.334,1h91.332 C103.71,1,107,4.703,107,9.253v17.494c0,4.551-3.29,8.253-7.334,8.253H8.334z"/>

そこで、SVGButton.javaの一部分をこのように書き換えた。

protected void init() {

    // SVGのXMLのロードと画面密度の取得
    // (svgDocがstaticであることに注意。つまり、SVGButtonがいくつも作られる場合は最初の一度だけ行うことになる)
    if (svgDoc == null) {
      try {
        svgDoc = loadResourceAsString(R.raw.roundbutton); // SVGのテキストをロード
        screenDensity = getDisplayMetrics(context).density; // 画面密度を取得
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    // SVGが読み込まれてたら
    if (svgDoc != null) {
      try {

        //Log.v("init", "スタイリング開始");
        long start = new Date().getTime();

        // スタイルパラメーターをパース
        TypedArray styles = context.obtainStyledAttributes(attrs, R.styleable.SVGButton);

        //UP用にSVGドキュメントを変更
        String docUp = replaceStyleValue(svgDoc, styles, R.styleable.SVGButton_innerColorUp, "#4D4D4D", "#444444");
        
        // UP用Picture取得
        picUp = SVGParser.getSVGFromString(docUp).getPicture();

        //DOWN用にSVGドキュメントを変更
        String docDown = replaceStyleValue(svgDoc, styles, R.styleable.SVGButton_innerColorDown, "#4D4D4D", "#999999");
        
        // DOWN用Picture取得
        picDown = SVGParser.getSVGFromString(docDown).getPicture();

        // UP用Pictureを背景描画に設定
        picOnScreen = picUp;

        long end = new Date().getTime();
        Log.v("init", "スタイリング終了 " + (end - start) + " msec");

      } catch (Exception e) {
        e.printStackTrace();
      }
    }

  }


  public final String loadResourceAsString(int resourceid) throws IOException {
    long start = new Date().getTime();
    //Log.v("loadResourceAsString", "開始");
    StringBuilder sb = new StringBuilder();
    InputStream is = getResources().openRawResource(resourceid);
    InputStreamReader isr = new InputStreamReader(is);
    BufferedReader br = new BufferedReader(isr, 1024*8); //1024*8を指定しないとwarnが出る
    String line = null;
    while ((line = br.readLine()) != null) {
      sb.append(line);
    }
    br.close();
    isr.close();
    is.close();
    long end = new Date().getTime();
    Log.v("loadResourceAsString", "終了 " + (end - start) + " msec");
    return sb.toString();
  }


  protected String replaceStyleValue(String doc, TypedArray styles, int styleId, String replaceValue, String defaultValue) {
    long start = new Date().getTime();
    //Log.v("replaceStyleValue", "開始");
    String val = styles.getString(styleId);
    if (val == null) {
      val = defaultValue;
    }
    String tmpDoc = doc.replaceAll(replaceValue, val);
    long end = new Date().getTime();
    Log.v("replaceStyleValue", "終了 " + (end - start) + " msec");
    return tmpDoc;
  }

ポイントは

  • loadResourceAsString()でSVGをテキストとして取得
  • replaceStyleValue()を使ってSVGの指定した部分の値を変更

といったところだろうか。

上で書き換えた部分以外は、すべてオリジナルと同じ。

Eclipseプロジェクトファイル:SVGButtonNoDOM.zip (274KB)


結果と注意点

こうすることで、標準APIで4000msec程度かかる処理が1100msec程度で処理できるようになった。約3.6倍高速だ!

しかしこの方法だと、SVGのソースレベルで気をつけておかないと変えたくないところまで変わってしまうなど、様々な問題が起こる可能性がある。

とは言え、ある程度気をつけておけばかなり速い。

これなら実用に耐えるのではないだろうか。