【Android】メソッド数が64Kを超えてしまいビルドできない

APKビルドしたら突然のエラー

apkビルド時に以下のエラーが出てビルド失敗となりました

Error:The number of method references in a .dex file cannot exceed 64K.
Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html

ちなみにエミュレータでの実行時にはエラーになりません

エミュレータと全然違うじゃん!
もういいよ、私Androidやめる!

何が起きているのか

64K を超えるメソッドを使用するアプリの設定 | Android Studio

要約:メソッド数はライブラリ含め65,536

対処

大抵の場合ライブラリが原因だと思われます

私の場合はGooglePlayServiceが圧迫していました SDKバージョンを変えたことにより、メソッド数もつられて増えたようでした 調べたところGooglePlayServiceだけで2万超えるようです

build.gradleを以下のように変えました

BEFRE

dependencies {
    compile 'com.google.android.gms:play-services:+'
}

AFTER

dependencies {
    compile 'com.google.android.gms:play-services-gcm:+'
}

GooglePlayService利用は必要なものだけ含めましょう

より詳しい対策などはこちら

64K を超えるメソッドを使用するアプリの設定 | Android Studio

qiita.com

qiita.com

Githubのbranchesの画面にプルリクエストのタイトルを表示する

やりたいこと

Githubのbranchesのブランチが何のブランチかブランチ名だけじゃわかりづらい なのでプルリク名をだして該当ブランチを見つけやすくしたい

こんな感じに

f:id:flabel:20170509153442p:plain

やり方

専用のChrome拡張を作ってもいいんだけど些末な機能なのでこちらの拡張を利用させてもらう

chrome.google.com

拡張をインストールしたらいかのコードを貼るだけ

const _wr = function(type) {
    var orig = history[type];
    return function() {
        var rv = orig.apply(this, arguments);
        var e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return rv;
    };
};
history.pushState = _wr('pushState'), history.replaceState = _wr('replaceState');

