Android XML 解析(Mysql非同期接続) データ取得

objective-c での MySQL への接続 及び XML パース方法は検索すれば多数でてきましたが、Android版はあまり出てこなかったので、自分の備忘録としてここに残すことにします。

まずは、下のコードを実行した結果です。

通信で、取得されてくるXMLのサンプルです。

<?xml version="1.0" encoding="utf-8"?>
<data>
<no>1</no>
<name>佐藤</name>
<no>2</no>
<name>鈴木</name>
<no>3</no>
<name>高橋</name>
<no>4</no>
<name>田中</name>
<no>5</no>
<name>伊藤</name>
</data>

基本的にな、流れはobjective-cと変わりません。

やんだーのスマホアプリ開発メモ 様の記事「http://yandr.randy.boy.jp/?eid=18
上記記事の、objective-c 版の流れに似たコーディングをしています。

①プロジェクトを作ります

②取得した情報を表示するレイアウト(ウィジェット)を作ります

activity_main.xml の編集

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.xmlparsertest.MainActivity$PlaceholderFragment" >

    <!-- 
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    -->

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />

</RelativeLayout>

★HelloWorldを消して、新しくTextViewを追加します

③インターネットへの接続を許可する
AndroidManifest.xml の編集

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.xmlparsertest"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.xmlparsertest.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

★11行目の <uses-permission android:name=”android.permission.INTERNET”/> を追加します。

④メインアクティビティ
MainActivity.java の編集
★コピペできるように、まずはすべてのコードを。下で。。。

package com.example.xmlparsertest;

import java.io.InputStream;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;

import android.support.v7.app.ActionBarActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends ActionBarActivity {

	private String[] rank = new String[10];
	private String[] name = new String[10];

	private String sSrc;
	private int xml_index;

	TextView textView1;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		textView1 = (TextView) this.findViewById(R.id.textView1);
		getXml();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		int id = item.getItemId();
		if (id == R.id.action_settings) {
			return true;
		}
		return super.onOptionsItemSelected(item);
	}

	/** 非同期での通信処理 **/
	private void getXml() {
		AsyncTask<String, Integer, Short> task = new AsyncTask<String, Integer, Short>() {
			@Override
			protected Short doInBackground(String...value) {
				// データを取得する
				HttpURLConnection http = null;
				InputStream in = null;

				try {
					// HTTP接続
					URL url = new URL("http://192.168.1.100/firstnameranking.xml");
					http = (HttpURLConnection) url.openConnection();
					http.setRequestMethod("GET");
					http.connect();

					// データを取得
					in = http.getInputStream();

					// ソースを読み出す
					sSrc = new String();
					byte[] line = new byte[1024];
					int size;
					while (true) {
						size = in.read(line);
						if (size <= 0) {
							break;
						}
						sSrc += new String(line);
					}
				} catch (Exception e) {
					return 1;
				} finally {
					try {
						if (http != null){
							http.disconnect();
						}
						if (in != null){
							in.close();
						}
					} catch (Exception e) {
						return 2;
					}
				}

				// パースデータ格納用配列初期化
				rank = new String[5];
				name = new String[5];
				xml_index = 0;

				// XML解析
				try{
					XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
					factory.setNamespaceAware(true);
					XmlPullParser xpp = factory.newPullParser();

					xpp.setInput(new StringReader(sSrc));
					int eventType = xpp.getEventType();
					while (eventType != XmlPullParser.END_DOCUMENT) {
						if(eventType == XmlPullParser.START_TAG && xpp.getName().equals("no")) {
							// ランキング(順位)
							eventType = xpp.next();
							rank[xml_index] = xpp.getText();
						}else if(eventType == XmlPullParser.START_TAG && xpp.getName().equals("name")) {
							// 名前
							eventType = xpp.next();
							name[xml_index] = xpp.getText();
							xml_index++;
						}
						// 次へ
						eventType = xpp.next();
					}
				}catch (Exception e){
					return 3;
				}
				return 0;
			}

			@Override
			protected void onPostExecute(Short iRet) {
				if (iRet == 0) {
					// 成功
					textView1.setText("名字ランキング\n");
					for(int i = 0; i < rank.length; i++){
						textView1.setText(textView1.getText() + "No." + rank[i] + " >> 名前:" + name[i] + "\n");
					}
					showToast("成功");
				} else {
					// エラー
					showToast("エラーコード:" + iRet);
				}
			}
		};
		task.execute();
	}

	// トースト表示処理
	public void showToast(String text) {
		Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
	}

}

