2013年8月31日土曜日

あれ?すっとばした

ContentResolver resolver = this.getActivity().getContentResolver();
これやらないと始まらんよね。

ContentResolverってなんぞ?というとandroid内のプロセス間通信を制御するものらしい。
「らしい」というのはまだあんまり調べてないから(ぁ

調べます。

SDKのAPI GuidesのProcesses and Threadの章によると

Similarly, a content provider can receive data requests that originate in other processes. Although the ContentResolver and ContentProvider classes hide the details of how the interprocess communication is managed, ContentProvider methods that respond to those requests—the methods query(), insert(), delete(), update(), and getType()—are called from a pool of threads in the content provider's process, not the UI thread for the process. Because these methods might be called from any number of threads at the same time, they too must be implemented to be thread-safe.


ContentProvider methods that respond to those requests—the methods query(), insert(), delete(), update(), and getType()—are called from a pool of threads in the content provider's process, not the UI thread for the process

おおまかに訳すと
ContentProviderは呼ばれる。content provider'sのプロセスの中のスレッドプールから。UIスレッドではなく。

日本語らしく言うと、コンテンツプロバイダはUIスレッドからじゃなくてcontentのプロバイダ達がいるところのプロセスの中のスレッドプールから呼ばれてんじゃごらぁ。

まともに訳すと
コンテンツプロバイダはcontentプロバイダー達のいるプロセス中で発生しているスレッドプールで呼ばれています。

つまり、そもそも発生しているプロセス自体が違うってことですね。
だから「UIスレッド」で呼んでも問題ない。
「UIスレッド」だろうがおれおれスレッドだろうが「Appのプロセス」に結果が返される。

えーまじでー。ソースは?ってかどこで受けとってんのよその結果は。
UIスレッド上で結果を使ってあーだこーだできるじゃん。
ってことはスレッドを何らかの形で捕まえてて、そのスレッド中に投げてんじゃないのー?

スレッドを発生させているのはわかった。で、結果はどこで渡してるの?

    /**
     * After being instantiated, this is called to tell the content provider
     * about itself.
     *
     * @param context The context this provider is running in
     * @param info Registered information about this content provider
     */
    public void attachInfo(Context context, ProviderInfo info) {
        /*
         * We may be using AsyncTask from binder threads.  Make it init here
         * so its static handler is on the main thread.
         */
        AsyncTask.init();

        /*
         * Only allow it to be set once, so after the content service gives
         * this to us clients can't change it.
         */
        if (mContext == null) {
            mContext = context;
            mMyUid = Process.myUid();
            if (info != null) {
                setReadPermission(info.readPermission);
                setWritePermission(info.writePermission);
                setPathPermissions(info.pathPermissions);
                mExported = info.exported;
            }
            ContentProvider.this.onCreate();
        }
    }

sdkのソースをちらちら

ContentProviderをいれたアプリケーションで、
ふつーのUIスレッド上でContentResolver.insertとかできる。
なんだろね、これ。
予想ではUIスレッドからHandlerでLooperでも呼んでるのか?

というわけでContentResolverくんのソース。

public final Uri insert(Uri url, ContentValues values)
    {
        IContentProvider provider = acquireProvider(url);
        if (provider == null) {
            throw new IllegalArgumentException("Unknown URL " + url);
        }
        try {
            long startTime = SystemClock.uptimeMillis();
            Uri createdRow = provider.insert(url, values);
            long durationMillis = SystemClock.uptimeMillis() - startTime;
            maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
            return createdRow;
        } catch (RemoteException e) {
            // Arbitrary and not worth documenting, as Activity
            // Manager will kill this process shortly anyway.
            return null;
        } finally {
            releaseProvider(provider);
        }
    }

IContentProviderというのは、調べてみるとinterfaceだった。
で、誰がインプリメンツして応答してるの?

あった。ContentProviderNativeくん。
abstract public class ContentProviderNative extends Binder implements IContentProvider
さらに中のfinalクラス
final class ContentProviderProxy implements IContentProvider

で、このNativeくんはどこで使われてるのかというと、ContentProviderクラス。
class Transport extends ContentProviderNative
この中でabstructのInsertを呼ぶようになっている。

続く
                                            かもしれない。

そういえば

初めてAndroidのSqlite触ったときにびっくりしたなぁ。

ロールバックないんすかw
外部キーないんすかw

逆の立場、sqliteから他のDBだと、
ロールバック?あぁ、キャベツで巻かれてるry
何で結合できねぇんだごらぁ。
制約とかめんどくせー
関数超便利

となるんだろうな(遠い目

googleから新しいNexus7が発売されました

というわけで、発売されましたね。

相変わらずSDカードスロットなしっすかー。

googleとしては、外部メモリを歓迎してないのかなぁ。
作成コストが削れるからなのか、システム的に歓迎していないのか気になるところ。

データを組んでて思ったこと

SQLiteを使用する場合です。
タグを入れるテーブルとコストを入れるテーブルの関係性をどうやろうか考えました。

t_cost
_id
_value
_timestamp
_tagname

m_tag
_id
_name
_timestamp
_count

DBのアップグレードでカラムを加えるような場合、
前のデータ+デフォルト値を加えた新しいカラムをテーブルにインサートという感じにしたい。
手間を考えると、copyして古いのDropして新しいのをrenameする形が良さそうだ。
この時、_idを外部キー扱いとすると、_idの値が変わったときに連鎖updateが必要になる。
SQLiteだとシステム的にそういった仕組みがないので、自前でこつこつupdateしないといけない。
これはめんどくさい(ぁ


mapタイプのデータ群でいいや。←手抜き感
アップグレード時の手間を減らしたい。←怠惰
copyして古いのDropして新しいのをrenameすればいいよねー。←簡単
だから関係性はnameでやろう。_idはがんがん変わるし。←言い訳
連鎖アップデートを手動でとかめんどくさいですしおすし。←本音
重複しないようにロジック組めばいいよね(震え声

※良い子のみんなは「_idの値を持つ_idとは別のフィールド」を外部キー扱いして関係のあるテーブルの値を更新するように。

他のDBだったら外部キー制約あるから、ルールに則って連鎖アップデートしてね。

以下蛇足。
でかい規模のサーバにアクセスするシステムってどうなってるんだろう。
例えば、戸籍とか新幹線の予約システムとか。

サーバ系は触ったことはもちろん見たこともないのでどういう処理してるのか一度見てみたい。
サーバに投げて終了の方しかやったことねーんす。今度勉強してみよう。

新幹線の予約システムはマルス(MARS)と読むらしいです。
以下、マルス(システム)より抜粋
元々は"Magnetic electronic Automatic seat Reservation System"(磁気的電気的自動座席予約装置)の略とされていたが、現在では"Multi Access seat Reservation System"(旅客販売総合システム)の略となっている。
抜粋以上。

2013年8月29日木曜日

MMD杯のマイリス投票がぼちぼち締め切りです

締め切りは9月2日(月)21時です。

テーマが8本あって、相変わらずのごった煮でございます。

大会のサブタイトルが「REBIRTH」ということもあり、
PV系に力を入れてる方が目立つなという印象です。
それなりに見たつもりですが、他の人のおすすめからマイリスしたりしてます。

どんな閉会式になるか楽しみですね。

2013年8月27日火曜日

マルチバージョニングはやっぱりおかしい

どこに違いがあるかわからない。
0..0.0刻みでもどこに違いがあるのかわからない。
対処療法が確実とは言え、
テスト状態でとりあえず出して反応を見るのがプロモーションとは言え、
やはりマルチバージョニングは危険すぎる。
surportpackageがあるとは言え、その中にも色々違いがあったりするし。
同じようには絶対に扱えない。

利用者がエラーを寛容できるのが先か、
企業がマルチバージョニングをやめるのが先か、
利用者がベータ版でのプロモーションを認めなくなるのが先か。

とは言え、どこにエラーがあるかわかりませんは口から出せない。
やってみないとわかりませんなエラーはほんと見つけるのに苦労する。

現実的な対処がUserからの
「強制終了が頻発します。最悪です。アンインストールします。」
を真摯に受け止めて、対応をできるだけ早くすることぐらいしかない。

レポート送ってください。お願いします。

googleさん、何か声明なり注意書きなり出してください(泣)

SQLiteかPreferencesか-ProcessかThreadか

アプリの設定画面ならPreferencesというのがお約束。
そんなwebの情報をちらほら見かける。

が、そんなPreferencesで大丈夫か。

私も設定画面はPreferencesで作るつもりでした。
久々に使うことになったので色々調べてみました。

今時のPreferencesはjsonObjectやArrayListとかつっこめるらしい。すげー
キーだけでやりたい放題じゃまいか。便利便利。

さらに調べる。

どーもPreferencesがきなくさい。
追記:Contextを引数に取る場合のものは全てあやしい。
対策としては全て同じpackageで行うっていうことかな?
この問題はandroidのversionが4.0.3以上だと発生するそうです。
2.xとか早く駆逐されてほしいと切に願います。

何がきなくさいって、commitしたのにcommitできてないとかあるらしい。
より正確に言えば、commitを記述したけど、ちがう値をもってくるというものだった。
設定した値を反映してフラグメントやアクティヴィティで使用するケースがほとんどだ。
OnSharedPreferenceChangeListenerで変更検知というのもあるけど、
処理の重たいもの、もしくは何らかの理由で重たくなった場合、
こちらが期待したとおりの値を取得できるのだろうか。

さらに厄介なことにプロセスやスレッド絡みでも発生する問題がある。
AからPreferencesにアクセス。変更。
BからPreferencesにアクセス。変更。
同じコンテキスとした場合、さてこれはどうなるか。
例えが若干違うような気がしたので追記。
同一プロセス内で複数のアプリの Context を扱う場合、
つまり複数のパッケージのコンテキストで ContextImpl#getSharedPreferences(String name, int mode) を呼んだとき、
より具体的にすると、
アプリの中に複数のpackageがあり、各packageで同一のpreferenceを扱う場合、データの整合性が取れなくなる。ということです。

原因はPreferenceManagerの元、つまりSDKのソースにある。
@Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        SharedPreferencesImpl sp;
        synchronized (sSharedPrefs) {
            sp = sSharedPrefs.get(name);
            if (sp == null) {
                File prefsFile = getSharedPrefsFile(name);
                sp = new SharedPreferencesImpl(prefsFile, mode);
                sSharedPrefs.put(name, sp);
                return sp;
            }
        }


そうなるとAsyncQueryHandlerもあやしいな。
public AsyncQueryHandler(ContentResolver cr) {
        super();
        mResolver = new WeakReference<ContentResolver>(cr);
        synchronized (AsyncQueryHandler.class) {
            if (sLooper == null) {
                HandlerThread thread = new HandlerThread("AsyncQueryWorker");
                thread.start();

                sLooper = thread.getLooper();
            }
        }
        mWorkerThreadHandler = createHandler(sLooper);
    }

2013年8月24日土曜日

取り止めがなかったのでさらに追記

つまり、SQL発行から完了までの流れは、

SQLに使うパラメーターの準備

Acitivityの影響をできるだけ受けない場所=NoUiFragmentで発行準備

AsyncQueryHandlerをExtendsしたクラス

androidのworkerThreadを使ってContentResolver経由でSQLをContentProviderに発行

トランザクションどうしよう

通知を受け取る(NoUiFragmentがいいと思う)

UiFragmentの初期化とかやったりやんなかったり

Bundleの中でメッセージ出すフラグとか渡しておいて、UIFragmentが画面に出たらメッセージ

という流れになるんじゃないだろうか。

SQLの発行と完了の受け取り方

AsynQueryHandler。
別スレッド上でSQL発行。
終了通知はUIスレッド。
SQL絡みのエラーも通知できるように記述できる。メモリーカードいっぱいですよとか。

AsyncTaskLoader。
別スレッドを起こして、その中でSQLを発行。発行の仕方は生SQLでもServiceでもおk。
通知はUI上にくるけど、loader.destroyLoaderが走ればUI上にはこれない。

色々楽なのはServiceでSQLを投げること。

データ周りのアップデートは今後も続きそうだ。
serviceで投げた場合のトランザクションってどうなってるんだろう。

もうちょっとお勉強。

追記
トランザクションは特にないように見えた。
もともとSQLite自体がキャッシュした自身を公開してそれに対してアクセスする形だからなのかな。

contentResolverでエラーが発生するとプロセスKillされる。
つまり落ちる。
これはデータが常に静的に一意に使用可能なメモリが存在すること想定した動きと思われる。
つまり、エラーが起こった場合はandroidシステム上続行不可能なエラーとして扱うことを意味している。マウント解除とかありえねーという状況だと思う。

一方、AsynQueryHandlerの場合だとエラーでは落ちない。
これはContentResolverでのエラーをWorkerThread上でのエラーとしているからかな。
エラーであることを伝える実装が必要だが、メソッドが用意されている。
ついでにシンクロナイズドでスレッドを開始するので順次発行という形になっている。
public AsyncQueryHandler(ContentResolver cr) {
        super();
        mResolver = new WeakReference<ContentResolver>(cr);
        synchronized (AsyncQueryHandler.class) {
            if (sLooper == null) {
                HandlerThread thread = new HandlerThread("AsyncQueryWorker");
                thread.start();

                sLooper = thread.getLooper();
            }
        }
        mWorkerThreadHandler = createHandler(sLooper);
    }

生SQLを実行するなら色々工夫すればおk。だけどすんごい手間。

2013年8月22日木曜日

何をしているのか見てわかる

開始が始まったことがわかること。
処理中であることがわかること。
終わったことがわかること。

こいつらをもれなく満たすのがスピナーのダイアログだったりする。
しかし、ユーザーの操作はブロックされる。

Viewでもaddして触ったら内容書いてあるとかも考えたけど、画面回転とかであっさり逝きそうだ。

アニメーションで何とかできませんか。
俺みたいにアニメーションをデフォルトでオフとかされると意味なくなる。

んー困った。

DMLをどこで発行するのか

contentResolver使ってinsertやら何やらできる。
開発側で特にスレッドとか起こさなくても発行できる。
ちょーべんりやったねたえちゃんry

実はContentResolverのほうでIDつけたサービス発行してるだけなんすけどね(ぁ

何がいやかって、メインスレッド上で特にスレッドの切り分けもなく別スレッドが生成されて動いているのがなんかねー

じゃあ、どうやんのよということで色々考えた。

1.ぐるぐるダイアログだしてAsyncTaskLoaderなりThreadなり起こせばいいじゃん
A.こんなちょびっとしたのにダイアログ出して操作ブロックするとかやってられん

2.じゃ、トースト出せよwww
A.終わったか終わってねーかわかりずらいじゃねーかwww

3.もうサービスでいいよ
A.常駐とかおわっとる

4.じゃあ得意のNoUIFragmentでやれよ
A.えー何かコスト高そうでやだー

5.どうせキャンセル効かないんだしスレッドに丸投げでよかろう
A.下にあるNoUIFragmentでAsyncQueryHandlerとかやってみる?

6.モデルとして破綻してね?
A.そうだね

さて、どうしたもんか。

2013年8月21日水曜日

電子書籍化が進んでるそうです

先日のコミックマーケットで販売された「Effective Android」。
電子書籍化が進んでるそうです。ありがたや、ありがたや。

気になったのでざっと描いてみた

省電力、省メモリとなるとこんな感じなのかなぁ。
こうなるとNoUI_FragmentにごりごりCallBacksをつける形になる。且つ、もりもりメンバー保持。

UI_Fragmentがお飾りという結果に。まさにレイアウトのためだけに生きるUI_Fragment。
でもNewは少ない。再接続だけする。ひたすら参照。
実際のところ、UIに表示されてるもの、レイアウト以外はただの投影ないしミラーだったりする。
UI_Fragmentの初期値はそのままNoUI_Fragmentから持ってきてよし。

動的な変更はUI_FragmentとNoUI_Fragmentの間でInterfaceを使う。
DialogFragmentを使うなら、UI_Fragmentでとりあえず出す。
DialogFragmentのインスタンスや必要な情報もNoUI_Fragmentに持たせておいてもいい。
DialogFragmentでの一連の処理が完了したらNoUI_FragmentにInterfaceで値を渡す。
NoUI_Fragmentは必要ならLoader起こして、自身に値を確保して、その値をUI_FragmentにPost。
UI_Fragmentで処理を行う。
全部終わったらUI_FragmentからDialogFragmentを消す。

もしもUI_Fragmentがforegroundにないなら、そこで終了。
UI_FragmentのonActivityCreatedで必要なデータは全部格納する処理が必要。
すでにあるという状況をNoUI_Fragmentが作り出している。
UI_FragmentのonActivityCreatedの最後でDialogFragmentが生きているなら消す。

アキレス腱は、NoUI_Fragmentがシステムからkillされたらoutということ。
Activityよりも長生きだけど、消されるときはやっぱり消される。
どのインスタンスもそうだけどね。
違いは、いるならシステム以外から消させない。必要であることを主張しつづけることが可能。
いらないならさっさと退場していただくことが可能。
芋づる式に消す必要があるので、NoUI_FragmentのonDestroyにUI_FragmentのRemoveを行うようにしておけばいいのかな。

メモリはActivity.isFinishing()、もしくはFragment.isRemoving()で判定して消せばよし。
Backgroundに行くだけなら消す必要はないから残す。
画像に関しては画像を丸ごとキャッシュしてメンバで保持するのか、リサイズした画像のアドレスだけ持たせて再度読み込ませるか、仕様とよく相談。
個人的には丸ごとメンバでキャッシュは歓迎したくない。
ロードは早いけど、メモリではじゃまでしかない。
10件ぐらいないならいいでしょう?と言われても他に動いてるアプリあるんで^^;;;としか言えない。

ライフサイクル管理とメモリ管理が楽。いらなくなったら消せばいいし。
投げっぱなしのスレッドや張りっぱなしのサービスよりは行儀がいいと思う。

時間の限界値-追記

取り止めがない文章になっているような気がしたので追記。

時間をどこから取ってきて、
どんな端末を使って、
どんなフォーマットで、
何を使って、
どうやって表示しようとしているのかを考えましょう。

というわけでうちのアプリ君では
時間の取得はCaledarに一本化しています。
表示はandroid.text.format.DateUtilsに一本化しています。

時間の限界値

きっと誰もが通る道だと思われる日付や時間の処理。

今回作ってるものの場合、完全にローカル依存だからそれほど複雑ではないけれど。

ローカルなんだろ?好きなもん使えや。FA。
ということだと思うのですが、スマートフォンには扱える時間の範囲が決まっています。
UNIXベースというか、UNIX時間を使うものなら何でもそうらしいのです。

2038年問題のwikipediaから抜粋。2013/08/21 06:57時点
『1970年1月1日0時0分0秒から2,147,483,647秒を経過した、2038年1月19日3時14分7秒(閏秒を考慮しない場合)を過ぎると、この値がオーバーフローし、負と扱われる[2]ため、時刻を正しく扱えていることを前提としたコードがあれば、誤作動する。』
抜粋以上。

原因としては32bitで秒の計算を行う際にオーバーフローして負の数になるのが原因とのこと。

対策も同wikiより抜粋。
『対策としては、time_t型を符号つき64ビット整数型(一般にはlong long int型)にするという方法がある。符号つき64ビット整数型の場合、上限は9,223,372,036,854,775,807(263 - 1)である。これを秒数に用いるとおよそ西暦3000億年[4]まで使用できるので、事実上問題が発生することはない。最近のオペレーティングシステムや処理系では、time_t型は符号つき64ビット整数型で表されるようになってきている。』
抜粋以上。

だそうです(ぁ
ちなみにwindowsphoneだと3000年まではいけるそうです。

手持ちのスマホandroid4.0.4で時間をいじってみる。
機内モードにして、ネットワークから時刻を取得するチェックを外す。
タイムゾーンをグリニッジ標準時に手動で変更。
端末設定の日付と時刻の日付設定を変更。
2030年12月31日までは正しい日付が取れました。
2031年01月01日を設定すると設定が反映されませんでした。
うちのスマホ君はこれが限度値らしいです。

インターフェイスはDatePickerのスピナーの方でした。setMinとかsetMaxしてるってことかな。

長期なガントチャートやライフログの場合は注意が必要かと思います。

日付の取得や表示が何に依存しているのか理解した上で、
何をベースにやろうとしているのか確認することをおすすめします。
自前のUtilクラスで一本化しておくとメンテナンスも良さそうです。

2013年8月19日月曜日

時間や日付の 「カ タ チ」

UIというかUXというか、なんだろう、おれはこれがしたいという望みを叶える形について。

まず日付。
androidの3.0から標準でcalendarViewなるものが使える。
が、何か使いにくい。操作しづらいというか、何かイメージと合わない。
これは私のわがままなのです。直感というか何かそういうのを感じないのです。

次に時間。
androidの標準でtimepickerというのがあります。
確かにすぐ使える。間違いなく。
が、自分の中ではどうもこのUIがひっかかる。

もんもんとするが、作成コストが高そうなのでとりあえず上記2つで実装する。

ぱっと思いつくのが、
カレンダーは左右で月の変更。上下で年の変更。入力でも変更できるように入力箇所を設ける。
時計はアナログ時計を表示。入力でも変更できるように入力箇所を設ける。長針1回転が60分、短針1回転が12時間。AMなのかPMなのかわかるようなものがあるといい。

以上、アイディアだけ温めておくというメモ書きでした。

2013年8月16日金曜日

DateFormatよりもDateUtilsを使おう

日付の表示やらなんやらでDateFormatもしくはSimpleDateFormatを使っていたのですが、
DateFormatのほとんどがThis constant was deprecated in API level 18.非推奨とのこと。

というわけで、DateUtilsを使うことにしました。パースのほうはまだ調べてません。
DateUtilsは表示形式をintの値をflagとして指定します。

具体的には、今日の日時をlongで取得したとして、
2013/08/16(金)とStringで表示したい場合、
this.mBtnDate.setText(DateUtils.formatDateTime(
getActivity(),
justnow,
DateUtils.FORMAT_SHOW_YEAR|DateUtils.FORMAT_SHOW_DATE|DateUtils.FORMAT_SHOW_WEEKDAY)
);
こんな感じになります。|で区切って使う感じです。

時間を24時間表記で表示したい場合は、
this.mBtnTime.setText(DateUtils.formatDateTime(
getActivity(),
justnow,
DateUtils.FORMAT_SHOW_TIME|DateUtils.FORMAT_24HOUR)
);
こんな感じです。

というわけでApiLevel18以降もターゲットする人はDateUtilsに移行しましょう。
他にも便利な比較関数もあったので使ってみます。
インターネット越しの時間取得とかどうなるんだろうなー。

2013年8月14日水曜日

ひじょーーーに悔しかったので忘れないように書いておく

肝に銘じるともいう。

今回困ったのは、ListViewの1行をハイライト(強調)して維持することでした。
ListViewにsetchoicemode(ListView.singlechoice)
this.mListView.setItemChecked(position,true)※positionはハイライトしたい子のposition

起動。ハイライト(強調)されない。あれ?
ひたすら見直す。
Listのselectorの条件に引っかからないものでもあるのかと色々試した。
だめ。まったくだめ!

何が悪かったのか。rowのlayoutの親にセレクター当ててない。ふぁっきゅぅ。

そんなわけでrowのLinearLayoutに
android:background="?android:attr/activatedBackgroundIndicator"
を当てたらできました。

とてもくやしいです。

NavigationDrawerのサンプルを自分で改造したものを思い出しました。
コンテンツの部分をハイライトしてたなーと思い出してgithubから引っ張り出してきて血眼で調べました。

2013年8月12日月曜日

Effective Android かえなかったあああああああああああああああ

TechBooster様監修のEffective Androidを買いに有明に行きました。

開始30分で売り切れたとのこと。
再販については検討したいとのこと。

どんな形でもいいので再販おなしゃす!

2013年8月11日日曜日

色々考える

前提条件
データを表示するフラグメントがあるとする。
このフラグメントはデータの追加、更新、削除を行う機能をもつ。
レイアウトのケースはどうなるか。

ケース1
ListView置いて、CursorLoaderを接続する。
更新の場合は、裏でDB更新。更新完了時に表示されてるレイアウトを上書き。
追加と削除はDataSetChangeで表示をフル更新。
ActivityCreatedで常に最新のデータを取得する。


ケース2
FlameLayoutを置いて、LinearLaytouを持つSchrolViewを配置。AsyncTaskLoaderを接続する。
AsyncTaskLoaderは表示するデータのレイアウトを丸々キャッシュ。
更新の場合はケース1と同じ。
削除の場合は、裏でDB更新。更新完了時にキャッシュと表示の両方でremoveView。
追加の場合は、裏でDB更新。更新完了時にキャッシュと表示の両方でaddView。

ケース1のメリット
1高速。
2導入が簡単。
3作成時間も短い。
4ライフサイクルの管理も全部おまかせで大丈夫。

ケース2のメリット
1アニメーションの付与でリッチに。
2レイアウトのフル更新が少ない。
3レイアウトのキャッシュが効いてるのでインスタンスの取得だけでいい。

ケース1のデメリット
1表情にとぼしい。
2GCが多い。
3一からレイアウトを組み直す必要がある。

ケース2のデメリット
1キャッシュが通常の2倍以上。
2作るのがめんどくさい。
3ライフサイクルやシングルトンに疑問が残る。

ぱっと思いつくとこんなもん。
ケース2のメリットの2に関してはレイアウトの位置を変更するから、これはデータの全更新と同じような感じじゃないだろうか。
ケース2のキャッシュはかなり致命的な感じが。。。メモリの占有が多いってことは他のサービスがぶっちされるからユーザーとしてはうれしくない。スレッドだろうがサービスだろうが同じ。foregroundでもやだ。しかし、ローダーを使う以上何かしらキャッシュは効かしてるからしょうがないと割り切ればそんなもんだったりするのだろうか。

2013年8月10日土曜日

ListViewやめてみよっかな

LinearLayoutにしたほうが何かと自由が利きそうだ。
inflateしたViewをつこっむ。消す。更新する。したほうがいいような気がしてきた。
自前でアダプター組むようなもんだなー。
そうすりゃanimationはLayoutTransitionを親につけるだけでいいし。
どんな感じに仕上がるのやら。

2013年8月9日金曜日

ListViewをリフレッシュせずに表示だけ変えたい

なぜ表示だけ変えたいか。
1変えるのに全部変えるとかバカス。
DBだけ変わってりゃいいんだよ。どうせActivityCreatedで更新すんだしさー。

前提
CotentProvider実装済み。
select発行→cursor.setNotificationUri(getContext().getContentResolver(), uri);
DML発行→getContext().getContentResolver().notifyChange(returnUri, null);
2013/08/13追記
DML発行→contentProviderコマンド投げたら特に何もしないに変更してました。
_idやらなんやらはView.GoneしてるTextviewのhintにこっそり設定してそれで掴んでます。
追記ここまで。

ListFragmentを使用。
getListView.setDivider(null)でデフォルトのディバイダーを消してます。
adapterはCursorAdapterをカスタムしたもの。
cursorloaderの戻り値をそのまんまadapterにセット。newでViewholdr。bindでv.getTag()。

更新の場合
更新するViewを取得してv.getTag()でViewHolderをゲット。
ViewHolderから内容を書き換える。
notifyとかdatasetchangedとか使ってない。

削除の場合
削除するViewをVisivle.GONEに設定。
v.getLayoutParams().height = 0にする。←力技です
notifyとかdatasetchangedとか使ってない。

GONEだけでは行が詰まらなかった。height = 0だとちゃんと行が詰まる。
height = 0 でViewのレイアウトの更新なのか、adatpterviewでの更新なのか不明。
もし、viewだけの更新ならView.Goneしてからそのトリガー呼べばできるか?

アニメーションつけて下のViewを上に上げるのが理想かな。
グルーピングできるなら一斉に動かせるのだろうけど、
たぶんできないと思うから連動かな。
LayoutTransition使えると楽そうなんだけどなー。ダミーでもつっこむか?

追加の場合
adapter自体を更新しないとだめみたい。
ぽいっといれられないのか?
ためしに無理やりCursor取得してそいつでnewView→bindViewを読んでみた。
CursorLoaderがおかしくなった。エラーにはならないけども。
リフレッシュしないといけないなら、
WHERE field1 = 'hoge' ORDER BY id Desc LIMIT 10 OFFSET 0 とかか?

getViewとかで何とかなりませんか。

他のadapterならどうなんだろうなー

2013年8月8日木曜日

alertdialog.builderを使ったdialogfragmentのthemeを変更して表示したい

やってみた。

ケース
あるFragmentからDialogFragmentを呼び出して、
themeをholo.lightからholo(白から黒へ)にする。

方法1
onCreateでthis.setStyle(style,theme)
onCreateDialogで自前のレイアウトいれる。
結論
だめだった。白いまんま。


方法2
onDialogCreateでContextWrapperを作成し、
そいつにsetStyleしたものでAlertDialog.builder。
白から黒になった。やったねたえちゃんry
結論
実はだめだったりする。
ListFragmentのonListItemClickから呼び出したのだけど、
DialogFragmentが表示されている状態で画面回転すると、
ListFragment内のアダプター内のViewの表示がおかしい。
ログで確認するとDialogFragmentで当てたContextWrapperの影響を受けていた。
再度回転すると元の表示に戻るんだけどね。
調査がめんどくさかったので、それ以上調べてません。
何やかんややればできるだろうけど、設定がオンリーワンすぎて使いにくい。

方法3
onListItemClickで別Activityを起動。
マニフェストで背景を透明に設定。
styleをtheme_holo_noactionbarに設定。
表示したいものをIntentで渡して、タグなりなんなりでDialogFragmentを出す。
Activityを使うと他のサービスとかからも呼び出せて便利かなー。
でも、ListView扱うときにちょっと難儀かもしれない。

方法4
そもそもAlertDialogを使わない。Dialogでいい。
onCreateViewでレイアウトいれるなら白から黒になる。