Bescottee苦しいときは伸びてるとき、楽なときは伸びていないとき

au one market は KDDI が運営しているAndroid アプリのマーケットの名前です。
auマーケットアプリの再インストールをしたいだけの方はこちらの記事を見ればすぐにインストールできます。

Android Market とは別に独立したマーケットで、 au の各種サービスを利用するためのアプリがこのマーケットだけで提供されています。具体的には、LISMO Player や LISMO WAVE, LISMO Unlimited, LISMO Book Store, LISMO Channel , LISMO CONNEXION, Run&Walk, Fitnessや karada Manager、au Smart Sports やナビアプリなどたくさんあります。それ以外にも Android Market に提供されているアプリもいくつか提供されていますし、有料で販売されているものもあります。

au のAndroid 端末には、このau one Market にアクセスするためのアプリの au one Market アプリが標準搭載されています。

au one Market を利用しない人にとっては必要ないため、このアプリを削除できるようになっています。良い作りだと思います。

ただし、削除したあとに LISMO などのサービスを利用したいために再インストールが必要になった場合に、再インストール方法が難しい現状があります。私自身もとても時間をかけて探しました。
次回も簡単に探せるようにここにメモしておきます。

まず、端末のブラウザから au one のトップページにいきます。ホームページを変更している場合は、下の画像のようにブラウザのブックマークから au one を選択しましょう。

au one のインターネットサイト

au one のホームページはコンテンツが多いため、上から下まで見ると以下の画像のように大変長いホームページです。
この中の一番下のほうの矢印の部分の「アプリを探すならau one Market!」を選択します。
au one ホームページ

次のページの下のほうにある「au one Market アプリ ダウンロード」を選択すると、au one Market アプリのダウンロードが始まります。
ダウンロードが完了すると通知(Notification)に AuOneMarket.apk のダウンロード完了が通知されるので、これを選択して、インストールを行いましょう。

au one Market アプリのダウンロードページ

au one market アプリのダウンロード

au one market アプリのダウンロード中

au one market アプリのダウンロード後

au one market アプリのインストール方法

au one marketアプリのインストール中

au one marketアプリのインストール完了

あとは、いつも通りに au one Market を選択して起動してください。

au one Market アプリを削除できるようにしていることは大変良いことだと思いますが、再インストールするのに手間がかかるのは残念です。
もっと簡単に再インストールできるようになってほしいと思います。au さんに期待します!!


Androidアプリ開発でもっとも多く利用されている IDE は eclipse で、eclipse の便利な機能としてソースコードを整形(インデント位置やif前後のスペースや{}括りの位置、改行位置など)機能があります。詳しくは[Eclipse] ソースコード整形(Code Format / Java Code Style Formatter)にも説明があります。

XMLのソースコード整形の問題点

上記のeclipseのjavaソースコード整形は大変便利なのですが、Androidアプリ開発でUIを作成する場合に利用するXMLファイルには同じ整形ルールが効きません。

以下のようにXMLファイル上でマウス右クリック→「Source」→「Format」(キーボードショートカットの場合、Shift+Ctrl+f)を実行しても、以下のような結果となり1行に1つや2つのアトリビュートが入ったものとなり、見づらかったり、1アトリビュート削除や切り取り貼り付けが実施しづらいものになります。

EclipseXmlSourceFormat

XMLフォーマッターの標準設定の場合

具体的には以下のように1行に2つの要素(アトリビュート)が入るため、切り貼りや削除がやりにくい状態です。

XMLのソースコード整形ルールの設定

上記の問題は解決可能です。eclipse で XML ファイルのコード整形を実施する場合は、javaソースコードへの設定とは別に設定を行う必要があります。
設定場所は、メニューの[Window]→[Preference]でPreferenceを開いて、以下のキャプチャ画像のように[XML]→[XML Files]→[Editor]に存在します。

Eclipse XML Format Setting

  • Split multiple attriutes each on a new line
    • 複数の属性をそれぞ新規行に分割
  • Align final bracket in multi-line element tags
    • 複数行要素タグに最終括弧を位置合わせ

(pleadesの翻訳参照)

「複数の属性をそれぞ新規行に分割」を設定ONにした場合

「複数の属性をそれぞ新規行に分割」と「複数行要素タグに最終括弧を位置合わせ」を設定ONにした場合

2つの設定の比較

2つの設定を抜粋して以下のように比較してみると、閉じるブラケット(>)の位置や閉じるタグ()の位置が異なります。

切り貼りを行うことを考えると、両方ともONの設定がおススメです。

結論

Androidアプリ開発を行う場合、XML で UI を記載します。。画面数や機能が多くなってくるとこのレイアウトXMLの数も増えていくと思います。そうなると、XML内での切り貼りや他のアプリに利用するためにレイアウトXMLの一部だけを再利用したり、コピペで持っていきたいという場合が増えてくると思います。それ以外にも git や svn のような構成管理システムを利用している場合、システムに変更点を入力するまえに diff で確認するときに1行ごとに変更点が見えることは大変有益です。個人的にはこちらの効果のほうが協力だと思っています。XML Formatter の実行方法は、マウス右クリックからの

[/source]

->[Format]もよいのですが、キーボードショートカットのShift+Ctrl+f が簡単でよいと思います。残念ながらJava エディターの Formatter のように save action に xml formatter の機能を付けることがいまの eclipse ではできない(?)ようなので、XMLを書き変えたら、XMLで何か編集したら、Shift+Ctrl+f を押す癖をつけておくとよいです。もし、eclipseで XML Formatter を保存時に適用する方法があれば教えてください。標準設定のままのXML Formatter では、相当な手間が発生するため、今回ご紹介した2つの設定は、常にONにしておくことをお勧めします。

おまけ

今回の設定項目の4つ目の「Clear all blank lines」については、一人で開発する場合はON,OFFどちらでもいいと思いますが、複数人で開発する場合は、ONにして全体のXMLで統一できるようにしておいたほうが個人的にはよいと思います。

FragmentからActivity呼び出し方法のメイン画像

以前のエントリの「 1apkでAndroidタブレット向けとスマートフォン向けアプリを実現する方法 その3」の「FragmentとActivityの役割分担」でFragmentとActivityの関係について記載し、上の図のように Fragment が導入されたからといっても、Fragment はActivityに依存しないように機能をモジュール化できる機能であり、以下の図のように他のActivityと依存関係を持たせることは良くないため避けるべきと記載をしました。

それについて、FragmentのAPIで依存しないような機能が提供されていることが判明しました。それについて記載したいと思います。以下で解説を行うAPIは、compatibility v4ベースで行います。Fragment クラスのAPIとして以下の2つのAPIが提供されています。Androidアプリ開発者にはなじみ深いAPIだと思います。

 void	startActivity(Intent intent)
          Call Activity#startActivity(Intent) on the fragment's containing Activity.
 void	startActivityForResult(Intent intent, int requestCode)
          Call Activity#startActivityForResult(Intent, int) on the fragment's containing Activity.

Fragment.java で提供される API

Fragment.java のソースコードは、(Windowsの場合)以下にあります。Linux, MAC の方はそれぞれの環境に読みかえてください。存在しない場合は、SDK Tool で Compatibility Package をダウンロードしてください。

...\android-sdk-windows\extras\android\compatibility\v4\src\java\android\support\v4\app\Fragment.java

