[[勉強会]] * XMLとSQLiteを使用する [#mdc78c0d] - ※現在レビュー中です。内容は間違いを含んでいる可能性があります。 WebAPIなどから取得したXMLを解析(パース)して、内容を取り出すアプローチは、大きく分けてDOMとSAXがあります。 DOMはXMLをツリー上に展開し、自由に読み書きできるものの、比較的多くのメモリを消費します。SAXはXMLを先頭から読み込んで処理していくため、高速でメモリ消費も少ないのですが、複雑な構造のXMLを読み込む場合に制御が複雑になるのと、その性質上汎用的に作るのが難しいため、ケースバイケースで使い分けることになるかと思います。 一方、SQLiteについては強力で使いやすく、Web上にもコードを含む資料がたくさんあるものの、android ver1.0に至るまでの過程でAPIが変化してしまったため、今では動かないコードも散見されます。 ここでは、前のチュートリアルに引き続き、org.w3c.domパッケージ(?)を使ってXMLをパースしているRestfulClientクラスと、アプリケーションの設定値保持などに使用可能な、SQLiteを使った永続化機能付きのHashとしてのSettingsクラスを使って、Twitterクライアントを作ってみます。 - 確認中:XMLパーサはorg.w3c.domを使っているという表現で良いでしょうか? これってインターフェイスしか定義してませんよね? ** 手順1:公開されているandroid用のユーティリティのソースファイルをダウンロードし、Eclipseに登録する [#ib16b912] - [[Androidでorg.apache.http.clientを使用する>勉強会/HttpClient]]の手順1に従い、GitHubから最新版ユーティリティのソースファイルをダウンロードし、Eclipseにセットアップします。 - RestfulClientクラスにBASIC認証機能を付けたので、先のチュートリアルを試した方も、再度ダウンロードし直してください。 ** 手順2:プロジェクトを作成しましょう [#h9cf8a87] Twitterクライアント用のプロジェクトを作成します。 - "File -> New -> Project"を選択して、開いたダイアログで"Android Poroject"を選択して、"Next"ボタンを押します。 - 開いたダイアログで、以下のように入力してFinishボタンを押します。 -- Project Name: HelloXML -- Package name: net.it4myself.helloxml -- Activity Name: .HelloXMLActivity -- Application Name: ハローXML ** 手順3:マニフェストファイルを設定する [#j007d933] - このプロジェクトではインターネットにアクセスするため、パーミッションの使用を許可する必要があります。 - もうソース無くても大丈夫でしょう? ** 手順4:レイアウトを決定する [#x055f760] - このプロジェクトは2つのレイアウトファイルを使います。 - 画面のレイアウトを決定するために、res/layout/main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="User:" > </TextView> <EditText android:id="@+id/username_edit_id" android:layout_width="200px" android:layout_height="wrap_content" android:textSize="18sp" > </EditText> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Password:" > </TextView> <EditText android:id="@+id/password_edit_id" android:layout_width="200px" android:layout_height="wrap_content" android:textSize="18sp" > </EditText> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" > <Button android:id="@+id/publicTimeline_button_id" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="PublicTimeline" > </Button> <Button android:id="@+id/myTimeline_button_id" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="My Timeline" > </Button> </LinearLayout> <ListView android:id="@+id/android:list" android:layout_width="fill_parent" android:layout_height="wrap_content" > </ListView> </LinearLayout> - リストの各行のレイアウトのために、res/layout/list_row.xml <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" /> - main.xmlのListViewのidを"@+id/android:list" とするのは、黒魔術のおまじないかもしれません。(後に説明しますが、データを用意するだけで良きに計らってくれるので) ** 手順5:手順1で作ったプロジェクトをビルドパスに加える [#m02d1021] - [[Androidでorg.apache.http.clientを使用する>勉強会/HttpClient]]の手順5に従い、net.it4myself.utilパッケージをビルドパスに加えます。 ** 手順6:パブリックタイムラインとフレンドタイムラインを取得するため、Twitter APIを理解する [#n5752763] - ※筆者はTwitterにあまり詳しくないので、用語など間違えていたらごめんなさい。 - Twitter APIは英文らしいので、[[Twitter API 仕様書 日本語訳版:http://watcher.moe-nifty.com/memo/docs/twitterAPI.txt]]を使って仕様を確認しましょう。 - Twitterユーザ全体のタイムラインである、パブリックタイムラインをXMLで取得するにはhttp://twitter.com/statuses/public_timeline.xmlからXMLを読む必要があります。パブリックタイムラインは、BASIC認証を使う必要はありません。 - 自分がフォローしている友達だけのタイムラインである、フレンドタイムラインをXMLで取得するにはhttp://twitter.com/statuses/friends_timeline.xmlからXMLを読む必要があります。この際、BASIC認証を使って自分のTwitter用IDとパスワードを一緒に渡す必要があります。BASIC認証でID/パスワードを送信するため、httpよりは暗号化されたhttpsの方が好ましいかも知れません。RestfulClientクラス、TwitterAPIサーバ共にSSLに対応しているので、接続先スキーマをhttpsにするだけで使えます。 - 先にブラウザを使ってどんなXMLが返ってくるのか確認しておくのが吉です。 - BASIC認証用にRestfulClientクラスを改良しておいたので、確認してみてください。RestfulClient.basicAuthUsername = "YOUR ID";とRestfulClient.basicAuthPassword = "YOUR PASSWORD";でTwitterのID/パスワードを設定できます。RestfulClient.basicAuthUsername = "";とするとBASIC認証は使わなくなります。 ** 手順7:パブリックタイムラインとフレンドタイムラインを取得する [#s660e7d3] Settingsクラスについては - mSettings = new Settings(this, null);で生成 - String user = mSettings.get("user");でuserに関連づけてある値の取得 - mSettings.set("user", "hoge");でuserにhogeを設定 - 設定したタイミングでSQLiteに格納するので、途中でアプリケーションが終了しても、値は保持されます。 - これらを利用して、入力されたID/パスワードを保存しておき、次回起動時に復元させる。 また、XMLのパースについては - RestfulClient.Get(uri, null, mFactory.newDocumentBuilder());として、Documentとして結果を取得する - RestfulClient.RemoveEmptyNodes(result.getDocumentElement());を使って、改行だけのテキストノードを削除する。(削除しなくても構いませんが、削除した方が理解しやすいと思います) - Node.getChildNodes();で、現在のノードの子ノードリストを取得し、ループにかける - Node.getNodeName();で欲しいタグ名を探す - 欲しいタグを見つけたらNode.getFirstChild().getNodeValue();でテキストを取得する。getFirstChild()を入れるのは、テキストは現在のノードの子ノード(テキストノード)に格納されているため。 - これらを利用し、Twitterでの発言内容とユーザ名を取得し、ListViewを使って表示する。 - ※android ver1.0時点ではorg.w3c.domパッケージ(?)はバグが多いらしく、一部オプションが動いてくれなかったため、空テキストノード削除用にRemoveEmptyNodes()も用意しました。DOMオブジェクトを扱いたい場合はお使いください。 実際にコーディングしてみます。 package net.it4myself.helloxml; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import net.it4myself.helloxml.R; import net.it4myself.util.RestfulClient; import net.it4myself.util.Settings; import android.app.ListActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class helloXMLActivity extends ListActivity { private EditText mUsernameEdit; private EditText mPasswordEdit; private DocumentBuilderFactory mFactory; private Settings mSettings; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mUsernameEdit = (EditText)findViewById(R.id.username_edit_id); mPasswordEdit = (EditText)findViewById(R.id.password_edit_id); mFactory = DocumentBuilderFactory.newInstance(); mSettings = new Settings(this, null); String user = mSettings.get("user"); if(null != user){ mUsernameEdit.setText(user); } String pass = mSettings.get("pass"); if(null != pass){ mPasswordEdit.setText(pass); } Button b1 = (Button)findViewById(R.id.publicTimeline_button_id); b1.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { RestfulClient.basicAuthUsername = ""; RestfulClient.basicAuthPassword = ""; showTimeline("https://twitter.com/statuses/public_timeline.xml"); } }); Button b2 = (Button)findViewById(R.id.myTimeline_button_id); b2.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { String user = mUsernameEdit.getText().toString(); String pass = mPasswordEdit.getText().toString(); mSettings.set("user", user); mSettings.set("pass", pass); RestfulClient.basicAuthUsername = user; RestfulClient.basicAuthPassword = pass; showTimeline("https://twitter.com/statuses/friends_timeline.xml"); } }); } private void showTimeline(String uri){ try { Document result = RestfulClient.Get(uri, null, mFactory.newDocumentBuilder()); Node removedNode = RestfulClient.RemoveEmptyNodes(result.getDocumentElement()); NodeList statusNodes, l1Nodes, l2Nodes; Node statusNode, l1Node, l2Node; List<String> items = new ArrayList<String>(); StringBuilder sb; statusNodes = removedNode.getChildNodes(); int statusCounter = statusNodes.getLength(); for(int i=0; i < statusCounter; i++){ statusNode = statusNodes.item(i); sb = new StringBuilder(); l1Nodes = statusNode.getChildNodes(); int l1Counter = l1Nodes.getLength(); for(int ii=0; ii < l1Counter; ii++){ l1Node = l1Nodes.item(ii); String nodeName = l1Node.getNodeName(); if(nodeName.equals("text")){ Log.v("helloxml", "l1 text node value: " + l1Node.getFirstChild().getNodeValue()); sb.append(l1Node.getFirstChild().getNodeValue()).append(" by "); } else if(nodeName.equals("user")){ l2Nodes = l1Node.getChildNodes(); int l2Counter = l2Nodes.getLength(); for(int iii=0; iii < l2Counter; iii++){ l2Node = l2Nodes.item(iii); String userNodeName = l2Node.getNodeName(); if(userNodeName.equals("name")){ Log.v("helloxml", "l2 name node value: " + l2Node.getFirstChild().getNodeValue()); sb.append(l2Node.getFirstChild().getNodeValue()); break; } } } } items.add(sb.toString()); // Log.v("helloxml", "item base: " + sb.toString()); } ArrayAdapter<String> itemAdapter = new ArrayAdapter<String>(this, R.layout.list_row, items); setListAdapter(itemAdapter); // ここで渡したアダプタがListViewに表示されるまでが黒魔術っぽい } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, "パスワード合ってる?", Toast.LENGTH_SHORT).show(); } } } ** 手順7:SQLiteの使い方を追ってみる [#td67fd6b] - 力尽きたのでまた今度 ----- [[勉強会]]