WebAPIなどから取得したXMLを解析(パース)して、内容を取り出すアプローチは、大きく分けてDOMとSAXがあります。 DOMはXMLをツリー上に展開し、自由に読み書きできるものの、比較的多くのメモリを消費します。SAXはXMLを先頭から読み込んで処理していくため、高速でメモリ消費も少ないのですが、複雑な構造のXMLを読み込む場合に制御が複雑になるのと、その性質上汎用的に作るのが難しいため、ケースバイケースで使い分けることになるかと思います。
一方、SQLiteについては強力で使いやすく、Web上にもコードを含む資料がたくさんあるものの、android ver1.0に至るまでの過程でAPIが変化してしまったため、今では動かないコードも散見されます。
ここでは、前のチュートリアルに引き続き、org.w3c.domパッケージ(?)を使ってXMLをパースしているRestfulClientクラスと、アプリケーションの設定値保持などに使用可能な、SQLiteを使った永続化機能付きのHashとしてのSettingsクラスを使って、Twitterクライアントを作ってみます。
Twitterクライアント用のプロジェクトを作成します。
<?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>
<?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" />
Settingsクラスについては
また、XMLのパースについては
実際にコーディングしてみます。
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(); } } }