Fragment.java の startActivity()メソッドとstartActivityForResult()メソッドをそれぞれ具体的に見ていきたいと思います。
それぞれのメソッドの抜粋が以下のようなものです。

    /**
     * Call {@link Activity#startActivity(Intent)} on the fragment's
     * containing Activity.
     */
    public void startActivity(Intent intent) {
        if (mActivity == null) {
            throw new IllegalStateException("Fragment " + this + " not attached to Activity");
        }
        mActivity.startActivityFromFragment(this, intent, -1);
    }
    /**
     * Call {@link Activity#startActivityForResult(Intent, int)} on the fragment's
     * containing Activity.
     */
    public void startActivityForResult(Intent intent, int requestCode) {
        if (mActivity == null) {
            throw new IllegalStateException("Fragment " + this + " not attached to Activity");
        }
        mActivity.startActivityFromFragment(this, intent, requestCode);
    }

上記の抜粋をみると、mActivityのメソッドを呼び出していることがわかります。
それ以外はただの初期化状態の確認(nullチェック)だけです。
ここで startActivityFromFragment()メソッドという見慣れないメソッドが呼び出されています。
これは何でしょうか?それを探るために、mAcitivyt が何者かを調査します。

FragmentActivity.java で提供される API

    // Activity this fragment is attached to.
    FragmentActivity mActivity;

すると、225行目で mActivity の宣言がありました。ここから FragmentActivity クラスということがわかりました。
続いて、Fragment.java と同じディレクトリにある FragmentActivity.java の中身を見ていきます。

FragmentActivity.java の643行目に startActivityFromFragment() メソッドがありました。

    /**
     * Called by Fragment.startActivityForResult() to implement its behavior.
     */
    public void startActivityFromFragment(Fragment fragment, Intent intent,
            int requestCode) {
        if (requestCode == -1) {
            super.startActivityForResult(intent, -1);
            return;
        }
        if ((requestCode&0xffff0000) != 0) {
            throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
        }
        super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff));
    }

ここでの super は何かというと、FragmentActivity.java の class 宣言で以下のように記載があり、Activity クラスということがわかりました。
つまり、startActivityFromFragment() メソッドで行っていることは、Android開発では一般的な Activity の startActivityForResult()メソッドか startActivityForResult()メソッドを リクエストコード(requestCode)の有無によって、切り替えて実行していることがわかりました。

public class FragmentActivity extends Activity {
    private static final String TAG = "FragmentActivity";

    private static final String FRAGMENTS_TAG = "android:support:fragments";

結論

以前のエントリで Fragment から startActivity() すべきでないと書きましたが、よく分析してみると Fragment で startActivity()しても以下の図のように上位のActivity の startActivyt()メソッドが呼び出される仕組みがあるため、Fragment からでも実施して問題ありません。
FragmentからのActivity呼び出しの正しい方法

Honeycomb はソースコードが公開されておらずこのような調査・分析ができませんが、Compatibility package に関してはソースコードが公開されているのでこのように調査できることは大変有益だと思います。
Fragmentは、Honeycomb と Compatibility package のそれぞれで提供されているAPIが一部異なっているものがあります。
具体的には、ActivityとFragmentActivity や getFragmentMangaer() とgetSupportFragmentManager() などです。

それでも、本ソースコードと Honeycomb 内に実装が大きく異なっているとは考えづらいため、この Compativility package のソースコードから Honeycomb の実装が一部見えてくると思います。早くICSになってソースコードが公開されることを楽しみにしています!

AndroidMarketNewVersion1AndroidMarketNewVersion1

AndroidMarketNewVersion3AndroidMarketNewVersion4

取得方法

必要なもの
  • SIMフリーなAndroid端末(対応Android Versionは不明)
    • 私の場合は、Nexus S(2.3.4)でした
  • USのSIMカード
    • 私の場合は、NexusS購入時についてきた以下の写真のT-MobileのSIMカードを利用しました

※他の国のSIMカードでも可能かもしれませんが、試していないのでわかりません。
T-Mobile SIM Card

手順
  1. USのSIMカードを刺して端末を起動。Wifi で Internet に接続状態にしておくこと。
  2. 「設定」アプリ→アプリケーション→アプリケーションの管理(スクリーンショットあり)→マーケット(スクリーンショットあり)に移動して、「キャッシュの消去」を実行
  3. ホームに戻って、1,2分待ってからAndroid Market を起動すると新しいタイル表示版Android Market に更新済み。
  4. Android Market 更新中にホーム上のショートカットから起動しようとすると「アプリケーションがありません」と言われます。こうなると更新中に間違いないありません。(Android Market アプリはシステムアプリなのでユーザは削除できないため)

人によっては、キャッシュの消去を実施しなくても新しいタイル表示版Android Marketに更新された人もいるようですが、私の場合は、更新されなかったため、この方法を試してみてうまくいきました。USのSIMが無い方は、周りにもっている人がいれば少しだけ借りて、AndroidMarketを更新したあとに返すという方法もあります。

AndroidMarket How to update new version

AndroidMarket How to update new version2

Eclipse Memory Analyzer (MAT)のサイト画像

メモリリークを発見!Androidアプリのメモリ解析手法」でEclipse Memory Analyzer(MAT)のインストール方法と使い方の紹介をしました。今回は、Eclipse Memory Analyzer (MAT)のさらに踏み込んだ使い方を紹介します。

Eclipse Memory Analyzer(MAT)のインストール方法

MATがまだインストールしていない場合は、以下の手順でインストールしてください。

  • Eclipseのメニューバーの(日本語化済みの場合)「ヘルプ」⇒「新規ソフトウェアのインストール」
    • 作業対象:–すべての使用可能なサイト–
    • フィルター入力:memory
    Memory Analyzer (MAT)インストール画面

    Memory Analyzer (MAT)インストール画面

  • 「Memory Analyzer」 と 「Memory Analyzer(Charts)[オプション]」にチェックをいれて、「次へ」を選択し、インストールを継続
  • まだ再起動せずに、続いて、 「Memory Analyzer(Charts)[オプション]」で利用する図作成ライブラリ(BIRT)をインストール。これをいれておくとメモリ利用状況がMAT上で綺麗な図で表示されます。是非インストールしておきましょう。
    Memory Analyzer Charts向け図作成ライブラリBIRTインストール

    Memory Analyzer Charts向け図作成ライブラリBIRTインストール

  • インストール後は、eclipse 再起動を求められるので、再起動しましょう。

OQL (Object Query Language)利用方法

Eclipse Memory Analyzer(MAT)には、以下の主要な機能があり、OQLはクラスオブジェクトの任意の抽出方法を定義でできるものです。

  • アクション
    • ヒストグラム(Histgram):クラスごとのインスタンス数を表示
    • クラスオブジェクトごとの木構造(Dominator Tree):もっとも大きなクラスとそれらの存在期間を表示
    • 上位の消費者(Top Consumers):クラスごととパッケージごとにもっともメモリを利用しているクラスを表示
    • クラスの複製(Duplicate Classes):複数のクラスローダからロードされているクラスを発見(レビューコメント頂いたkare様、ありがとうございました)
  • レポート
    • メモリリーク予想(Leak Suspects):メモリリークの予想とシステム概要をレポート
    • 上位のコンポーネント(Leak Suspects):全体のヒープの1%よりも大きなコンポーネントをレポートする機能

