2013/04/07

JavaのDateの時刻をリセットするメモ

時間ねたが続くが Java で Date から時刻をリセットして日付だけを取り出そうとしてはまったのでメモ。

日付関係の処理はややこしい。タイムゾーンとか夏時間とか閏秒とか訳分からん。

Java の場合は java.util.Calendar が一手に引き受けてくれるのだが こいつがまた曲者ではまった。

結論だけ欲しい人の為の正解、

static Date clearTime(Date date) {
    Calendar cal = new GregorianCalendar();
    cal.setTime(date);
    cal.clear(Calendar.AM_PM);
    cal.clear(Calendar.HOUR);
    cal.clear(Calendar.HOUR_OF_DAY);
    cal.clear(Calendar.MINUTE);
    cal.clear(Calendar.SECOND);
    cal.clear(Calendar.MILLISECOND);
    return cal.getTime();
}

これで、もろもろの面倒を考えずに日付だけが取り出せる。

単純に考えると

    cal.clear(Calendar.HOUR);
    cal.clear(Calendar.MINUTE);
    cal.clear(Calendar.SECOND);
    cal.clear(Calendar.MILLISECOND);

だけで良さそうだがこれだとこうなる。

2013/04/07 14:00:00.000

AM_PMリセットしても

    cal.clear(Calendar.AM_PM);

こうだったりする。

2013/04/07 02:00:00.000

結局、AM_PM をクリアしてから HOURHOUR_OF_DAY の両方をクリアする必要があるという謎な仕様。

普通に Calendar.clearTime() が有っても良いと思うんだが...


2013/03/30

SQLServerのdatetime型にはまったのでメモ

仕事で Postgre から SQLServer に移植していてはまったのでメモ。

  • SQLServer の datetime 型はミリ秒の桁が 0,3,7 に丸められる。
  • WHERE句で比較する場合は丸めが起こらないので比較が一致しない。

具体的にどういう時に起こったかと言うと O/R マッパーを使っていて Bean に日付が残っていたケース。

bean.setKey(123);
bean.setUpdateTime(new Date());
ORMapper.insert(bean);

bean.setItem("hogehoge");
ORMapper.update(bean);

とかやった時に O/R マッパーがこんな SQL を生成してた。

INSERT INTO Bean (key,updateTime) VALUES (123,'2013-01-01T00:00:00.999');

ここで updateTime には '2013-01-01T00:00:01.000' が入る。

UPDATE Bean SET item='hogehoge' WHERE key=123 AND updateTime='2013-01-01T00:00:00.999';

WHERE句の updateTime 丸めが起こらないので UPDATE が失敗してしまう。

WHERE updateTime がそもそも不要なので O/R マッパーの設定ファイルの問題なんだけど DB がアプリが指定した物と違う値を書き込むって有りなの?

後、1/3ミリ秒単位って言うのも謎だし。

尚、SQLServer 2008 からは datetime2型が用意されて 100ナノ秒まで持てます。


2013/02/16

Androidの非同期処理と画面回転

Android-x86-4.4r2 の設定はこちら


Androidで非同期処理を Thread でやろうとするとこんな例外が出て怒られる。

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
UI スレッド以外が UI を操作出来ないって事らしい。

普通は Handler か AsyncTask を使えば回避できるのだが、ここで一つ問題が発生する。

Android のデフォルト動作では画面が回転すると Activity の破棄と再構築が行われる。
したがって AsyncTask が処理を終らせて結果を Activity に通知しようとしても既に存在しない可能性がある。

とりあえずの解決法としては AndroidManifest.xml の <activity> タグに以下の属性を追加して回転しても再構築を行わせないようにすることができる。

android:configChanges="orientation|keyboardHidden"

とは言えこれで逃げられないケースも有るのでまじめな対策を考える。

そもそも、Android の Activity はテンポラリな物で何時破棄されてもおかしく無いという位置付けになっている。にもかかわらずアプリケーションは Activity を中心に設計するようになっているので問題が発生しているように思える。

ここのページの方は Activity はあくまで「従」立場で使えとおっしゃってますが現実には色々難しいかと思います。既存のコードも有るし。


問題の本質は「主」である Activity が「従」である AsyncTask の知らない間に入れ替わってしまっていることだ。

