投稿履歴: プログラム

FabProgress(AndroidのカスタムView)をGithubで公開しました

GIF_20141228_204743

こういうやつです。

 

FloatingActionButtonの周りにRing表示をしてProgress状態を表示してくれます。

https://github.com/katsuki-nakatani/FabProgress

 

VolleyのJsonRequestで411エラーが返却される

VolleyのJsonRequestでAndroid 2.3.xとAndroid 4.x系で動きが違ったのでメモ

Listener<JSONObject> listener , Response.ErrorListener errListener){
		String url = Common.BASE_URL + SEARCH_URL;    	
		CustomJsonRequest request = new CustomJsonRequest(Method.POST ,url,new JSONObject(), listener,errListener);        	
    	request.setShouldCache(false);
    	request.setSequence(0);
    	return request;



CustomJsonRequestは単純にJsonRequestをextendしたクラスです。

JsonRequestを利用する際にJSONObjectをコンストラクタのパラメータで渡すのですが、パラメータなしで実行したい場合、に new JSONObject()の該当箇所をnullで渡すと2系では411のエラーコードが帰ってきます

下記とか調べてContent-Lengthの値が2系では違うのかなぁと勝手に推測して、空のJSONObjectを渡すとオッケーでした。

http://stackoverflow.com/questions/3208861/http-response-411-length-required-http-client-4-0-1-android

KitKat:4.4でimage選択intentを利用する

はじめに




このエントリは Android Advent Calendar 2013 の9日目(12/9)の記事になります。

KitKatで搭載されたStorage Access Frameworkを使ったアプリからの画像選択の取り扱い方法
全然記事みつけられなくてて苦労したので少しでも参考になればとおもって書きます。


本題



さてAndroid4.4からStorage Access Framework(SAF)が導入されましたね
私が作っているアプリでもintentを発行して画像を選択するアプリを作っていたのですが

1.画像を選択
2.com.android.camera.action.CROPでクロップ処理のIntentを発行
3.投げた先のアプリでで強制終了してしまうという結末に

最初はギャラリーとかアルバムアプリを疑いました。(遷移先アプリで強制終了したので)

でもギャラリーとかがおかしいとかないよねとおもって調べたのですが
Intentの呼び出しとResultは以下のようなコードで書いていました
protected void showPickerIntent(){
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(intent, PICKER_INTENT);
}
こんな感じでintentが発行されて画面が表示されます
Screenshot_2013-12-03-21-16-03

この時点でお分かりだと思いますが4.3以前の場合ですとギャラリー・アルバムなどの写真選択ができるアプリしか
Intentの対象になっていませんでしたが4.4ですと状況が一変しています。
DropboxやESファイルエクスプローラなどのファイルが扱えたりするもの、はたまたGoogleDriveまで選択できます。
(SAFのプロバイダを提供しているアプリがでているんですかね)

さて選択画面で画像を選択してonActivityResultで戻り値を受け取ります。
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
     case PICKER_INTENT:
         if (resultCode == Activity.RESULT_OK){
             if(data == null || data.getData() == null) {
                return;
             }
         Toast.makeText(context,data.getData().toString(),Toast.LENGTH_SHORT).show();
     }
  }
}

Toastで中身を見てみます。
アルバムやギャラリーなどの写真表示系のアプリから写真を選択した場合は
getData()の中身はcontent://media/external/images/media/XXXXのような形式でURLが返却されます。
この場合はCROPへ処理を投げても強制終了はしませんでした(4.3系と同じですね)

ただしほかのアプリを選択した場合違うURLが返却されます。

私が確認した限りでは、
Dropbox
file:///storage/emulated/0/Android/data/com.dropbox.android/files/scratch/35_1_20120927183120.jpg
GoogleDrive
content://com.google.android.apps.docs.storage/document/acc%3D1%3Bdoc%3D204

このようなURLで返却されておりそのままCROPへIntent発行してしまうと対応しているURLじゃないので落ちてしまいます。
これで私のアプリが投げているパラメータが悪いということがわかりました。
 
 

どうやってDropboxやGoogleDriveのイメージに対応するか