    OQL (Object Query Language):は、Javaヒープの検索をするために利用できるSQLのような問い合わせ言語です。OQLはJAVAヒープから必要な情報を選択したり、フィルターしたりすることができます。OQLは、JavaScrip埋め込み型言語(JavaScript Expression Language)をベースとしています。OQLのクエリー定義は以下のようなものです。

    select <JavaScript expression to select>
       [ from [instanceof] <class name> <identifier>
       [ where <JavaScript boolean expression to filter> ] ]
    

    class name にはフルパスの Javaクラス名(例えば、 java.lang.String)を指定します。

    OQLは、以下のMATのメニューから選択できます。

    Eclipse Memory Analyzer(MAT) OQL選択画面

    Eclipse Memory Analyzer(MAT) OQL選択画面

    OQLは特定のクラスオブジェクト検索を行えるものです。具体例として、100文字以上のString を保持しているオブジェクトを検索したい場合は以下のようなSQLに近いコマンドを実行します。OQLのテキストボックスに以下のコマンドを記載し、「!」ボタンをクリックすると実行されます。

    Eclipse Memory Analyzer(MAT) OQL実行方法

    Eclipse Memory Analyzer(MAT) OQL実行方法

    SELECT s FROM java.lang.String s WHERE (s.count >= 100)
    

    実行結果は以下のようになります。

    Eclipse Memory Analyzer(MAT)のOQL実行結果

    Eclipse Memory Analyzer(MAT)のOQL実行結果

    さらに、以下のようなクラスオブジェクトの中身の値も表示することができます。以下の例は、Fileオブジェクトの持っている path フィールドの中身の値を一覧で表示する例です。

    select file.path.value.toString() from java.io.File file
    

    実行結果は以下のようになります。

    Eclipse Memory Analyzer(MAT) OQL実行例 Fileオブジェクトのpathを表示

    Eclipse Memory Analyzer(MAT) OQL実行例 Fileオブジェクトのpathを表示

    SELECT cl FROM INSTANCEOF java.lang.ClassLoader cl
    

    実行結果は以下のようになります。

    Eclipse Memory Analyzer(MAT) OQL実行結果

    Eclipse Memory Analyzer(MAT) OQL実行結果

    プリミティブ配列を利用する場合は以下のように利用します。

    select a from int[] a where a.length >= 256
    

    Android的なOQLの利用方法

    Android開発でOQLを利用する方法としては、まだ良いものは思いついていませんが1つ紹介します。

    以下の例は、Activity を継承したクラスの一覧を表示するOQLです。いくつかの画面遷移を行うアプリの場合、ライフサイクルの管理をきちんと行っている前提で、画面遷移した直後やメモリを消費する処理を行った場合にヒープダンプを保存しておいて、後から以下のコメントでどのActivityが残っているかを調査するのに役立ちます。

    SELECT ac FROM INSTANCEOF android.app.Activity ac
    

    Eclipse Memory Analyzer (MAT) OQL for Activity

    今回のテストで利用したソースは以下です。ボタンを押すと、ActivityContextActivity⇒ActivityContext2Activity⇒ActivityContext3Activity ⇒ActivityContext4Activity の順番にActivityを立ち上げていくのみのサンプルで、ActivityContext4Activityまで画面遷移してから実行した結果です。

    public class ActivityContextActivity extends Activity {
    
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
        }
    
        public void onClickButton(View v) {
            Intent intent = new Intent(this, ActivityContext2Activity.class);
            startActivity(intent);
        }
    }
    
    
    
    	
    
    

    OQLを利用してみた感想

    javaの世界では一般的な本ツールも、Androidの世界ではまだ Memory Analyzer ですら利用されていない状況ですが、Eclipse を利用したAndroid開発を進めていくうえで、Eclipse で提供されている JAVA テクノロジーには大変良いものが多く、是非活用していきたいと思っています。OQL以外にもまだまだ利用できるものがありそうなので調べていきたいと思います。

    Android開発でMemory Analyzer やOQLの便利使い方をご存じの方は是非コメント頂ければうれしいです。

1apkでAndroidタブレット向けとスマートフォン向けアプリを実現する方法」で完成後のクラス図を記載しましたが、「Android デベロッパーラボ 東京 2011」のコードラボ向けソースコードをベースに今回の目的である1apkでタブレット向けとスマートフォン向けアプリを実現するために、大幅な変更が必要なことが判明しました。

設計方針の変更

そのため、今回はまず1apkでタブレット向けとスマートフォン向けアプリを実現するために、簡素化して実装を進めることにしました。

以下のものがそのクラス図です。

緑色のクラスが新規作成のクラスで、青色が変更を行う必要があるクラスです。

変更のステップは以下のような流れです。

  1. 起動Activityのタブレット、スマートフォンの両対応向けの準備
  2. スマートフォン向けレイアウト作成
  3. スマートフォン向けノート編集画面の作成
  4. Fragment クラスへの機能追加

起動Activityのタブレット、スマートフォンの両対応向けの準備

スマートフォン向けレイアウトを res/layout におくために、コードラボ完了時のタブレット向け画面レイアウトを res/layout-xlarge-v11 に名前変更を実施

起動Activityをタブレット向けとスマートフォン向けの両対応させるためには、Androidプラットフォームがどのようにレイアウトの切り替えを行っているかを知っておく必要があります。以下のアクティビティ図をもとに紹介します。Activity が起動されると onCreate() が呼び出されて、この中でレイアウトファイルを setContentView() するというのがAndroidのもっとも一般的な画面の作り方です。setContentView()を行うとAndroidの仕組みによって端末の固有情報をもとに読みだすレイアウト xmlファイルの読み出し先のディレクトリを変更してくれます。もっと詳しく知りたい場合は、Android SDK:Load the XML Resource(英語)を参照ください。

layout フォルダの使い方

今回の場合は setContentView(R.layout.notepad); と指定しているので、スマートフォンの場合、layout/notepad.xml が対象となり、タブレットの場合は、layout-xlarge-v11/notepad.xml が対象になり、対応したレイアウトファイルを読み込んでくれます。そのため、コードラボ完了時のレイアウトファイルのフォルダを以下のように変更してから、スマートフォン向けのnotepad.xml を新規作成します。

表:レイアウトフォルダの変更
変更前のフォルダ 変更後のフォルダ
/res/layout/ /res/layout-xlarge-v11/
/res/layout-port/ /res/layout-xlarge-port-v11/

(※)layoutより後ろに続く識別子(xlargeやportやv11など)の順番は正しい順序で指定しないと正しく動作しません。詳細な項目や並べる順番を知りたい場合は、Providing the Best Device Compatibility with Resources(英語)How Android Finds the Best-matching Resource(英語)を参照してください。

続いて、新規作成するスマートフォン向けのレイアウトファイルを紹介します。中身はタブレット向けのものとあまり変化はなく、以下のようになります。

タブレット向けの /res/layout-xlarge-v11/notepad.xml と比較してみるために以下の掲載します。

もともとの notepad.xml では、2ペイン構成で左側にノートリストがあって、右側に編集画面がでてくるものでした。スマートフォン向けの画面の場合は画面サイズが小さいため、2ペイン構成にはできないため1ペインで画面遷移を発生させるユーザインタフェースにします。そのため、タブレットのレイアウトファイルの <fragment …. NoteListFragment のみを抜き出したものがスマートフォンの起動画面のノートリストのレイアウトになります。

