ひらい ぶらり Hi-Library

ぷろぐらみんぐについて。ときどきどうでもいいことについて。

AWS SDK for PHP 3.x のS3のCopyObjectで署名エラー

getObjectとかlistObjectは特に問題なく動作しているのに、CopyObjectの時だけ署名エラーがでる。

SignatureDoesNotMatch

はい。 色々検証してみた結果、CopySourceの項目に日本語が使われているとエラーになるようだ。 Keyには使用しても問題ない。

仕方ないので以下のようにしてエラーを回避することにした。

<?php
$info = pathinfo($content['Key']); // listObjectとかで取ってきたデータだと思いねぇ

$s3client->copyObject([
    'Bucket' => $bucket,
    'Key' => $key,
    'CopySource' => $bucket . '/' . $info['dirname'] . '/' . rawurlencode($info['filename']) . '.' . $info['extension'],
]);

はい。日本語が使われているファイル名の部分だけURLエンコードしておけばとりあえず大丈夫。

IAMとかでS3のarnを指定するときも同じようにURLエンコードすればイケるかと思ったけどダメだった。 日本語を使わない方が健全な気もするけど、そうも行かないのが悲しいところですよね。 終わり。

AndroidTestCaseでsetUpが呼ばれた後にApplicationクラスが生成されてハマった

解決策:InstrumentationTestCaseにして、setUpメソッドsuper.setUp した後にgetInstrumentation.waitForIdleSync() を呼ぶべし

期待していた動き

- Appicationクラスが生成される
- TestクラスのsetUpメソッドが呼ばれる
- Testクラスのtestメソッドが呼ばれる
- TestクラスのtearDownメソッドが呼ばれる
- TestクラスのsetUpメソッドが呼ばれる
- Testクラスのtestメソッドが呼ばれる
- TestクラスのtearDownメソッドが呼ばれる

実際の動き

- TestクラスのsetUpメソッドが呼ばれる
- Appicationクラスが生成される <------
- Testクラスのtestメソッドが呼ばれる
- TestクラスのtearDownメソッドが呼ばれる
- TestクラスのsetUpメソッドが呼ばれる
- Testクラスのtestメソッドが呼ばれる
- TestクラスのtearDownメソッドが呼ばれる

何が困ったかと言うと、Singletonなクラスの初期化処理をsetUpメソッド内で行っているわけだけれども Applicationクラスの中でも初期化処理をしている。その為テスト用の初期化処理が、通常のアプリケーション で使う初期化処理に上書きされてしまい、テストがコケた。

d.hatena.ne.jp

Thread.sleep で対処したという話もあったけど、setUpの度にスリープされるのは嫌だし、sleepしたところで 環境に依ってはsleepが足りなくて結局コケたりする(実際ローカルではうまく動いたけど、Travis CI上ではコケた)

stackoverflow.com

どうやら InstrumentationTestCase で取得できる Instrumentation には waitForIdleSync なるメソッドがあるらしい。

Instrumentation | Android Developers

Synchronously wait for the application to be idle. Can not be called from the main application thread -- use start() to execute instrumentation in its own thread.

なるほど、わからん。 英語読めないなりに呼んでみると、「アプリケーションと同期するために待つよ。だからメインスレッドじゃ呼べないよ(メインスレッドを待ってるわけだから)。startメソッドを実行したらInstrumentationが実行されるよ」 ということだろうか。わからん。特に後半わからん。 本来の使い方はtestメソッド内で、runOnUiThread の実行が完了するのを待つために使っているので、多分looperが空になるのを待つっていう認識で良い・・・ハズ。多分。

実際に試してみたところ、setUpメソッド内で superを呼んだ直後にwaitForIdleSyncを実行するように書いたところapplicationクラスが生成されて一通りの処理が終わってから処理が再開された。 ローカルでテストして見たところ、期待通りの動作をしてくれたので、TraviceCI上でも試してみたところうまくどうさしているっぽい。

Singletonなんかにしているからテストで苦労するんだ、なんて言われそうな気もするけどSingletonはSingletonで便利な場面もあるので実に悩ましい話でした まる

FragmentStatePagerAdapterを使えと言ったな、あれは嘘だ

第二の結論

  • データの更新をしようなんて考えない。FragmentStatePagerAdapterごと作りなおす。