朝、出社したら課長の席に知らないおじさんが座っているようなものである。

こういう場合は人事課に現在の所属課の課長が誰なのか問い合わせられれば良いわけだけど Android にはそういう仕掛けが用意されていない。

と言うわけで、Activity を ID で管理するクラスを用意して AsyncTask は ID から必要な時に Activity 問い合わせる方式で実装しみた。

ソースコード


ActivityManager.java:
package org.kotemaru.android.asyncrotate;

import java.util.HashMap;
import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.os.Bundle;

/**
 * Activityの管理クラス。
 * - Activityが destroy/create されても同一IDで継続的にアクセスできる。
 * - インスタンスをApplication.registerActivityLifecycleCallbacks()に設定する事。
 * - Bundle のキー "___ACTIVITY_ID___" を汚染する。
 * @author kotemaru.org
 */
public class ActivityManager implements ActivityLifecycleCallbacks {
    public final String ACTIVITY_ID = "___ACTIVITY_ID___";
   
    /** Application内で一意のActivityのIDカウンタ */
    private Integer nextActivityId = 0;
   
    // マップ
    private HashMap aid2activity = new HashMap();
    private HashMap activity2aid = new HashMap();

   
    /**
     * ActivityからIDの取得。
     * - すでにIDを持っている場合はそれを返す。
     * - IDを持っていない場合はマップに新規登録して返す。
     * @param activity
     * @return Application内で一意のID
     */
    public synchronized String getActivityId(Activity activity) {
        String aid = activity2aid.get(activity);
        if (aid == null) {
            aid = (nextActivityId++).toString();
            aid2activity.put(aid, activity);
            activity2aid.put(activity, aid);
        }
        return aid;
    }
    /**
     * IDからActivityの取得。
     * - 登録されているIDからActivityを引いて返す。
     * - Activity が destroy/create されていてると更新されている。
     * @param aid ActivityのID
     * @return Activity。未登録の場合はnull。
     */
    public synchronized Activity getActivity(String aid) {
        return aid2activity.get(aid);
    }
   
   
    /**
     * Activity.onCreate()のハンドリング。
     * - Bundleに ___ACTIVITY_ID___ を持っていればマップを更新。
     */
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        if (savedInstanceState == null) return; // First
        String aid = savedInstanceState.getString(ACTIVITY_ID);
        if (aid == null) return; // Not managed.
       
        synchronized (this) {
            aid2activity.put(aid, activity);
            activity2aid.put(activity, aid);
        }
    }
   
    /**
     * Activity.onSaveInstanceState()のハンドリング。
     * - ___ACTIVITY_ID___ にActivityのIDを保存。
     */
    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        String aid = activity2aid.get(activity);
        outState.putString(ACTIVITY_ID, aid);
    }

    /**
     * Activity.onDestroy()のハンドリング
     * - マップからActivityを削除。
     * - Activityインスタンス開放の為、必須。
     */
    @Override
    public synchronized void onActivityDestroyed(Activity activity) {
        String aid = activity2aid.get(activity);
        if (aid == null) return; // Not managed activity.
        aid2activity.put(aid, null);
        activity2aid.remove(activity);
    }
   
   
    @Override
    public void onActivityStarted(Activity activity) {
    }
    @Override
    public void onActivityResumed(Activity activity) {
    }
    @Override
    public void onActivityStopped(Activity activity) {
    }
    @Override
    public void onActivityPaused(Activity activity) {
    }

}

AsyncHelperApplication.java:
package org.kotemaru.android.asyncrotate;

import android.app.Application;

public class AsyncHelperApplication extends Application {
    private ActivityManager activityManager = new ActivityManager();

    @Override
    public void onCreate() {
        // API14 からサポートのActivityのライフサイクルのコールバック設定。
        registerActivityLifecycleCallbacks(activityManager);
    }

    @Override
    public void onTerminate() {
    }

    public ActivityManager getActivityManager() {
        return activityManager;
    }
}
MainActivity.java:
package org.kotemaru.android.asyncrotate;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.widget.TextView;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       
        // 3秒後にメッセージを書き換えるタスクを起動
        TextView m = (TextView) this.findViewById(R.id.message);
        new SlowAsyncTask(this).execute(m.getText()+"{3sec}");
    }
   
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
}