タブレット向けのレイアウトファイルのnotepad.xml の中の <LinearLayout android:id=”@+id/note_detail_container” という部分はまだ Fragment 指定になっていないため、あとから Fragment の指定に変更します。コートラボの課題では問題になりませんでしたが、せっかく NoteEdit を Fragment 化したので、ここも Fragment 指定にするように課題に入っていたほうがよかったですね。

スマフォでの実行

この状態でスマートフォンで実行してみましょう。ノートが1つもないため、真っ黒な画面になると思います。ここでノートを追加するためにメニューボタンを押すと Exception が発生して落ちてしまいます。

onCreateOptionsMenu() の中のadd.setShowAsAction() で落ちます。理由は、このメソッドは Honeycomb 3.0(API Level 11)からのAPIのため、API Levelが 10 以下のスマートフォンでは提供されておらず、実行時に Exception が発生してしまいます。

これを避けるために、実行時にAPIレベルの判断ロジックを呼び出して実行するかどうかを制御します。具体的な判断ロジックについては、前回のエントリ「1apkでAndroidタブレット向けとスマートフォン向けアプリを実現する方法 その2」を参照ください。今回やりたいことはAPIレベルの判断のため、UIUtils.isHoneycomb() を利用して、add.setShowAsAction() メソッドをコールするかどうかを判定します。

NotepadActivity.java の具体例は以下のようになります。

スマフォ向け対応

続いて、「スマートフォンの場合に画面遷移が必要なため、それ向け対応」を行います。

メニューボタンを押して「Add Note」を実行したときに、タブレットの場合は画面遷移を行わずに右側に新規ノート画面を表示していましたが、スマートフォンの場合はノート編集画面を新しく起動するようにします。「Add Note」を実行したときには、onMenuItemSelected() がコールされるため、その中でタブレットかスマートフォンかの判断ロジックを利用して、処理の振り分けを行います。具体的には以下のようなコードになります。

いまの状態では、スマートフォン向けのノート編集画面(NoteEditActivity)がないため、コンパイルエラーのままなので、これを新規作成します。

NoteEditActivity はスマートフォン向けのみのActivityのため、明確にわかるように所属するパッケージを変更します。
パッケージ名:com.example.android.honeypad.phone の中に以下のように NoteEditActivity.java を新規作成します。

現時点で作成される NoteEditActivity は以下のようになります。

public class NoteEditActivity extends FragmentActivity implements
        NoteEditFragment.OnNoteSavedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.note_edit_phone);

        FragmentManager fm = getSupportFragmentManager();
        NoteEditFragment edit = new NoteEditFragment();
        // add the NoteEditFragment to the container
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.note_detail_container, edit, "Edit");
        ft.commit();

    }
}

続いて、NoteEditActivity で参照しているレイアウトファイル(R.layout.note_edit_phone)を以下のように作成します。

上記の res/layout/note_edit_phone.xml の中身は、res/layout-xlarge-port-v11/notepad.xml の <LinearLayout android:id=”@+id/note_detail_container” の部分を Fragment 指定に変更したものです。そのためこの機会に res/layout-xlarge-port-v11/notepad.xml の <LinearLayout android:id=”@+id/note_detail_container” の部分をFragment 指定に変更しておきましょう。変更後の res/layout-xlarge-port-v11/notepad.xml は以下のようになります。

これでスマートフォンの起動画面(ノートリスト画面)(NotepadActivity)からノート編集画面に遷移することができました。ここでTitleとBodyに文章をいれて、Confirm ボタンを押すと保存されますが、ノートリスト画面に戻りません。これはNoteEdit を Fragment 対応したときにタブレット向けしか想定しておらず、Confirm ボタンを押したときの動作として、保存を行うことしか実装されていないためです。タブレットの場合は、左側にノートリスト画面があるため、保存すればすぐに新しいノートのタイトルが追加されて反映されるので問題ないですが、スマートフォン向けの場合は保存を行ったあとにノートリスト画面に戻ってほしいため、この機能を NoteEditFragment に追加します。

さて、この機能はどのように実現すればよいでしょうか?何も考えずに実現しようとすると、NoteEditFragment にタブレットかスマートフォンかの判断ロジックをいれて、スマートフォンの場合に Activity を finish() するという以下のような実装を行いそうです。ここで判断ロジックにisHoneycombTablet() を使っている理由は、、前回のエントリ「1apkでAndroidタブレット向けとスマートフォン向けアプリを実現する方法 その2」を参照ください。

    private void saveNote() {
        // save/update the note
        ContentValues values = new ContentValues(2);
        values.put(NotesProvider.KEY_TITLE, mTitleText.getText().toString());
        values.put(NotesProvider.KEY_BODY, mBodyText.getText().toString());
        if (mCurrentNote != null) {
            getActivity().getContentResolver().update(mCurrentNote, values,
                    null, null);
        } else {
            getActivity().getContentResolver().insert(
                    NotesProvider.CONTENT_URI, values);
        }
        Toast.makeText(getActivity(), "Note Saved", Toast.LENGTH_SHORT).show();
        if( UIUtils.isHoneycombTablet(getActivity()) ){
                getActivity().finish();
        }
    }
FragmentとActivityの役割分担

この方法でも実現は可能ですが、Fragment の導入された目的に照らし合わせるとよろしくありません。

ここで今一度、ActivityとFragmentの関係について考えてみたいと思います。Fragmentが導入された経緯は、スマートフォンとタブレットという画面サイズが大幅に異なる2つの製品群に対して、いままでのActivityで画面を開発していくというスタイルのままではスマートフォン向けアプリとタブレット向けアプリに対してそれぞれの画面レイアウトをもったActivityを2つ開発していく必要があり、開発者にとっては大変な負担になっていくところでした。

そこで、honeycomb (Gingerbread 以下向けにも互換パッケージを提供)から Fragment という概念を導入し、画面を構成するActivityよりも概念的に小さく、ユーザに提供する機能単位で実装を行えるFragment の提供を開始しました。

Fragment はユーザに提供する機能を実装し、ActivityはFragmentの要素をいつ、どこに利用するかの管理や、Androidのお家芸であるIntentの処理を担当することによって、ソフトウェアのコンポーネント化を進めて、再利用性を向上し、ユーザに有用なアプリを提供していけるようになる仕組みです。

ということから考えると、上記のFragmentのなかでUtils.isHoneycombTablet()の判断ロジックをもって、Activityの画面遷移を操作していることは、Fragment が Activity の役割である画面遷移やIntentの処理を行っていることになり、本来の趣旨から外れてしまいます。

そのため、これらの処理は、Activity 側で実装を行い、Fragment 側はActivity の機能を呼び出す仕組みにします。

具体的なやり方としては、Fragment内でコールバックメソッドを定義し、Activityがそのコールバックメソッドを必ず実装するように定義することによって実現します。

この仕組みについては、Creating event callbacks to the activity(英語)ソフトウェア技術ドキュメントを勝手に翻訳:アクティビティとのやり取り:アクティビティへのイベントコールバックの作成に紹介があります。

NoteEditFragment.java に追加するコールバックメソッドは以下のようになります。

public class NoteEditFragment extends Fragment {