1) 変数の宣言

	private String[] rank = new String[10];
	private String[] name = new String[10];

	private String sSrc;
	private int xml_index;

	TextView textView1;

21-22行目 MySQLから取得した、データを格納する配列を用意します。
24行目 取得したXMLのソースの解析に利用します。
25行目 取得したデータを格納する配列の添字に利用します。
27行目 取得したデータを表示するエリアです。

2) onCreate処理

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		textView1 = (TextView) this.findViewById(R.id.textView1);
		getXml();
	}

34行目 テキストビューを編集するために必要
35行目 一連の処理を開始するメソッドを呼び出します。
(メソッド名がgetXmlと適切でない気がしますがここではスルーします。)

3) getXml処理
54行目から始まる、getXml処理の中にある、AsyncTask は非同期で処理を行います。
55行目から145行目で処理を記載し、実際にその処理は、146行目で実行されます。
55行目 task には、以下の処理が含まれています。
doInBackground ・・・ 非同期でおこなう処理
onPostExecute ・・・ 処理が終わったときの処理

それでは、実際にMySQLからデータを取得するロジックを説明します。

				// データを取得する
				HttpURLConnection http = null;
				InputStream in = null;

				try {
					// HTTP接続
					URL url = new URL("http://192.168.1.100/firstnameranking.xml");
					http = (HttpURLConnection) url.openConnection();
					http.setRequestMethod("GET");
					http.connect();

					// データを取得
					in = http.getInputStream();

					// ソースを読み出す
					sSrc = new String();
					byte[] line = new byte[1024];
					int size;
					while (true) {
						size = in.read(line);
						if (size <= 0) {
							break;
						}
						sSrc += new String(line);
					}
				} catch (Exception e) {
					return 1;
				} finally {
					try {
						if (http != null){
							http.disconnect();
						}
						if (in != null){
							in.close();
						}
					} catch (Exception e) {
						return 2;
					}
				}

59-60行目 データの取得に必要な変数を宣言しています。

64行目 取得したいURLをセットします。
★上記のサンプルでは、直接XMLファイルを読んでいますが、同じ結果が返ってくるphpファイルを読みに行けば動きは同じです。
下で、詳しく説明します。

65-82行目までは、呪文のように書いておけば動きますが興味のある方は、検索しすれば沢山でてきます。

84行目 主に通信時のエクセプションを検知します。
★実際に上記コードで作成した場合、ここで検知したエラーコード:1が発生した場合は、マニフェストへのインターネットアクセスの許可が無いかネットワークが不安定である可能性が高いです。

86-95行目 通信とソースを読みだした変数の後始末をしています。
94行目で検知したエラーコード:2が発生した場合は、通信の切断または書き込みしたファイルを閉じる処理に失敗しています。

				// パースデータ格納用配列初期化
				rank = new String[5];
				name = new String[5];
				xml_index = 0;

配列の初期化をします。ここで初期化することで、2回目・3回目も問題なく値がセットできます。

メインの解析処理です。

				// XML解析
				try{
					XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
					factory.setNamespaceAware(true);
					XmlPullParser xpp = factory.newPullParser();

					xpp.setInput(new StringReader(sSrc));
					int eventType = xpp.getEventType();
					while (eventType != XmlPullParser.END_DOCUMENT) {
						if(eventType == XmlPullParser.START_TAG && xpp.getName().equals("no")) {
							// ランキング(順位)
							eventType = xpp.next();
							rank[xml_index] = xpp.getText();
						}else if(eventType == XmlPullParser.START_TAG && xpp.getName().equals("name")) {
							// 名前
							eventType = xpp.next();
							name[xml_index] = xpp.getText();
							xml_index++;
						}
						// 次へ
						eventType = xpp.next();
					}
				}catch (Exception e){
					return 3;
				}
				return 0;

