Blog -
Androidアプリに共有機能などを付ける際に、onCreateでUriを貰って処理を行う
なんて動作を考えたりする、という事でやってみた。
画像をUriで受け取ってUriがNullだったらToastを出して終わらせる。
ACTION_SENDが渡って来たら、外部からの共有で来ている物
そう出なければアプリケーション内から呼び出して物
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.action_send); Uri mUri = null; String action = getIntent().getAction(); if (Intent.ACTION_SEND.equals(action)) { //ACTION_SENDで呼び出し Bundle bundle = getIntent().getExtras(); if (bundle != null) { mUri = (Uri)bundle.get(Intent.EXTRA_STREAM); } intent_flg = true; } else { //アプリケーション内から呼び出し Bundle bundle =getIntent().getExtras(); mUri = (Uri)bundle.get("data"); intent_flg = false; } if(mUri == null ){ if (Intent.ACTION_SEND.equals(action)) { Toast.makeText(getApplicationContext(),"Error", Toast.LENGTH_LONG).show(); finish(); } else { //Uriから画像を読み込み、ImageViewにセットする。 Intent intent = getIntent(); setResult(-1, intent); finish(); } } //UriからBitmap抽出やCopyなど }
もし受け取ったUriがNullだったら、Toastを出すかSetResultでエラーを返して finish()します。
ですが、これを実行するとエラーが発生します。
java.lang.RuntimeException: Unable to start activity
これは何故かというと 、finish()を実行した際にonDestroyが呼ばれた後にまたonCreateに戻り
finish()以降のコードが実行される為です、 onCreateが完了するとonDestroyが呼ばれて実行されます。
つまり、onDestroyはfinish()時にスケジュールされるだけだという事です。
エラーで回避したと思っていたが思わぬ処でエラーが発生しました。
その様な、ライフサイクルなのでメンバ変数などの初期化や扱いも注意しないと。
開発しているとXMLをいじる事が多い、特にAndroidはレイアウトや言語定数など
XMLで記述する、またその専用エディタもSDKに含まれている。
ただ、XMLをいじった後に、デバッグの実行や実行などを行うとコンソールに
Error in an XML file: aborting build.
が発生して、XXXXXXX.out.xmlというファイルが作成されて中止されてしまう。
これは、Eclipse側のバグではないかという話ですが、対応策をメモ代わりに記事にします。
簡単な話ですが、XXXXXXX.out.xmlを消して(すぐさま消せないのでバックグラウンドの処理が終わったら)
プロジェクトのクリーンを行ってパッケージエクスプローラーで右クリック、実行またはデバッグから
Androidアプリケーション()を実行すると、エラーにならずに実行される。
Eclipse側のXMLへのチェック機構の問題か稀に発生するのでちょっと面倒ですがこの方法で回避可能です。
文字列の操作、連結でとてもスピードの速いStringBuilderですが
私がハマったところ(ただのおっちょこちょいですが)の原因と注意を記事にします。
StringBuilderをメンバーで持って使いまわす場合に初期化の方法として、setLength(0)を使います
こうする事で、初期化と同じ行為(ポインタを初期化)する事が出来ます。
private StringBuilder mStringBuilder = new StringBuilder(); //初期化 mStringBuilder.setLength(0);
これを分岐に使用してifで比較します。
private StringBuilder mStringBuilder = new StringBuilder(); //初期化 mStringBuilder.setLength(0); int ret = 0; //1-3の乱数 ret = (int)Math.floor(Math.random()*(3+1-1))+1; switch(ret){ case 1: mStringBuilder.append("A"); break; case 2: mStringBuilder.append("BB"); break; case 3: mStringBuilder.append("CCC"); break; default: mStringBuilder.append("A"); break; } if(mStringBuilder.equals("A")){ Log.d(TAG,mStringBuilder.toString); }
これを、何度か繰り返します。
A → BB → CCC → A と発生した場合最後のAではFALSEとして評価されます。
これは何故かというと、mStringBuilderはポインタをリセットしましたが中身のメモリがクリア
された訳では無いからです。
実際、最後のif分の評価は、ACC == A を評価していることになります。
if(mStringBuilder.toString().equals("A")){ Log.d(TAG,mStringBuilder.toString); }
この様に比較すると想定通り評価出来ます。
これにハマって余分にデバッグしてしまった^^;
VideoViewでランダムで取得した動画を再生する機能を作成中に
MediaPlayer error(1,-2147483648)が発生する、再生自体に問題無い様だが、気持ちが悪い
調べても、殆どが英文で、拙く訳してもGoogleに聞けとかコメントの無い物も多かった。
その中でもいくつかヒントを得て試行錯誤した結果、回避したので記事にします。
どうも発生する原因は、動画読み込み中に再生を始めたとか動画再生中にほかの動画を読み込んだとか
そう言った感じのエラーと解釈した。
まず最初のコード(簡略化)
private static VideoView mVideoView; //中略 //ランダムでリソースIDをゲット-rawから取得 int movRId = getMovie(); mVideoView.setVideoURI(Uri.parse( "android.resource://" + mContext.getApplicationContext().getPackageName() + "/" + movRId )); mVideoView.start();
ボタンクリックにて毎回ランダムにIDを取得して再生させる。
これでエラーが発生するも、再生自体には問題無いただ、ボタンの連打が怖いので加工する。
onCompletionは再生終了後にコールバックされるので開始と合わせてフラグを設定する。
private static VideoView mVideoView; private static boolean mov_mode = false; mVideoView.setOnCompletionListener(new OnCompletionListener () { public void onCompletion(MediaPlayer mp) { mov_mode = false; //動画終了 } }); //中略 //ランダムでリソースIDをゲット-rawから取得 if(!mov_mode){ mov_mode = true; int movRId = getMovie(); mVideoView.setVideoURI(Uri.parse( "android.resource://" + mContext.getApplicationContext().getPackageName() + "/" + movRId )); mVideoView.start(); }
連打の抑止は出来たもののエラーはまだ発生している。
次に、リソース読み込み前に再生されている可能性への対応をする。
onPreparedは、読み込み完了時にコールバックされる。
mVideoView.setOnPreparedListener(videoViewPrepared); private OnPreparedListener videoViewPrepared = new OnPreparedListener() { public void onPrepared(MediaPlayer mp) { mVideoView.start(); } }; //中略 //ランダムでリソースIDをゲット-rawから取得 if(!mov_mode){ mov_mode = true; int movRId = getMovie(); mVideoView.setVideoURI(Uri.parse( "android.resource://" + mContext.getApplicationContext().getPackageName() + "/" + movRId )); //mVideoView.start(); onPreparedで再生するのでコメント }
これで、setVideoURI時にonPreparedがコールバックされて再生される事になる。
しかし、まだエラーが発生している。読み込み遅延でのエラーでは無いらしい。
次に、再生中に次の読み込みを始めている可能性を考えるが onCompletionが発生しているので
再生中という事は無いだろうと判断する デバッグを何度か見直すとどうやら初回は発生しない、
という事は2回目の読み込みの 前か同時に発生している事になる。
手始めに、リソースを初期化する事にした。
mVideoView.setOnCompletionListener(new OnCompletionListener () { public void onCompletion(MediaPlayer mp) { mov_mode = false; mVideoView.setVideoURI(null); //動画終了 } });
と、発生しなくなった。
VideoURIに再生出来るリソースが読み込まれているのに読み込むと出る様だ。
いちいち空にしろということなのかストリームから取り込めという事なのか、とにかく回避した。
開発者にとって、リバースエンジニアリングは知的財産の侵害です。
最近では、オープンソースの大変ありがたい知識が豊富にありますが
それとはまた違った形でソースを守りたいというニーズが多様にあると思います。
特に商業的な開発は、内部に多数のブラックボックス化したいアクセスなども含みますし
新発明(あまり良い表現ではないですが)は守られて当然だと思います。
この辺は、モラルの問題もあると思いますがやはり苦労した物は守りたい物です。
AndroidのSDKは2.2からproguardという難読化のツールを使ってapkを難読化出来ます
利用すれば、完全で無いまでも解読にはそれなりの努力と投資が必要となります。
もうちょっとなんとかして欲しいなあと思いますが、Googleさんお願いします。
(OSがオープンで厳しいのは分かりますがなにとぞよろしく)
それと、もうひとつのタイトルのエラーですが
Eclipseでproguardを使うといろいろエラーが出ます、特にSDK15はお手上げでしたが
苦労の末、回避出来ました(といっても大した事ないですけど)ので備忘録として記事にします。
Proguard returned with error code 1. See console がコンソールに出てエラーになる場合
1.難読化対象のパッケージ、クラス内に参照先が無い物が含まれている。
2.プラグイン関係のエラー
3.コンパイル順(同名のパッケージ、クラスの参照が目的の物で無い)
4.バージョンが低い(proguard)
1の場合、proguard.cfgに対象として含めない様に設定する。
(Twitter4jなど必要なlibだけ組み込む場合に参照先が無い場合がある)
-dontwarn twitter4j.**
これでこのパッケージは難読化対象では無くなる様です。
2の場合、eclipse.exe -cleanで回避出来る事が多い。
3の場合、ビルド・パス→ビルドパスの構成→順序およびエクスポートで該当パッケージの優先度を上げる
4の場合、自分はこれだったんですが、SDK15の場合proguard4.4だとこれが発生するようです。
最新が4.6ですのでここで入手して(記事執筆時点では4.7が最新)解凍後のlibフォルダの中身を
Android SDKのフォルダtools/proguard/libへ上書きします。
これで回避出来ました。
・リソースにある画像を順番に入れ替える
・特定のコードに当てはまる、リソース内に定義してあるArrayを呼び出したい。
リソースID(一部) + 変数でリソースを取得したいという処理があります。
こんな時に便利なのが
i = 1; picture_id = "pict_" + (i);
pict_res_id = getResources().getIdentifier(Picture_id,"drawable", getPackageName());
Arrayの場合は
i = 1; Array_id = "Array_" + (i);
Array_res_id = getResources().getIdentifier(Array_id,"array", getPackageName());
ExpandableListなどで択ばれた親のコードを元に子供のArrayなどを呼び出すなどに応用出来ます。