window.addEventListener('pushState', function(e) {
setTimeout(setPRName, 1000);
function setPRName() {
    const $branches = document.querySelectorAll(".branch-summary");
    const style = {
        "position": "absolute",
        "top": "12px",
        "left": "330px",
        "background": "#a60bff",
        "padding": "1px 5px",
        "line-height": "20px",
       "color": "#fff",
        "text-align": "center",
        "border-radius": "3px"
    };
    for (let $br of $branches) {
        const $btn = $br.querySelector(".state,.State");
        if ($btn) {
            const title = $btn.getAttribute("title");
            const $elem = document.createElement("div");
            $elem.innerText = title;
            for (let name in style) {
                $elem.style[name] = style[name];
            }
            $br.style.position = "relative";
            $br.appendChild($elem);
        }
    }
}

2017/05/10更新 GithubがhistoryAPIで制御されていたので画面遷移後にスクリプトが実行されていなかったので変更 - pushStateをフック - setTimeoutで画面レンダリングを待つ

あまりきれいではない…

UTF-8, SJIS, EUC-JPまとめてgrep

困ったことにプロジェクト内で文字コードが入り乱れていることがある そうなるとマルチバイト文字のgrepが素直にいかない

とりあえず以下のようなシェルを作っておくことにした

cgrep() {
  LANG=ja_JP.utf-8 grep -r `echo "$1" | nkf -u`  .  | nkf -w
  LANG=ja_JP.sjis grep -r `echo "$1" | nkf -s`  .  | nkf -w
  LANG=ja_JP.eucjp grep -r `echo "$1" | nkf -e`  .  | nkf -w
}
alias cgrep=cgrep

alertを使ってjavascriptからJavaへイベントの通知を行う

webview側からAndroid側へ通知を行いたいとき、以下のような方法があります

  • JavascriptInterface
  • Android側から一定間隔でWebView側を監視
  • onJsAlert

JavascriptInterfaceが正攻法で、指定したメソッドをJS側へ公開します しかし、JavascriptInterfaceはv4.2以前だと任意のメソッドが実行可能な脆弱性を持っています

AndroidのWebViewの脆弱性についての私的なまとめ - 金利0無利息キャッシング – キャッシングできます - subtech

古いバージョンをサポートしているといった状況だと使うことが難しくなります

かんたんに連携を行いたい!セキュリティを意識したくないと行ったときにalertを使った方法が使えます

実装方法

WebChromeClientを継承したクラスでonJSAlertをoverrideします

Android

@Override
public boolean onJsAlert(WebView view, String url, final String message, JsResult result) { 
    if (message != null && message.length() > 0) {
        if (message.equals("click#button")) {
            onClickHogeHoge();
            result.confirm();
            return true;
        }
    }
}

JS側

$("#button").click(function() {
    alert("click#button");
});

解説

messageにalertのメッセージが渡ります この文字列を判定し、任意の処理に流します

result.confirmを実行することにより、アラートダイアログを終了させます

戻り値をtrueにすることで、JS側のアラート表示を抑制できます

注意事項

  • result.confirmは必ず実行する

これを実行しない場合、アラートが閉じられずアプリがフリーズします

  • JSのalertの実行はアプリからの

ブラウザでも同じアプリケーションを提供している場合、当然ながらこのjsをそのまま仕込むとボタンをクリックするたびにアラートが表示されます UA等でアプリからのアクセスかどうかをチェックしJSを実行しましょう (余計なお世話でしょうか)

IPC::Cmdの使い方など

少しハマったので整理

使い方の基本

use IPC::Cmd qw[can_run run];

$command = 'ls';
#コマンドのパスを取得
my $full_path = can_run($command) or die 'ls can\'t use!';
my $buffer;
my($success, $error_message, $full_buf, $stdout_buf, $stderr_buf) = run(command => $command, buffer  => \$buffer);
if (!$success) {
    die @$stderr_buf[0];
}

print $buffer;

文字列を渡す場合の注意

echo "1 2 3 | cut -d ' ' -f1

これをrunに落とし込む場合、

run(command => ['echo', "1 2 3", '|', 'cut', '-d', ' ', '-f1'], buffer => \$buffer);

となる

区切り文字の指定は"' '"ではない

バッファーについて

実行の戻り値として取得する各種バッファーは配列リファレンスとなっている この配列は実行環境のバッファサイズ(バイト数)で区切られている

改行ではないので、CSV等を取得しループでsplitするといったときにはバッファ同士を結合する事前処理が必要になる

引数で渡した$bufferはすべての出力が入っているのでこっち使うほうが楽

自作のマグネットづくり

かんたんにマグネット作ってみた

用意するもの

  • よくポストに入っている水道業者のなどのマグネット
  • 好きな画像
  • 両面テープ
  • カッター

作り方

1. マグネットを水に浸す

用意したマグネット、業者の情報が書いて合って使えたものではありません なのでこれを水に浸しましょう 1時間もずれば表面をはがせ、マグネットだけ残ります

2. 画像をプリント

プリンターを持っている場合は写真プリントなど表面がつるつるした紙にプリントしましょう ない場合も大丈夫 ローソンが使えます

ローソンではプリントに光沢紙が使えます 詳しくはこちら

www.lawson.co.jp

A4サイズ(3508 × 2480くらい)の画像を用意しスマホ等で持ち込みます

3. 画像を切り取り貼り付ける

あとはかんたんです - プリントした画像を切り抜く - マグネットに両面テープで貼り付ける - マグネットを切り抜く

これで終わりです

そして出来上がったのがこちら

f:id:flabel:20170422135800j:plain

(初めて作ったものなので結構雑…)

GoogleAppEngineでPerlを動かす

GoogleAppEngineは5年前に使ったきり Dockerはよく知らない Perlもまだまだ しかし、ローカル以外の何処かでとりあえず動かしておきたい

こちらを参考にさせていただきました qiita.com

gcloudのインストール

GoogleCloudSDKをインストールします

Google Cloud SDK Documentation  |  Cloud SDK  |  Google Cloud Platform

https://cloud.google.com/sdk/docs/#install_the_latest_cloud_tools_version_cloudsdk_current_version

$ tar xvzf google-cloud-sdk-137.0.1-darwin-x86_64.tar.gz 
$ ./google-cloud-sdk/install.sh
$ source .bash_profile

#認証
$gcloud auth login

GoogleCloudConsole

プロジェクトを作っておきましょう console.cloud.google.com

gcloudにプロジェクトを設定します(やらなくても大丈夫かと思います)

$ gcloud config set project プロジェクト名

デプロイする

サンプルを利用します ここまでは一緒

$ git clone git@github.com:aql/perl-appengine-sample.git
$ cd perl-appengine-sample

SDKの変更があったのか、gcloud preview app --project "foo-bar" deploy .は動きません

app.yamlの設定を2箇所修正します

diff --git a/app.yaml b/app.yaml
index 5ca2107..592d919 100644
--- a/app.yaml
+++ b/app.yaml
@@ -1,6 +1,5 @@
-version: 1
 runtime: custom
-vm: true
+env: flex
 api_version: 1

修正後、以下のコマンドでデプロイします

$ gcloud app --project "プロジェクト名" deploy app.yaml

デプロイが完了したら以下のコマンド、またはブラウザから直接開いて確認ができます

$ gcloud app browse

手軽にDockerでperlの環境をGAEに上げることができました perl以外にもdockerコンテナを設定すれば任意の言語等が動きます ちなみにGKE(GoogleComputeEngine)でもDockerを利用できますが、GAEのほうがかんたんにセットアップをすることができました