SlowAsyncTask.java:
package org.kotemaru.android.asyncrotate;

import android.app.Activity;
import android.os.AsyncTask;
import android.widget.TextView;

public class SlowAsyncTask extends AsyncTask {
    private ActivityManager activityManager;
    private String activityId;
   
    public SlowAsyncTask(Activity activity) {
        activityManager = ((AsyncHelperApplication)activity.getApplication()).getActivityManager();
        // Activity の ID を取得して保存。
        activityId = activityManager.getActivityId(activity);
    }
   
    @Override
    protected String doInBackground(String... params) {
        // 時間のかかる非同期処理。
        try {Thread.sleep(3000);} catch (Exception e) { }
        return params[0];
    }

    @Override
    protected void onPostExecute(String result) {
        // Activityは保存しておいたIDから取得する。
        Activity activity = activityManager.getActivity(activityId);
        TextView message = (TextView) activity.findViewById(R.id.message);
        message.setText(result);
    }
}

ソースの解説

とりあえずこれでちゃんと動作している。

API14 からサポートされた ActivityLifecycleCallbacks を利用して Activity の create/destory をフックし入れ替わりを管理している。ID の保存の為に Activity の Bundle を1つ汚染することになるが最小限だろう。

AsyncTask.onPostExecute()が create と destory の間に発生する事を危惧したが起こり得ないようである。onPostExecute() は UI スレッドで呼ばれる事が保証されているので UI の準備の整わない状態で呼ばれないと解釈した。





2013/02/09

VirtualBoxのAndroid 4.0 x86を縦置きにする。

Android-x86-4.4r2 の設定はこちら


VirtualBoxのAndroid4.0 x86をEclipseから使う」 の記事で宿題になっていた縦画面を調べてみた。

画面の回転

画面の回転は[F9]〜[F12]キーの長押しで発生させる事ができる。

キー
方向
F9
F10
F11
F12

但し、VirtualBox は回転したことは知らないのでそのまま。

つまりこういう事になる。


マウスも90度回転するのでモニタを縦にできる場合はこれて解決する。
また、ちゃんと回転イベントが発生しているようなのでテストにもちょうど良い。

普通の縦画面

縦置きできるモニタは常に有るわけでもないので普通に縦画面にできないか調べてみた。 現行のVirtualBox-4.2はモニタの回転をサポートしていない様子。
しかし、オプションで任意のVGA解像度を指定できるらしいのでこれを試す。

1. 仮想環境の設定ファイルをテキストエディタで開いて タグを追加する。
  • ファイルの場所は VirtualBox のディレクトリの "仮想環境名\仮想環境名.xml"
  • このときVirtualBoxは完全に終了させて置くこと。

<ExtraData>
   <ExtraDataItem name="CustomVideoMode1" value="640x800x32"/>
              :
</ExtraData>


2. 起動オプションで追加したVGAモードのコードを確認する。
  • GRUB の起動メニューで 「A」を押すと起動オプションが入力できるので「vga=ask」を追加して[Enter]。

  • するとこのような画面になるのでもう一度 [Enter]。

  • VGAモードの一覧が出るので追加したVGAモードを探してモードのコードをメモる。

3. GRUBの起動メニューに Portrait を追加する。
  • Androidをデバッグモードで起動し GRUB の menu.lst を編集する。
# mount -o remount,rw /mnt
# vi /mnt/grub/menu.lst
  • 起動メニューをコピペして 「vga=モード」 を追加する。
  • モードは 16進->10進 変換が必要。(360 -> 864)

4. 再起動して確認
  • Portrait が増えているはず。

  • 縦画面になれば成功。
  • Android側の orientation の認識も Portrait になります。縦横比で決まるのでしょう。


以上。

2013/02/03

WindowsPEのUSBメモリから起動するメモ

ITメディアの記事を参考にしたらはまったのでメモ。
(旧版の記事が検索上位にくる模様、3.1版の記事の内容は正しかった。)

1. ダウンロード


Widows7 SP1 対応の WAIK を以下からDLする。


サイズ 1.6G で2時間かかった。