    public interface OnNoteSavedListener {
        public void onNoteSaved();
    }

これを Activity で implements して実装します。注意点としては、Activityに対して実装を強制するため NoteEditFragment 内でこのコールバックメソッドが実装されていなければ、ClassCastException を発行して実行時例外が発生し、実行できないようにしています。

上記の実行時例外の発行は、以下のようなコードで実現しています。

public class NoteEditFragment extends Fragment {
...
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnNoteSavedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnNoteSavedListener");
        }

    }

NoteEditFragment にコールバックインタフェースに追加ができれば、以下のようにそのコールバックを呼び出すように変更します。

    private void saveNote() {
        // save/update the note
        ContentValues values = new ContentValues(2);
        values.put(NotesProvider.KEY_TITLE, mTitleText.getText().toString());
        values.put(NotesProvider.KEY_BODY, mBodyText.getText().toString());
        if (mCurrentNote != null) {
            getActivity().getContentResolver().update(mCurrentNote, values,
                    null, null);
        } else {
            getActivity().getContentResolver().insert(
                    NotesProvider.CONTENT_URI, values);
        }
        Toast.makeText(getActivity(), "Note Saved", Toast.LENGTH_SHORT).show();
        // イベントをホストのアクティビティに送信する
        mListener.onNoteSaved();
    }

NoteEditFragment でコールバックの実装を強制することによって、それを利用する2つのActivityで implements する必要がでてきました。

NoteEditActivity では、本来やりたかった保存時にActivityを終了して、ノートリスト画面に戻るという機能を実現したいため、以下のように onNoteSaved() 内で Activity を終了する finish() を実行します。

NotepadActivity では、現状通り特になにもすることはないので、以下のように空のまま実装します。

public class NotepadActivity extends FragmentActivity implements
        NoteListEventsCallback, NoteEditFragment.OnNoteSavedListener {

    @Override
    public void onNoteSaved() {
        // do nothing
    }

これでスマートフォンとタブレットの両方でノート編集画面でノートを追加することができるようになりました。

次は、スマートフォンの起動画面のノートリスト画面ですでに作成済みのノートをクリックした場合に追加編集できる機能を実現します。

現状では、ノートリストを選択した場合、NoteListFragment の onListItemClick() メソッドが呼び出されます。

public class NoteListFragment extends ListFragment {
...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);
        Uri noteUri = ContentUris.withAppendedId(NotesProvider.CONTENT_URI, id);
        mContainerCallback.onNoteSelected(noteUri);
    }

onListItemClick() メソッドの中でさきほどもあったような mContainerCallback.onNoteSelected(noteUri); のようにコールバックインタフェースを実行しています。このコールバックは以下のように定義されています。

public class NoteListFragment extends ListFragment {

    public interface NoteListEventsCallback {
        public void onNoteSelected(Uri noteUri);

        public void onNoteDeleted();
    }

このコールバックインターフェースは誰が実装しているかを調べてみると、NoteListFragment を利用している Activity で実装していることが以下からわかります。

public class NoteListFragment extends ListFragment {
...
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            // check that the containing activity implements our callback
            mContainerCallback = (NoteListEventsCallback) activity;
        } catch (ClassCastException e) {
            activity.finish();
            throw new ClassCastException(activity.toString()
                    + " must implement NoteSelectedCallback");
        }
    }

NoteListFragment を利用しているActivityは、NotepadActivity1つのため、こちらを見てみます。

public class NotepadActivity extends FragmentActivity implements
        NoteListEventsCallback, NoteEditFragment.OnNoteSavedListener {
...
    @Override
    public void onNoteSelected(Uri noteUri) {
            showNote(noteUri);
    }

    private void showNote(final Uri noteUri) {
        // check if the NoteEditFragment has been added
        FragmentManager fm = getFragmentManager();
        NoteEditFragment edit = (NoteEditFragment) fm.findFragmentByTag("Edit");
        if (edit == null) {
            // add the NoteEditFragment to the container
            FragmentTransaction ft = fm.beginTransaction();
            edit = new NoteEditFragment();
            ft.add(R.id.note_detail_container, edit, "Edit");
            ft.commit();
        } else if (noteUri == null) {
            edit.clear();
        }

        if (noteUri != null) {
            edit.loadNote(noteUri);
        }
    }

NoteListFragment のリスト項目のクリックから onNoteSelected() コールバックインタフェースが実行されて、showNote(noteUri);が実行されます。

この中で FragmentManager を利用して fragment の管理を行っています。この処理は、2ペイン構成の場合の処理がモロに記載されているため、スマートフォン向けには不必要な処理です。

そのため、onNoteSelected() コールバックインタフェース内でスマートフォンかタブレット判断ロジックを利用して、処理を分岐させます。分岐させたスマートフォン側の処理では、ノート編集画面を起動するように実装します。

public class NotepadActivity extends FragmentActivity implements
        NoteListEventsCallback, NoteEditFragment.OnNoteSavedListener {
...
    @Override
    public void onNoteSelected(Uri noteUri) {
        if (UIUtils.isHoneycombTablet(this)) {
            showNote(noteUri);
        } else {
            showNotePhone(noteUri);
        }
    }

    // for phone api
    private void showNotePhone(final Uri noteUri) {
        Intent i = new Intent(this, NoteEditActivity.class);
        i.setData(noteUri);
        startActivity(i);
    }

これでスマートフォンのノートリスト画面からノート編集画面に uri を渡して起動することができました。動作確認を行ってみると、どのノートリストを選択しても、TitleとBodyは表示されずに新規作成扱いになってしまいます。

これはまだNoteEditActivity 側で受信した Intent の処理を行っていないためです。

Activity と Fragment の役割分担の上記で話をしましたが、今回のようなバグを追うときにも Intent の処理の場合は、基本的に Activity が担当するため、呼び出す前のActivityが正しく送っていることが確認できていれば、デバッグを呼び出した後のActivityに注力することができます。

NoteEditActivity の onCreate() 内で Intent の受信を行っていないことが原因と判明したため、以下のようなロジックを onCreate() 内に追加します。

追加後のNoteEditActivity は以下のようになります。

public class NoteEditActivity extends FragmentActivity implements
        NoteEditFragment.OnNoteSavedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.note_edit_phone);
        Intent intent = getIntent();
        Uri uri = intent.getData();

        FragmentManager fm = getSupportFragmentManager();
        NoteEditFragment edit = new NoteEditFragment();
        edit.loadNote(uri);
        // add the NoteEditFragment to the container
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.note_detail_container, edit, "Edit");
        ft.commit();

    }

    @Override
    public void onNoteSaved() {
        finish();
    }

}

おわりに

これで目的の1apkでタブレット向けとスマートフォン向けのアプリを作成することができました。今回のコードラボのアプリがActivityベースのスマートフォン向けアプリをスマートフォン向け構成を維持しないままにタブレット向けアプリにするというものでした。そして Fragment 実装に置き換えたうえで2ペイン対応のタブレット向けアプリにするというものでした。それをあとから、1apkでスマートフォンとタブレットの両対応アプリにするという一直線ではなく、横道にそれて一度喫茶店で休憩したあとに戻りながら目的地に歩いていくというやり方でした。

本来なら、Activity ベースのスマートフォン向けアプリがベースになるなら、スマートフォン向けアプリのままで Fragment 対応を実施して、スマートフォン向け動作を確保しながら、徐々にタブレット向けアプリを開発していくというスタイルがよいと思います。