実はDropBox(file://の場合)はDropboxで画像を選択した時点でDropboxアプリがサーバからデータを取得してくれるので単純にファイルとして扱えば問題なくCROPまでIntentを投げることができました。

問題はGoogleDriveです。

さっぱり記事が見つからず(見つけられず)いろいろ試行錯誤したのですが
content://から始まるのでContentResolverを利用したら取れるのかなと思ったら取れました。
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
       switch (requestCode) {
            case PICKER_INTENT:
                if (resultCode == Activity.RESULT_OK){
                    if(data == null || data.getData() == null) {
                       Cursor cursor = getActivity().getContentResolver().query(uri,null, null, null, null);
                       cursor.moveToFirst();
                       int docIdIndex = cursor.getColumnIndex("document_id") ;
                       String fpath = cursor.getString(docIdIndex);
                       int index = cursor.getColumnIndex("mime_type");
                       String type = cursor.getString(index);
                       //TypeがImageのものだけ処理
                       if(type != null && type.contains("image")){
                       //GDrive処理
                       downloadPicker(data.getData());
                       return;
                    }
                }
            }
        }
}

public void downloadPicker(Uri uri){
       //非同期処理呼び出し
       new DownloadSAFFile(this.getActivity(),uri).execute();
}

public class DownloadSAFFile extends AsyncTask<Void,Void,File>{
      private Context context;
      private Uri uri;

      public DownloadSAFFile(Context context,Uri uri){
         this.context = context;
         this.uri = uri;
      }
      @Override
      protected File doInBackground(Void... params)
      {
            File cacheFile=new File(context.getExternalCacheDir(), "image_cache");
            try {
                        InputStream is = null;
                        //ここで取り出し
                        is = context.getContentResolver().openInputStream(uri);
                        OutputStream os = new FileOutputStream(cacheFile);
                        FileUtility.copyStream(is, os);
                        return cacheFile;
            } catch (Exception ex) {
                  // something went wrong
                  ex.printStackTrace();
                  return null;
            }
      }

      @Override
      protected void onPreExecute()
      {
      }

      @Override
      protected void onPostExecute(File file)
      {
            if(file != null){
            //file処理する
            //パラメータのファイルが実態なのであとはこのfileを扱えばOK
            }else{
                  Toast.makeText(context,"error", Toast.LENGTH_SHORT).show();
            }
      }
}
なぜ非同期なのかとなるのですがやはり外部へのアクセスになるのでデータのサイズやネットワーク状況によってダウンロードが遅い場合固まってしまうようにユーザから見えてしまうのでバックグラウンドで処理する必要があります。(あまり確認していないのですが長時間かかるとANRになるかも。また処理前と処理あとでダイアログなりなんなり上げてください)

これでGoogleDriveのデータもPickerで呼び出しできます。
 
 

残った課題



実は実装した際にダウンロード中にダイアログを上げるようにしたのですが、GoogleDrive上にあるものは
良いのですが、GoogleDriveからすでにダウンロードしてローカルにあるものについて実行すると
ダイアログが一瞬出て、すぐ閉じる(もちろんローカルにあるので)こととなってしまいました。
いろいろとコンテンツのUriのパラメータからダウンロード済みかどうか判断できるかどうかも調べたのですが
結果としてできませんでした。もしご存じの方いれば教えてください!
 
 

最後に



Storage Access Frameworkを利用すれば今まで簡単には使えなかったGoogleDriveへのファイルアクセスが簡単にできます。
ぜひぜひみなさんStorage Access Framework有効に使いましょう!

Android SupportLibraryのActionBarを利用する

AndroidのサポートライブラリのRev18が公開されました。
それに伴いサポートライブラリでActionBarが使えるようになりました!!!
今まではActionBarをGBやFroyoでサポートするためにはActionBarSherlockなどにお世話になっていましたが
組み込まれましたので早速使い方を書いてみます。

まずActionBarはライブラリのv4ではなくv7に組み込まれています。
(APIレベルv7(2.1)以上で利用可能です)

とりあえずワークスペース内に、v7のappcompatのプロジェクトをインポートします

importimport1

 

ちなみにこの画面ではすでに追加しているためにエラーになっていますが、通常であれば問題なくインポートできるはずです

次に、自分のアプリのプロジェクトにライブラリプロジェクトのリンクとjarのインポートを行います。

import2import2

 

(jerichoは無視してくださいね)

android-suport-v4とv7のappcompatのjarをlibs配下に配置してビルドパスに追加しておきます。

これで準備は完了です。

下記の順番にコードを追加しましょう。

 

AndroidManifest.xml