2. WAIKインストール


  • ISOをマウントして setup.exe を実行する。
  • 「Windows AIK のセットアップ」を選択する。

3. WindowsPE の起動イメージ作成


> cd %WAIK_HOME%\Tools\PETools
> copype x86 %WINPE_HOME%
> copy x86\winpe.wim %WINPE_HOME%\ISO\sources\boot.wim
%WAIK_HOME%はWAIKのインストール先。
%WINPE_HOME%はWindowsPEの起動イメージ作成先。


4. USBメモリのフォーマット


diskpartコマンドを使用。

DISKPART> list disk
DISKPART> select disk <USBメモリのディスク番号>
DISKPART> clean
DISKPART> create partition primary
DISKPART> select partition 1
DISKPART> active
DISKPART> format fs=fat32 quick
DISKPART> assign
DISKPART> exit
うまく起動しない場合のおまじない。
> %WAIK_HOME%\Tools\PETools\bootsect /nt60 %USBメモリのドライブ%

5. 起動イメージをUSBメモリにコピー

まるごとコピー。エクスプローラからコピペでもOK。
> xcopy %WINPE_HOME%\ISO\*.* /s /e /f %USBメモリのドライブ%
以上で起動できた。



参考:
  • http://www.computerworld.jp/topics/560/164289

2013/01/26

Nexus7にキーボードとマウスを付けてみた。

Nexus7を開発に使っているとコンソールを使う事が多いのだがソフトウエアキーボードだと ls -l とか打つだけでも結構つらいw

というわけでキーボードとマイクロB接続のUSBハブを購入してみた。

その結果こういう事に。


もはや、タブレットの必然性が全く有りませんが入力は全然楽になりました。
マウスを繋ぐとちゃんとカーソルが現れてタッチと併用できます。画面に指紋を付けたく無いとき便利です。

このキーボードは Bluetooth ですが普通のUSBキーボードでも認識します。
っぽいキーボードが欲しかったので合えて買いました。

一見、アップル純正に見えますが中国製のパチ物で作りはチープです。



完全な英語配列である事だけが取りえなのですが一つ落し穴が...



[DEL]キーの位置に意味不明のキーがあります。
PCに繋いだときはブラウザ起動のホットキーになりますがAndroidの時は何も起こりません。
DEL は [Fn]+[BS] に割り振られているのですが利用頻度を考えると全く意味不明です。
これさえ無ければ値段相応の良いキーボードだったんですが (;_;)

追記:Macでは [Fn]+[BS] で DEL になるのが標準なのですね。



その他:
ハブに USBメモリを差せば読み込みはできます。書き込みはrootを取らないと出来ないようです。
商売の都合でしょうがちょっとがっかりです。

USBハブは必ず外部電源付きの物を買いましょう。Nexus7を電源に使っているとあっと言う間に電池が無くなります。




2013/01/20

eclipseにNexus7を繋げてみた

Nexus7を買ったので eclipse と繋げてみたのだが軽くハマったのでメモ。

Nexus7はWin7に繋げるとストレージとして認識される。
但し、eclipse からはそのままだと認識されない。ドライバが必要らしい。

Android実機が eclipse から認識されてアプリを実行するまでの手順。

1. Nexus7をデバッグモードにする。

4.1.2 から[開発者向けオプション]が隠しになったらしく
[設定]->[タブレット情報]の[ビルド番号]を7回タップする必要がある。

情報元

ついでに開発元不明のアプリを有効にする。


2. ドライバのインストール


開発用にSDKのドライバをインストールする必要が有る。
オプションなのでまず SDK Manager から「Google USB Driver」をインストール。


デバイスマネージャからNexus7のドライバを更新。


Android-SDK のフォルダを指定する。



OSにデバイスが認識される。




3. eclipse から実行


DDMSのパースペクティブを開くとNexus7が認識されている。


アプリを実行するとNexus7側にいきなりアプリが起動してくる。



以上。


2013/01/13

USBメモリからのブートについてメモ

おいらは光学ドライブが嫌いだ。
買ってもすぐ壊れる。
しかも滅多に使わないから使おうと思うと壊れてたりする。
(Plextorのみ例外。但し、お値段は2〜3倍するけど。)