今回作ったアプリは eclipse のプロジェクト形式で整理したうえ(まだぐちゃぐちゃなので)で公開したいと思います。もし急ぎでほしいという方は(そんな人いないと思いますが)ご連絡頂ければ先に展開させて頂きます。

「Android Layout Cookbook アプリの価値を高める開発テクニック」の作者の方もY.A.M の 雑記帳:Android Developer Lab Tokyo 2011 のノートパッドをカスタマイズしてみた。(こちらのほうが相当レベルは上ですが。。。大変勉強になります。)でカスタマイズされています。

Fragment についてはまだ日本でAndroidタブレットが普及していない事情もあり、利用されているアプリは少ないと思います。Fragmentは、画面を持たないものも開発可能ということでタブレットアプリとスマートフォンアプリの両方で利用できる汎用ライブラリ的なものを作るプラットフォームにもなれるのではないかと考えています。ADL2011で google の android developer Advocate の方が次期バージョンの IceCreamSandwich(ICS) でも Fragment は推進していくのでどんどん Fragment を利用すいてアプリを開発してくださいとおっしゃっていました。そういうことを踏まえると、今後Androidアプリを開発していくにあたって Fragment は避けて通れないものになっていく予感がしますし、使いこなせるようになれば大変強い武器になっていくのではないかと考えています。

今後の色々と調査結果を紹介していければと思っています。Fragment使ってAndroidアプリを作っていきましょう!!

1apkでAndroidタブレット向けとスマートフォン向けアプリを実現する方法」で紹介した1apkにするためにタブレットとスマートフォンの判断ロジックを準備する必要があると紹介しました。前回紹介した判断ロジックがなぜそのような実装になっているかを紹介します。

public static boolean isHoneycomb() {
    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
}

public static boolean isHoneycombTablet(Context context) {
    return isHoneycomb() && (context.getResources().getConfiguration().screenLayout
            & Configuration.SCREENLAYOUT_SIZE_MASK)
            == Configuration.SCREENLAYOUT_SIZE_XLARGE;
}

上記の isHoneycomb()と isHoneycombTablet() の判断ロジックを解説するために、以下の表で端末の対応APIバージョンと端末のインチサイズの関係を整理しました。(1)から(6)についてそれぞれ紹介します。世の中に対応端末が多い順番に紹介します。

端末インチサイズ xlarge(Extra large)
7~10インチ
xlarge以外(large,normal,small)
2~7インチ
端末説明 タブレット画面 スマートフォン画面
コードネーム APIレベル
GigerBread(GB)(2.3.4)系とそれ以前 10 以下 (1) 現在存在しないが、今後発売される可能性がある (2) 多数存在する
Honeycomb(HC)(3.0)系 11 以上 (3) いくつか存在する (4) 存在しない
IceCreamSandwich(ICS)(バージョン番号未定)系 ?? 以上 (5) 発売予定 (6) 発売予定

xlargeなどの詳細な説明は、公式ドキュメント(英語)のSupporting Multiple Screensの「Range of screens supported」を読んでみてください。ソフトウェア技術ドキュメントを勝手に翻訳(2. 多様な画面のサポート)に日本語訳がありますのでこちらもお役に立つと思います。

(2)は、現在発売されているAndroid端末のほとんどが該当するものでいまAndroidマーケットにあるアプリのこの端末サイズに対応しているものがほとんどです。

(3)は最近発売されたAndroidタブレット端末で、xoom や Galaxy tab 10.1 や Optimus pad が該当します。

(4)は、該当する端末が今も将来も発売されません。理由はグーグルが honeycomb 3.0 系はタブレット向けのみでスマートフォン向けのリリースは実施しない、スマートフォン向けの次期OSは、IceCreamSandwich(ICS)(バージョン番号未定)で対応するという発表を行っているためです。

(1)は、現在のGBの最新バージョンが2.3.4ですが、GBも今後アップデートされる可能性があり、端末メーカーから発売される可能性があるためアプリ開発者としては対応しておく必要があります。(発売後に対応してもよいですが、事前に準備できることはしておきましょう)

(5),(6)については、まだ発表もされていませんが、現段階でわかっていることは、APIバージョンは11以上の数字が割り当てられますし、タブレット向けとスマートフォン向けのインチサイズの両方に対応されるということです。

では、 isHoneycomb()と isHoneycombTablet() の判断ロジックがどれに対応しているかというと、以下のようになります。

isHoneycomb()は、(3),(5),(6)を判断するために利用します。

isHoneycombTablet() は、(3),(5)を判断するために利用します。

isHoneycombTablet() の中で isHoneycomb() と実行端末のスクリーンサイズの取得、比較を行っています。これによって、将来(6)の端末が出てきたときに isHoneycomb() だけの判断ロジックではタブレット向けの画面レイアウトになってしまい、3インチのスマートフォン向けで2画面構成のアプリになり大変使いづらくなってしまいます。それをさけるために、 isHoneycomb()だけでなく、実行端末のスクリーンサイズを参照して、適切は画面レイアウトをユーザに提供する必要があります。

もちろん、開発スピードを上げるために、いま出ている端末だけの対応に注力して、 isHoneycomb()のみを利用することもよいと思います。
必ず上記のロジックを使わないといけないということではなく、きちんと開発するアプリの目的や対象端末、対象期間、対象とするユーザに応じて、適切な判断ロジックを利用して、1apkでタブレット向けとスマートフォン向けのアプリを開発していく必要があるということです。

「Android デベロッパーラボ 東京 2011」に参加して、Androidタブレット向けアプリ開発方法を聞いてきました。その1その2で内容を紹介しています。

このイベントの codelab ではスマートフォン向けアプリをタブレット向けアプリに変更する実践を行いました。さらにそれを発展させて、1apkでタブレット向けとスマートフォン向けアプリに対応させる方法を検討してみました。
codelab でスマートフォン向けアプリをタブレット向けアプリに変更して、以下のようなクラス図になりました。
adl2011jpのstep7時点のクラス図

1apkで両対応させるために、以下の方針でクラス設計を行います。
タブレット向けアプリ: 1 Acrivity に 複数の Fragment を割り当てる(変更なし)
スマートフォン向けアプリ: 1 Activity に 1つの Fragment を割り当てる

更新したクラス図は以下のようになる予定です。タブレット向けとスマートフォン向けのActivity が明確になるように、パッケージをそれぞれ tablet と phone を追加して、そのパッケージ内に Activity をいれました。緑色のところが今回のために追加・変更した部分です。Fragment を実装した各画面パーツはそのまま上位のパッケージのままとしました。

ここで1つ重大な課題が発生しました。1apkのため、アプリ起動時に起動した端末がタブレットかスマートフォンかを判定するロジックが必要になりました。
これについては、