テーマをActionbarCompatのものを指定します。

    <application
        android:allowBackup="true"
        android:icon="@drawable/icon"
        android:label="@string/applicationname"
        android:theme="@style/Theme.AppCompat.Light" >
/>
(Theme.AppCompat.Light.DarkActionBarなんてのもあるよ)

次にActivityの継承元を変更します。
import android.support.v7.app.ActionBarActivity;

public class BaseActivity extends ActionBarActivity {

}
これでソースもほとんど終わりです。

それではActionBarを呼び出してみましょう
//ActionBar
getSupportActionBar(); //ActionBarのインスタンス取得

//MenuItemなどの追加時の注意
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
//ではなく下記のようにしてください
 MenuItemCompat.setShowAsAction(menuItemInstance, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
なおActionbarActivityでは、onMenuItemSelectedがOverrideできなくなっています。

NavigationDrawerで利用している場合などはOnMenuIemSelectedを利用していると思うのですが、

onOptionsItemSelectedでイベントを取得できますので処理をこちらに移動してあげてください。

これを実行すると下記のようになります

device-2013-07-25-032407device-2013-07-25-032509

 

ちなみに英語のわかる方ならたぶんこの動画を見れば一発かと。。。
Google+
追記。NavigationDrawerでドロワーをOpenCloseしている際にinvalidateOptionsMenuを読んでいると落ちるので注意(APILevel11からのメソッドなので。。)

CloudSaveの実装時の注意点

Androidのクライアント側でCloudSave機能を実装時に下記のExceptionに出くわしました

java.lang.IllegalStateException: A fatal developer error has occurred. Check the logs for further information.
 at com.google.android.gms.internal.p$f.a(Unknown Source)
 at com.google.android.gms.internal.p$f.a(Unknown Source)
 at com.google.android.gms.internal.p$b.p(Unknown Source)
 at com.google.android.gms.internal.p$a.handleMessage(Unknown Source)
 at android.os.Handler.dispatchMessage(Handler.java:99)
 at android.os.Looper.loop(Looper.java:137)
 at android.app.ActivityThread.main(ActivityThread.java:4441)
 at java.lang.reflect.Method.invokeNative(Native Method)
 at java.lang.reflect.Method.invoke(Method.java:511)
 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:823)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:590)
 at dalvik.system.NativeStart.main(Native Method)

僕の中ではManifestにAppIDを入れていたつもりでしたが、どうやら下記のGameServiceようのメタタグを設定していたみたいでした。
android:value="@string/app_id" />

CloudSaveを実装するときは下記のメタタグを入れましょう

android:value="@string/app_id" />

SQLiteを使う上ではまったこと

ストアアプリ(Windows RT)上でSQLiteを使うときにはまりました。

SQLiteがOpenできなくてなんでだろうと思って数時間は待っていました。
MSのサンプルがあったので落としてきてビルドして尾やっぱりDBはOpen時にCannot Openとなっていました。

さて簡単なことで解消しましたがOpenするときはフラグを付けないとCreateされないです!
(これはサンプルできちんと対処しておいてほしいですが。。。)

SQLite.SQLiteConnection(_dbPath, SQLite.SQLiteOpenFlags.Create | SQLite.SQLiteOpenFlags.ReadWrite);

ListView内のコンテンツをListViewの横幅に合わせる方法

ListView内のコンテンツってコンテンツの幅になっていて、ListViewの幅と一致していません。
特に気にはしていなかったのですがBordorを入れると、Bordorのサイズがコンテンツのサイズによって変化してしまいます。


list_fail

これでは不格好なので、下記の通りXAMLを変更します

<ListView>
     <ListView.ItemContainerStyle>
                    <Style TargetType="ListViewItem">
                        <Setter Property="HorizontalContentAlignment"
                Value="Stretch" />
                    </Style>
                </ListView.ItemContainerStyle>
</ListView>
これで期待通りの動作となります。

list_success

なおこちらの記事を参考にしたのですが
ストアアプリのベースがSilverlightということでこの不具合?もこのままだったのでしょうか。

Windows Phone で実機デバッグする方法

Windows Phoneはそのままでは実機にロックがかかっているのでアプリを発行できません。

http://msdn.microsoft.com/ja-jp/library/ff769508%28v=VS.92%29.aspx

こちらを参考にロックを解除しましょう。

これで実機デバッグできるようになります

Windows Phone 8の開発環境セットアップ