なのでOSとかのインストールはUSBメモリからするんだけど毎回、CD-ROMのISOファイルをUSBメモリから起動させるのに苦労する。

BIOSの起動シーケンスを理解しないまま適当にやっていたのでちょっと真面目にしらべて見た。

物理的に起動可能なUSBメモリ

そもそも物理的に起動可能なUSBメモリとそうでないUSBメモリが存在する。
これは単純にM/BのBIOSとUSBメモリの相性であるようでUSBメモリの問題では無い。
BIOSで起動デバイスとして認識されなければアウトなのでBIOSのアップデートや幾つかのメーカのUSBメモリを試して見るしか無い。

USBメモリの起動シーケンス

通常USBメモリはHDDとして認識されるので起動シーケンスもHDDと同じである。

1. BIOSがHDDの先頭のセクタ 512Byte を MBR として読み込んで実行する。
  • MBRにはパーティション情報も含まれる。
  • OSのセレクタがMBRに書き込まれていればセレクタが起動する。

2. MBRはアクティブなパーティションの先頭セクタ 512Byte を PBR として読み込んで実行する。

3. PBRはパーティションのファイルシステムから特定のファイルを読み込んで実行する。
  • 各OSのカーネルが読み込まれて実行される。
  • 通常はファイルシステムを処理するコードは 512Byte では足りないので、間にもう一段大きなローダを読み込む。

CD-ROMの起動シーケンス

1. BIOS が Boot Record Volume Descriptor から Boot catalog の位置を調べる。

  • Boot Record Volume Descriptor は第17番目のセクタに固定されている。
  • CD-ROMのセクタは 2048 なので番地は 17*2048=0x8800 となる。

00008800= 0043 4430 3031 0145 4c20 544f 5249 544f / .CD001.EL TORITO
00008810= 2053 5045 4349 4649 4341 5449 4f4e 0000 /  SPECIFICATION..
00008820= 0000 0000 0000 0000 0000 0000 0000 0000 / ................
00008830= 0000 0000 0000 0000 0000 0000 0000 0000 / ................
00008840= 0000 0000 0000 0013 0000 0000 0000 0000 / ................

  • Boot catarog のセクタ番号は 0x47〜0x4A Byte にリトルエンディアンで書き込まれている。

2. BIOS が Boot catalog からローダを読み込んで実行する。

00009800= 0100 0000 4d69 6372 6f73 6f66 7420 436f / ....Microsoft Co
00009810= 7270 6f72 6174 696f 6e00 0000 4c49 55aa / rporation...LIU.
00009820= 8800 0000 0000 0400 3701 0000 0000 0000 / ........7.......

  • Boot catalog の
    •  0x26〜0x27 Byte に ローダのセクタ数
    •  0x28〜0x2B Byte に ローダのセクタ番号
    がリトルエンディアンで書き込まれている

3. ローダがCD-ROMのファイルシステムから特定のファイルを読み込んで実行する。

CD-ROMのISOからUSBメモリへの変換はできない


FDD,FDD から CD-ROM への変換はできるがその逆はできない。
CD-ROM のほうが後から出来たので上位互換だが下位互換にはなっていないらしい。

CD-ROM に書き込まれているローダは CD-ROM 用でファイルシステムは ISO9660 である。
したがってこのローダをUSBメモリから読み込ませても動作しない。

結局、OS毎の対応しか無い


最終的にローダが実行するファイルが分かっている場合はそのファイルを実行する環境がUSBメモリから起動できれば良い。

Windows系の場合は HP USB Disk Storage Format Tool を使えば良い。
WinXP の場合は、FreeDOS の command.com から i386/winnt.exe を起動する事でインストールできた。
試して無いが WindowsPE を使えば何でも有りっぽい。

FreeBSD,Linux 系はUSBメモリ用のイメージが用意されるようになったので気にしなくて良いのかな。

GRUB は殆どの環境に対応しているようなのでこれを使いこなすのが良さげ。
ただ、必要となる頻度が少ないのが何とも...



プロフィール
20年勤めた会社がリーマンショックで消滅、紆余曲折を経て現在はフリーランスのSE。 失業をきっかけにこのブログを始める。

サイト内検索

登録
RSS/2.0

カテゴリ

最近の投稿【メモ】

リンク

アーカイブ