Google IO 2011 のスケジュールアプリ(http://code.google.com/p/iosched/)の中で Honeycomb 3.1かつ大画面タブレット と それ以外(大半がSDK Version 10(GingerBread 2.3.3)以内でしょうが)の判定ロジックがあり、参考になります。今回はこのあり方を採用させて頂くことになると思います。

年末に出るといわれている Ice Cream Sandwich (ICS)(バージョン番号不明)ではタブレットとスマートフォンの両方に標準対応するそうで、このあたりの判定ロジックも提供されると思うので、それまではこのやり方が利用できそうです。

public static boolean isHoneycomb() {
    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
}

public static boolean isHoneycombTablet(Context context) {
    return isHoneycomb() && (context.getResources().getConfiguration().screenLayout
            & Configuration.SCREENLAYOUT_SIZE_MASK)
            == Configuration.SCREENLAYOUT_SIZE_XLARGE;
}

クラス設計を行ったクラスの具体的な実装はまた後日紹介する予定です。

以上

「Android デベロッパーラボ 東京 2011」に参加して、Androidタブレット向けアプリ開発方法を聞いてきました。その1では、codelab で実際に利用した資料を使って、スマートフォン向けアプリをタブレット向けアプリに変更する実践を行いました。

クラス図で全体像を把握しよう

資料だけでは全体像がわかりづらいため、トレーニングの全体像を把握するために変更前のスマートフォンアプリのクラス図と変更後のタブレット向けのクラス図を書きました。これをみてわかるように、既存のActivityはFragmentを継承したクラスに変更され、新しくActivity(NotepadActivity)(android-support-v4.jarを利用する場合はFragmentActivityを継承すること)を作成しています。

スマートフォン向けに作っていた Activity はタブレット向けアプリでは Fragment に相当していて、ListActivity は ListFragment に相当しています。
ContentProvider 関連は変更がないため、スマートフォン向けアプリで利用していたものがそのまま再利用できるということがわかります。
いままでスマートフォン向けにアプリを開発していてDBを作成していた方には朗報ですね。

今回のトレーニングでは、スマートフォン向けアプリをタブレット向けアプリに変更するというものでしたが、1つのアプリでスマートフォン向けとタブレット向けに両対応することも可能なようです。

Google IO 2011 のスケジュールアプリ(http://code.google.com/p/iosched/)はそのような作りになっています。このアプリは嬉しいことにオープンソースになっているので中身を見ることができます。
スマートフォン向けとタブレット向けの両対応しているアプリとして大変参考になります。

いきなりソースコードから見始めると全体像がつかめずわかりづらいと思います。幸運なことに全体像をつかむのに有効な資料を公開してくれている方がおられます。Iosched読書会向け資料(途中で力つきられたようで途中までの分析のようですが)で全体像を掴んでからソースコードを見ていくとよいと思います。

変更前と変更後のクラス図

■(変更前)スマートフォン向けノートパッドサンプルアプリのクラス図
adl2011jp STEP1時点のクラス図

■(変更後)タブレット向けノートパッドサンプルアプリのクラス図
adl2011jpのstep7時点のクラス図

Fragmentについては以下のサイトが役に立ちます。

「Android デベロッパーラボ 東京 2011」に参加して、Androidタブレット向けアプリ開発方法を聞いてきました。

ハッシュタグは
#adl2011jp
です。

Togetterは
#adl11jp Android Developer Lab 2011 7/2-3
です。

以下のソースコードをベースに既存のスマートフォン向けのアプリ(Activityベース)をタブレット向けに変更していきます。
コードラボ向けソースコード
をダウンロードしてください。

手順はHoneypad Codelab Steps — Tokyo 2011に書かれていますが自分の復習のために以下に記載しておきます。
以下の情報だけでは今回のトレーニングの全体像がつかみづらく、いま何をしているのかがわかりにくいため、Androidタブレット向けアプリ開発方法その2に全体像がわかるように変更前と変更後のクラス図を記載しておきました。参考にしてください。

Honeypad コードラボ 東京2011 目次

Step 1. ターゲットをHoneycombにしよう

Step1のゴール

  • プロジェクトのビルドターゲットをAPI level 11に変更する
  • minSdkVersionを API level 11 として明記する
  • Set the android “Theme.Holo.Light” theme on the application
  • アプリケーションのテーマを “Theme.Holo.Light”に設定する
  • おまけ(余裕があれば):ハードウェアアクセラレーションをオンにする

実施手順

  • AndroidManifest.xml の タグのコメントを外す
    <uses-sdk android:minSdkVersion="11" />
    <activity android:name=".Notepad"
              android:label="@string/app_name"
              android:theme="@android:style/Theme.Holo.Light"
              android:hardwareAccelerated="true">
    

Step 2. アクションバーを利用しよう

Step2のゴール

  • アクションとテキストを’Add Note’ オプションメニューを表示する
  • 適切なアイコンを追加する

実施手順

  • Notepad.java から古いonCreateOptionsMenuを削除して、新しいonCreateOptionsMenuのコメントを削除する
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
            super.onCreateOptionsMenu(menu);
            MenuItem add = menu.add(0, MENU_ADD_ID, 0, R.string.menu_add);
            add.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
                            | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
            add.setIcon(R.drawable.ic_menu_add);
            return true;
    }
    
  • おまけ(余裕があれば):メニューをXMLで定義する

Step 3. Fragments(Honeycomb 3.0からの新機能)を利用して新しいActivityを作成しよう

Step3のゴール

  • NotepadActivityを作成する
  • プロジェクトに入っているレイアウトファイルの notepad.xml を利用する
  • NotepadActivityの onCreate メソッドをオーバーライドする
  • notepad.xml を content view にセットする

実施手順

  • NotepadActivity.java の中で、クラス定義と onCreate() メソッドのコメントを外す。
  • (注)onCreate() メソッド以外のメソッドのコメントは外さないでください!!
    public class NotepadActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.notepad);
        }
    }
    

Step 4. Fragment対応するために NoteEdit クラスを変更しよう

概要

  • Fragmentクラスを継承する
  • 空のコンストラクタを追加する
  • onCreateView()を実装する
  • 目的のレイアウトをインフレートして、それを返す
       View v = inflater.inflate(R.layout.note_edit, container, false);
    
  • これでsetContentViewを呼ぶ必要はない
  • onCreateView()に初期化ロジックを移動して、onCreate()メソッドを削除する
  • 1つのスクリーン向けに setTitle() を行う必要はないでしょう
  • findViewById() メソッドコールを実施して、view を利用する
  • Fragmentは、Context を拡張するものではありません。例えば、Content Resolver にアクセスするために必要な場合など。
  • Context にアクセスする必要な場合は、上位(親の)Activityから取得する
  • NoteEdit スクリーンを閉じるためにコードを削除する
  • intent で起動されるのではなく、起動intent によって特定された Data Uri の蓄積も削除する
  • NoteEditをNoteEditFragment に名前変更する

実施事項

  • NoteEdit.javaの中で、loadNote()とclear()メソッド以外のすべてのコメントを外す
  • NoteEdit を NoteEditFragment に名前変更する
  • おまけ(余裕があれば):タブレット向けの fragment を widgets に最適化する
  • テキスト書体とパディングを適切に設定する
  • widgetにバックグラウンドボーダーを設定する

Step 5. Fragment対応するために Notepad クラスを変更しよう

概要

  • ListFragment を継承する
  • 空のコンストラクタを追加する
  • viewが作成された後の初期化場所を変更する
  • onActivityCreated() メソッドをオーバーライドする
  • onCreate()メソッドから初期化ロジックを移動する
  • onCreate()メソッドを削除する
      @Override
      public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            fillData();
            registerForContextMenu(getListView());
      }
    
  • Fragment は Context を継承していません。そのため、Content Resolver を得るためには、上位(親の)Activityから取得する必要あり。
  • オプションメニューのメソッド(onCreateOptionsMenu() & onMenuItemSelected() ) を NotepadActivity クラスに移動する
  • メニューのアイテムIDも移動すること
  • createNote()メソッドコールを削除する
  • createNote() メソッドを削除する
  • Notepad クラスを NoteListFragment クラスに名前変更する