注意

  • FragmentPagerAdapterではAdapter作りなおしても意味がない

チラ裏

必ずしもFragmentStatePagerAdapterを使えば、notifyDataSetChangedで期待通りに変わるかと言われればそうじゃない場合も全然あるということに気付いた。

パターンが多すぎて具体的に書けないので、あと眠いので細かい説明は端折るがAdapter内で生成されるFragmentには必ずsetInitialSavedStateでsavedStateが渡されるため、データが更新されても必ず1回は更新される前のsavedStateが渡される。

つまり、Fragmentがまともに実装されていれば一度attacheされたFragmentは一回だけ更新前のデータで表示されてしまうということになる。

なので、データだけ入れ替えてnotifyDataSetChangedしても、savedStateが残っている限り完全に更新はされない。savedStateを消すメソッドもないので、Adapterごと作りなおしてViewPagerにセットし直すのが良さそう。notifyDataSetChangedなんてなかった。っていうかnotifyDataSetChanged送ったらAdapterのメンバ変数全部クリアしてほしい。

余談だけど、FragmentPagerAdapterを作り直すじゃダメなの?と思うかもですが、FragmentPagerAdapterはfragmentManagerにFragmentを保持させているのでAdapterを作りなおしてもfragmentManagerがfragmentを保持している為作りなおされない。 FragmentStatePagerAdapterはメンバ変数にFragmentのリストを保持しているのでAdapterを作りなおせば消える。

ん?てことは新しいAdapterを作った時に渡すデータのリストが、前のデータリストよりも短くなった場合かつ、Adapterによって削除されていない場合はFragmentはManagerに残り続けることになるのか・・・。きちんと消すならdestoryAllItem的なメソッドを実装して、adapterをセットし直す前に消すのが行儀良さそう。おわり。

FragmentPagerAdapterでnotifyDataSetChangedを呼んでも更新されない

結論

FragmentStatePagerAdapterを使え、でも問題はたくさんあるから油断をしてはいけない

※追記あり

FragmentStatePagerAdapterを使えと言ったな、あれは嘘だ - ひらい ぶらり Hi-Library

解決する内容

FragmentStatePagerAdapterは、destroyItemでfragmentをremoveしているので、notifyDataSetChangedを呼べば新しくgetItemで呼ばれたデータを元にFragmentが生成される。

解決しない内容

ただしsavedStateは残り続ける。以下のような場合カオスなことになる。

  1. Fragment内部にEditViewがあり、1ページ目に『ほげ』と入力された。
  2. notyfyDataSetChanged()
  3. Fragmentが再生成される。ただしsavedStateを渡して再生成されるので、Fragment内部に1と同じIDを持つEditViewがあれば『ほげ』が入力された状態で生成される

書いてて思ったけど、Fragmentの挙動としては正しい・・・のか? ともかく、気にしていないとわけも分からず想定しない動きをするのでFragment周りはソースをよく見ておいた方がいいということだけは肝に銘じておこうと思った。 viewのIDを変えれば回避できることだし、fragment的に考えたら再生成されたら復元されるのだろう。(大体余計なお世話であることのほうが多いけど)

結論その2

保持するアイテム数が少なく、絶対に更新されない場合→FragmentPagerAdapterを使う それ以外→FragmentStatePagerAdapterを使う

メモリを圧迫せず、データも更新されないならばFragmentPagerAdapterを使ったほうが、動作は早くなるハズ。でも大体FragmentStatePagerAdapterを使うことになると思う。

ここからチラシ裏

問題を解決するために色々見てたら以下の記事を見つけた。

outofmem.hatenablog.com

この記事はFragmentAdapterの使い方と言うよりは、FragmentPagerAdapter is クソみたいな話をします。

全面的に同意で、思ったことは自前でmakeFragmentNameメソッドを作らずにinstantiateItemメソッドを使えばいいんじゃないかなぐらい。書かれた時期が1年以上前なので何を今更感はあるかも。

あと、会社のパイセンと話していたらsupport libraryがバージョンアップして新たに getItemIdメソッドが実装されていることを教えてもらった。(4.1.2_r1から追加されてた) getItemIdでタグの生成をviewPager.getId() + position から、getItemで取れるなにがしかのIDをに変えてやれば問題は解決した。でもFragmentはマネージャーによって保持され続けるので、よっぽどのことがなければFragmentStatePagerAdapterを使うほうがよさそう。