105-106行目 XML解析に必要とされるクラスの準備をします。
111-124行目 このループの中で取得したXMLを解析しています。
112行目 タグの種類がスタートタグでかつ、タグの名前が「no」であるかを判定します。
<no>1</no>
↑太字の部分を検索しています

114行目 欲しいタグだった場合は、xppを一つ送り(xpp.next())ます。
115行目 「1」の部分をの文字列(xpp.getText())を配列にセットします。

116行目 タグの種類がスタートタグでかつ、タグの名前が「name」であるかを判定します。
<name>佐藤</name>
↑太字の部分を検索しています

118-119行目 noと同じです。
120行目 必要としているデータだった場合は、配列の添字を一つ増やします。

今回のXMLでは、

--snip--
<no>1</no>
<name>佐藤</name>
--snip--

data は no と name でできており、name が最後のなので、nameのときにxml_indexを加算します。

126行目 解析中のエラーを検知します。ここでのエラーは、主に存在しない配列にアクセスしたときなどのエラーになります。

128行目 エラーがなく最後まで非同期の task を実行できた場合は、return で 0 を返します。
綺麗なやり方は、iRetなど変数を用意しエクセプションを拾ったとき、iRetにエラーコードをセットし、128ではiRetをリターンしたほうが綺麗ですが、ここではスルーします。

			@Override
			protected void onPostExecute(Short iRet) {
				if (iRet == 0) {
					// 成功
					textView1.setText("名字ランキング\n");
					for(int i = 0; i < rank.length; i++){
						textView1.setText(textView1.getText() + "No." + rank[i] + " >> 名前:" + name[i] + "\n");
					}
					showToast("成功");
				} else {
					// エラー
					showToast("エラーコード:" + iRet);
				}
			}

上のtaskという非同期の処理で return した値(サンプルではint)が引数として、onPostExecuteがコールされます。
よって、iRetが0だった場合は、エラーが無く非同期処理が実行されています。

135-138行目 解析で配列にセットしておいた値を画面に表示させます。
配列へのアクセスは、name[i]です。
サンプルでは、テキストビューに追記しています。\n は改行コードです。
139,142行目行目は、トーストで結果を表示します。

	// トースト表示処理
	public void showToast(String text) {
		Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
	}

トースト処理です。メソッドとしておけば、見た目を変えたくなった時など一度に変更できます。

ここまでが、Android での処理です。

ここからは、XMLの説明を少々。詳しくは、他サイトで判り易く説明が沢山ありますので探して下さいね。
パース部分の確認だけでしたら、最初に記載のある「通信で、取得されてくるXMLのサンプル」をそのままコピーし、テキストエディタでUTF-8で保存してそれにアクセスすれば問題ありません。

今回のサンプルXMLを作る最小限のPHP

<?php

$link = mysql_connect('localhost', 'user', 'pass');
$db_selected = mysql_select_db('firstnameranking', $link);

//SQL文発行
mysql_set_charset('utf8');

$result = mysql_query('select * from Trank');

header("Content-Type: text/xml; charset=utf-8");
echo '<?xml version="1.0" encoding="utf-8"?>
<data>';
while ($row = mysql_fetch_assoc($result)) {
    echo '<no>'.$row['no'].'</no>';
    echo '<name>'.$row['name'].'</name>';
}
echo '</data>';
$close_flag = mysql_close($link);

?>

DB情報は以下と仮定します。
データベース名:firstnameranking
場所:localhost
ユーザ:user
パスワード:pass
テーブル:Trank

Trank
no | name
———–
1 | 佐藤
2 | 鈴木
3 | 高橋
4 | 田中
5 | 伊藤

DBへの接続方法などは、他サイト様を参考にして下さい。

★今回参考にさせて頂いたサイト様★
http://developer.android.com/reference/org/xmlpull/v1/XmlPullParser.html
http://techbooster.org/android/application/1643/
http://miffysora.wikidot.com/android-xml
http://yandr.randy.boy.jp/?eid=18
SPECIAL THANKS!!

以上、頑張ってる途ちゅ~(笑

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です