Windows Phone8の環境を作ったのでメモ

今回作った環境は下記の通りです。
 Windows 8 64bit
Visual Studio 2012 Proインストール済み

さぁこの環境に入れようと思って、
http://dev.windowsphone.com/ja-jp/downloadsdk
ここからSDKを入れました。
英語だった!!!!!
(ってか切り替えしてなくてデフォが英語だったので気づかなかった。。。。)

と、一度SDKをアンインストール

そして日本語でインストール!!!


新規プロジェクト作成してみました。

するとXAMLエディタNullReferenceで起動しません。

と、その解消法は
Phone 8 SDKをアンインストール
Silverlight関連(Silverlight とSilverlight SDK)をアンインストール
Phone 8 SDKをインストール

これで解消します。

とその後、今回再起動のメッセージが出なかったなぁと思いながら開発していて
デバッグでエミュレータを起動したのですが、一向にデバッガがエミュレータとつながりません。
(厳密にはパッケージも配置できるのですが、困ったことに、デバッガだけがつながりません)

さんざん悩みましたけど、再起動したら解消しました。

Phone 8 SDKをインストールした際には再起動しましょう!!


Proguardをかけた後の例外をトレースする方法

Proguardをかけた後、例外が上がってきました。
さてどうしましょう。中身はこんな感じになっています

java.lang.IndexOutOfBoundsException: Invalid index 1, size is 0
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:251)
at java.util.ArrayList.get(ArrayList.java:304)
at com.miruker.qcontact.c.ah.a(ProGuard:96)
at com.miruker.qcontact.Widgets.AppWidgetService.b(ProGuard:143)
at com.miruker.qcontact.Widgets.AppWidgetService.a(ProGuard:86)
at com.miruker.qcontact.Activity.WidgetSettingsListActivity.setAppWidget(ProGuard:274)
at com.miruker.qcontact.Components.ag.onItemClick(ProGuard:122)
at android.widget.AdapterView.performItemClick(AdapterView.java:292)
at android.widget.AbsListView.performItemClick(AbsListView.java:1068)
at android.widget.AbsListView$PerformClick.run(AbsListView.java:2525)
at android.widget.AbsListView$1.run(AbsListView.java:3186)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4441)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:823)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:590)
at dalvik.system.NativeStart.main(Native Method)

うーん。さてということでproguardではリトレースするためのツールが用意されています。

SDKやOSの環境によるのですが、とりあえずWindows(64bit)の方法です。

作業フォルダDドライブ

まず2つのファイルを用意します。
1.Eclipseプロジェクトの中に、proguardのフォルダがあるはずです。
  この中のmapping.txtをDドライブにコピーします。

2.次に先ほどのスタックトレースをDドライブにtra.txtとして保存します。

あとはコマンド
retrace.bat -verbose d:\mapping.txt d:\tra.txt
と実行
パスが通っていなければ、\tools\proguard\bin\
この中のretrace.batです。

これで結果が出力されます

java.lang.IndexOutOfBoundsException: Invalid index 1, size is 0
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:251)
at java.util.ArrayList.get(ArrayList.java:304)
at com.miruker.qcontact.Data.WidgetGroupCashData.boolean deleteInsert(android.content.Context,int,java.util.List)(ProGuard:96)
at com.miruker.qcontact.Widgets.AppWidgetService.android.widget.RemoteViews loadWidget(android.content.Context,android.widget.RemoteViews,int)(ProGuard:143)
at com.miruker.qcontact.Widgets.AppWidgetService.android.widget.RemoteViews initialWidget(android.content.Context,android.widget.RemoteViews,int)(ProGuard:86)
at com.miruker.qcontact.Activity.WidgetSettingsListActivity.void setAppWidget(android.view.View)(ProGuard:274)
at com.miruker.qcontact.Components.WidgetSettingsListView$onItemClickListener.void onItemClick(android.widget.AdapterView,android.view.View,int,long)(ProGuard:122)
at android.widget.AdapterView.performItemClick(AdapterView.java:292)
at android.widget.AbsListView.performItemClick(AbsListView.java:1068)
at android.widget.AbsListView$PerformClick.run(AbsListView.java:2525)
at android.widget.AbsListView$1.run(AbsListView.java:3186)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4441)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:823)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:590)
at dalvik.system.NativeStart.main(Native Method)


これでどこがエラーかわかるはずです。

1 / 41234