実施事項

  • Notepad.javaの中で、NoteListEventsCallback インタフェース, mContainerCallback フィールド, onAttach() メソッド以外のコードのほとんどのコメントを外す
  • NoteListFragment クラスに名前変更する
  • createNote()メソッドを削除する
  • おまけ(余裕があれば):タブレット向けの notes_row エントリに最適化する
  • ‘activated’ として選択済み row を設定する

Step 6. 新しいActivityのために Manifest を更新しよう

step6のゴール

  • NotepadActivityをメインアクティビティに変更する
  • NoteEditFragment のActivity 継承を削除する

実施事項

  • AndroidManifest.xml の中で、2つめのタグのコメントを外して、NoteEditFragment 向けのタグを削除する
    
    

Step 7. Activityと Fragments をつなげよう。パート1

設計上の留意点

  • 追加した大半の部品を接続するための実装(コンポーネントを接続するためのコード)
  • 次のようなUIイベントのルーティングを実施: Fragment (NoteList) –> Activity –> Fragment (NoteEdit).
  • このルーティングを実装するために、NotepadActivity にインタフェース定義を実装を追加
  • もう1つ(概要より少ない)の方法は NoteList を得るためにNoteEditに直接アクセスする方法。しかし、それでは密結合である。
  • コンポーネントを疎結合にするために、NotepadActivity がここでルーターの役割を担う。
  • Step7のPart1では、NoteListからNotepadActivityへの接続を実装する
  • Step8のPart2では、NotepadActivityからNoteEditへの接続を実装する

概要

  • リスト上のNoteの選択と削除のイベント処理のための(NoteListEventsCallbackを呼ぶために)コールバックインタフェースを定義する
    public interface NoteListEventsCallback {
            public void onNoteSelected(Uri noteUri);
            public void onNoteDeleted();
    }
    
  • NotepadActivityの中でインタフェースを実装する
  • NoteListFragment は含まれているアクティビティに接続されるときに、インタフェース実装と参照補助を確認する
  • この参照のコールバックを実行可能にする
    @Override
    public void onAttach(Activity activity) {
            super.onAttach(activity);
            try {
                    // check that the containing activity implements our callback
                    mContainerCallback = (NoteListEventsCallback) activity;
            } catch (ClassCastException e) {
                    activity.finish();
                    throw new ClassCastException(activity.toString()
                                    + " must implement NoteSelectedCallback");
            }
    }
    
  • Call the relevant callback when a note is selected from the list
  • リストからノートが選択されたときに、関連するコールバックを呼び出す
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
            super.onListItemClick(l, v, position, id);
            Uri noteUri = ContentUris.withAppendedId(NotesProvider.CONTENT_URI, id);
            mContainerCallback.onNoteSelected(noteUri);
    }
    
  • リストからノートが削除されたときに、関連するコールバックを呼び出す
    @Override
    public boolean onContextItemSelected(MenuItem item) {
            switch (item.getItemId()) {
            case DELETE_ID:
                    AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
                                    .getMenuInfo();
                    getActivity().getContentResolver().delete(
                                    ContentUris.withAppendedId(NotesProvider.CONTENT_URI,
                                                    info.id), null, null);
                    fillData();
                    mContainerCallback.onNoteDeleted();
                    return true;
            }
            return super.onContextItemSelected(item);
    }
    

実施事項

  • NoteListFragment.java の中で、インタフェース定義をコメントを外す
  • NoteListFragment.java の中で、onAttach() メソッドとmContainerCallback フィールドのコメントを外す

Step 8. Activityと Fragments をつなげよう。パート2

概要

  • 与えられたUriでノートが開くように NoteEditFragment にメソッドを追加する
  • 関連するメンバの値を Uri に保持する
  • フィールドの値を代入する
  • もし fragmentが初期化されたときにこのメソッドを呼びたい場合のみ、onCreateView()メソッドの中で状態の初期化を行う前にコールされないようにガードしてください。
    protected void loadNote(Uri noteUri) {
            mCurrentNote = noteUri;
            if (isAdded()) {
                    populateFields();
            }
    }
    
  • NoteEditFragment にテキストフィールドをクリアするためのメソッドを追加する
    protected void clear() {
            mTitleText.setText(null);
            mBodyText.setText(null);
            mCurrentNote = null;
    }
    
  • NotepadActivityの中で、リストからノートが選択されたときにノートの詳細を表示するためにNoteEditFragmentを呼び出す。
  • もし NoteEditFragment がアクティビティに追加されていれば、FragmentManager をチェックに利用する
  • ヒント:与えられたタグでFragmentをチェックする
  • もし、Fragment が見つけられなければ、コンテナーに追加を行う
  • タグ上の note_detail_container に NoteEditFragmentの新しいインスタンスを追加するためにFragmentTransaction を利用する
  • 選択したノートを表示するために NoteEditFragment の新しいメソッドを呼び出す
  • もしユーザが’Add Note’をクリックした場合は、edit fragment をクリアする
    @Override
    public boolean onMenuItemSelected(int featureId, MenuItem item) {
            switch (item.getItemId()) {
            case MENU_ADD_ID:
                    showNote(null);
                    return true;
            }
            return super.onMenuItemSelected(featureId, item);
    }
    
    private void showNote(final Uri noteUri) {
            // check if the NoteEditFragment has been added
            FragmentManager fm = getFragmentManager();
            NoteEditFragment edit = (NoteEditFragment) fm
                                    .findFragmentByTag("Edit");
            if (edit == null) {
                    // add the NoteEditFragment to the container
                    FragmentTransaction ft = fm.beginTransaction();
                    edit = new NoteEditFragment();
                    ft.add(R.id.note_detail_container, edit, "Edit");
                    ft.commit();
            } else if (noteUri == null) {
                    edit.clear();
            }
    
            if (noteUri != null) {
                    edit.loadNote(noteUri);
            }
    }
    
  • もし、ユーザがノートを削除する場合、NoteEditFragment を取り除く。
    public void onNoteDeleted() {
            // remove the NoteEditFragment after a deletion
            FragmentManager fm = getFragmentManager();
            NoteEditFragment edit = (NoteEditFragment) fm
                            .findFragmentByTag("Edit");
            if (edit != null) {
                    FragmentTransaction ft = fm.beginTransaction();
                    ft.remove(edit);
                    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                    ft.commit();
            }
    }
    
  • おまけ(余裕があれば):いくつかの画面では UI スレッド上でディスクアクセスを実行している。
  • StrictMode上でこれらの場所を発見してみてください。
  • そして、これらを修正してください。

Step 9. 縦画面レイアウトに対応しよう

Step9のゴール

  • 縦画面用のレイアウト向けのフォルダを作成する
  • すでに存在する notepad.xml レイアウトをコピーして、縦画面になったときにリストが詳細画面の上に配置されるように修正する
  • widthとheightを適切な値に設定する

実施事項

  • コメントアウトされていないところの場合、layout-port/notepad.xml の中で記載されているセクションで vertial(portrait)版に変更する

最終のクラス図とレイアウト図

以上、これであなたも honeycomb マスター!!