Fragment周りはしょっちゅうトラブルが起きて、理解が浅いんだなーと思って調べれば調べるほどキモい気もするし、もしかしてこういうこと?あれ?ひょっとしてFragmentってちゃんと使えばすごく便利なん・・・そんなことねーやの繰り返しなきもしますが、これからも頑張ってFragmentと付き合っていこうと思います。

特定の辺だけにstrokeを付ける場合の補足

qiita.com

こちらの記事で丁寧に書かれて居るのだけれども、Javaで書いた場合とXMLで書いた場合に微妙な差異があって、機種によってはJavaで書いた場合に意図しない挙動を起こす可能性があるので補足。

元記事

private int BORDER_WEIGHT = 2;

/* 中略 */

// 白(半透明),太さ2pxのボーダーをつける
GradientDrawable borderDrawable = new GradientDrawable();
borderDrawable.setStroke(BORDER_WEIGHT, 0x55ffffff);

// LayerDrawableにボーダーを付けたDrawableをセット
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{borderDrawable});
// ボーダーがいらない辺にオフセット(負値)をつける
layerDrawable.setLayerInset(0, 0, -BORDER_WEIGHT, 0, -BORDER_WEIGHT);

// ボーダーを付けたいViewにセットする
view.setBackground(layerDrawable);

修正後

private int BORDER_WEIGHT = 2;

/* 中略 */

// 白(半透明),太さ2pxのボーダーをつける
GradientDrawable borderDrawable = new GradientDrawable();
borderDrawable.setStroke(BORDER_WEIGHT, 0x55ffffff);
// ボーダーの背景を透明にする 
borderDrawable.setStroke(0x00000000);

// LayerDrawableにボーダーを付けたDrawableをセット
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{borderDrawable});
// ボーダーがいらない辺にオフセット(負値)をつける
layerDrawable.setLayerInset(0, 0, -BORDER_WEIGHT, 0, -BORDER_WEIGHT);
// ボーダーを付けたいViewにセットする
view.setBackground(layerDrawable);

元記事のXMLの方ではsolidで透明を指定していたけど、Javaの方にはなかったのでこのままだと京セラの端末とかで背景が真っ黒になる為、律儀に透明を指定してやる必要がある。指定しないの端末が勝手に設定してくださるので真っ黒と書いたけど、そこは機種依存になるので注意。

manifestのproviderのandroid:authorityesにvalues/strings.xmlに定義した文字列を使うとインストールに失敗する

BuildTypeをreleaseでapeをビルドしようとしたら、lintでMissingTranslationとか出てコケた。 values-jaとか作ったら、全てのkeyがvaluesにあるものと一致しないとコケるようだ。そこはよろしくvaluesの値を使って欲しい。

どうやらこの警告は無視してビルドすることも出来るようだけど、気持ち悪いのでやりたくない。 仕方ないので、valuesと一致するように全ての項目をvalues-jaにも記述してビルドしてみる。

INSTALL_PARSE_FAILED_MANIFEST_MALFORMED

( ´゚д゚`)

チクチク原因を探っていったところ、manifest.xmlproviderタグのauthoritiesにstrings.xmlに記述している値を使っているところがダメらしい。 values-ja ディレクトリにあるauthoritiesに使用している項目を削除したところ動作した。 authoritiesに指定する文字列は多言語化されて一意に特定出来ない場合はエラーにされるっぽい。

とはいえ、この項目はProductFlavor毎に分けたいところだったりするので、strings.xmlに書けないのツライ。仕方ないので今回はbuild.gradleの方に書くことで避ける事にしたけどこれはなんかキモい。 どうにかならんもんか・・・

androidでfont変えると色々と弊害があるっぽい

怪奇現象が色々発生した

  1. maxLinesとか指定するとずれる

なんだか2行目の文字が1行目に食い込んだりする

  1. 消える

もはや表示されない。

  1. 端末によって挙動が違う

消えたり、消えなかったり、ずれたり、ずれなかったり

噂によるとheightをしてしたり、layout_gravityにtop|bottomを指定するといいらしいけど効果がなかった。 なんじゃこりゃ・・・