2010年11月28日日曜日

Google Image Search API の android サンプル・アプリ作ってみた

Google Image Search API を使用した android アプリを作成してみました。

Google Image Search API は以下のサイトにあるように、q=xxx の部分で検索キーワードを指定します。
応答は JSON フォーマットです。


Google Image Search API



検索例: 高知城

リクエスト

レスポンス
{"responseData": {"results":[{"GsearchResultClass":"GimageSearch","width":"600","height":"400","imageId":"Ehuk98rEYswf5M:","tbWidth":"135","tbHeight":"90","unescapedUrl":"http://img01.kitaguni.tv/usr/akkesi1946/1%E9%AB%98%E7%9F%A5%E5%9F%8E.jpg","url":"http://img01.kitaguni.tv/usr/akkesi1946/1%25E9%25AB%2598%25E7%259F%25A5%25E5%259F%258E.jpg","visibleUrl":"akkesi1946.kitaguni.tv","title":"風のふくまま・・・:\u003cb\u003e高知城\u003c/b\u003e","titleNoFormatting":"風のふくまま・・・:高知城","originalContextUrl":"http://akkesi1946.kitaguni.tv/e543472.html","content":"\u003cb\u003e高知城\u003c/b\u003e","contentNoFormatting":"高知城","tbUrl":"http://images.google.com/images?q\u003dtbn:Ehuk98rEYswf5M::img01.kitaguni.tv/usr/akkesi1946/1%25E9%25AB%2598%25E7%259F%25A5%25E5%259F%258E.jpg"},{"GsearchResultClass":"GimageSearch","width":"420","height":"313","imageId":"MHxe0rjkGw3MRM:","tbWidth":"125","tbHeight":"93","unescapedUrl":"http://ozu.cc.kochi-u.ac.jp/~fuchu/syakai/top_img2%5B1%5D.jpg","url":"http://ozu.cc.kochi-u.ac.jp/~fuchu/syakai/top_img2%255B1%255D.jpg","visibleUrl":"ozu.cc.kochi-u.ac.jp","title":"top_img2[1].jpg","titleNoFormatting":"top_img2[1].jpg","originalContextUrl":"http://ozu.cc.kochi-u.ac.jp/~fuchu/syakai/kochicastle.html","content":"\u003cb\u003e高知城\u003c/b\u003e","contentNoFormatting":"高知城","tbUrl":"http://images.google.com/images?q\u003dtbn:MHxe0rjkGw3MRM::ozu.cc.kochi-u.ac.jp/~fuchu/syakai/top_img2%255B1%255D.jpg"},{"GsearchResultClass":"GimageSearch","width":"640","height":"480","imageId":"7H2hzs-xvZA-kM:","tbWidth":"137","tbHeight":"103","unescapedUrl":"http://map.papanavi.jp/ucan/img/sho/39000373_1.jpg","url":"http://map.papanavi.jp/ucan/img/sho/39000373_1.jpg","visibleUrl":"map.papanavi.jp","title":"\u003cb\u003e高知城\u003c/b\u003e | パパナビ","titleNoFormatting":"高知城 | パパナビ","originalContextUrl":"http://map.papanavi.jp/p/spot/details/S39000373-001","content":"\u003cb\u003e高知城\u003c/b\u003e","contentNoFormatting":"高知城","tbUrl":"http://images.google.com/images?q\u003dtbn:7H2hzs-xvZA-kM::map.papanavi.jp/ucan/img/sho/39000373_1.jpg"},{"GsearchResultClass":"GimageSearch","width":"2048","height":"1536","imageId":"6K73uOHjLlHRpM:","tbWidth":"150","tbHeight":"113","unescapedUrl":"http://userdisk.webry.biglobe.ne.jp/008/871/08/N000/000/000/121957888604916210164.JPG","url":"http://userdisk.webry.biglobe.ne.jp/008/871/08/N000/000/000/121957888604916210164.JPG","visibleUrl":"takachin0413.at.webry.info","title":"たかちゃんの独り言/ウェブリブログ","titleNoFormatting":"たかちゃんの独り言/ウェブリブログ","originalContextUrl":"http://takachin0413.at.webry.info/","content":"山内一豊の\u003cb\u003e高知城\u003c/b\u003eです。","contentNoFormatting":"山内一豊の高知城です。","tbUrl":"http://images.google.com/images?q\u003dtbn:6K73uOHjLlHRpM::userdisk.webry.biglobe.ne.jp/008/871/08/N000/000/000/121957888604916210164.JPG"}],"cursor":{"pages":[{"start":"0","label":1},{"start":"4","label":2},{"start":"8","label":3},{"start":"12","label":4},{"start":"16","label":5},{"start":"20","label":6},{"start":"24","label":7},{"start":"28","label":8}],"estimatedResultCount":"392000","currentPageIndex":0,"moreResultsUrl":"http://www.google.com/images?oe\u003dutf8\u0026ie\u003dutf8\u0026source\u003duds\u0026start\u003d0\u0026hl\u003dja\u0026q\u003d%E9%AB%98%E7%9F%A5%E5%9F%8E"}}, "responseDetails": null, "responseStatus": 200}


今回のサンプル・アプリでは、"title" と "unescapedUrl" の値を利用しています。

JSON は階層構造になっているので、こららの値を取得するには、
"responseData" --> "results" と値を辿っていく必要があります。

JSON の解析は、org.json.JSONObject か org.json.JSONArray を使うと簡単に行えます。

"responseData" は値が1つのなので、JSONObject を、
"results" は値が複数(4つ)なので、JSONArray を使用します。

ポイントは、こんなところです。


サンプル・アプリは、3つの要素で構成しています。

  • 検索キーワードを入力する EditText
  • 検索処理を開始する Button
  • 検索結果を表示する ListView


ListView の項目をクリックすると、
別途 Activity を起動し、画像を表示させるようにしています。



メイン Activity のレイアウトです。

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<EditText
    android:id="@+id/keyword"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="高知城"
    />
<Button
    android:id="@+id/search"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="イメージ検索"
    />
<ListView
    android:id="@+id/list"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_weight="2"
    />
</LinearLayout>


メイン Activity です。

JsonTestActivity.java
package jp.kochi.rtaki;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

public class JsonTestActivity extends Activity {

  private static final String QUERY_URL = "https://ajax.googleapis.com/ajax/services/search/images?v=1.0&q=";

  private ListView mListView = null;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Button search = (Button) findViewById(R.id.search);

    search.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick(View v) {
        EditText keyword = (EditText) findViewById(R.id.keyword);
        String key = keyword.getText().toString();

        updateList(key);
      }
    });

    mListView = (ListView) findViewById(R.id.list);

    mListView.setOnItemClickListener(new OnItemClickListener() {

      @Override
      public void onItemClick(AdapterView<?> parent, View view,
          int position, long id) {

        Map map = (Map) parent.getItemAtPosition(position);
        String url = (String) map.get("unescapedUrl");

        Intent intent = new Intent();
        intent.setClass(JsonTestActivity.this, ImageActivity.class);
        intent.putExtra("url", url);
        
        startActivity(intent);
      }
    });
  }

  private void updateList(String key) {

    ArrayList listData = requestImageSearch(key);

    SimpleAdapter adapter = new SimpleAdapter(this, listData,
        android.R.layout.simple_list_item_1, new String[] { "title" },
        new int[] { android.R.id.text1 });

    mListView.setAdapter(adapter);

  }

  private ArrayList requestImageSearch(String key) {
    Map<String, Object> temp;
    ArrayList<Map> listData = new ArrayList<Map>();

    String encodeKey = Uri.encode(key);

    HttpClient client = new DefaultHttpClient();

    String req = QUERY_URL + encodeKey;

    Log.v("JsonTest", "query :" + req);

    HttpUriRequest httpUriReq = new HttpGet(req);

    try {

      HttpResponse res = client.execute(httpUriReq);

      if (res.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
        Toast.makeText(JsonTestActivity.this,
            "StatusCode = " + res.getStatusLine().getStatusCode(),
            Toast.LENGTH_LONG).show();
      } else {

        
        String entity = EntityUtils.toString(res.getEntity());

        JSONObject jsonObj = new JSONObject(entity);

        String responseStatus = jsonObj.getString("responseStatus");
        Log.v("JsonTest", "responseStatus :" + responseStatus);

        if (responseStatus.equals("200") != true) {
          Toast.makeText(JsonTestActivity.this,
              "responseStatus = " + responseStatus,
              Toast.LENGTH_LONG).show();
        } else {

          JSONObject jsonObjData = jsonObj
              .getJSONObject("responseData");

          JSONArray jsonObjResultArray = jsonObjData
              .getJSONArray("results");
          for (int i = 0; i < jsonObjResultArray.length(); i++) {
            JSONObject jsonObjResult = jsonObjResultArray
                .getJSONObject(i);
            Log.v("JsonTest", "title :"
                + jsonObjResult.getString("title"));

            Map map = new HashMap<String, String>();
            map.put("title", jsonObjResult.getString("title"));
            map.put("unescapedUrl", jsonObjResult
                .getString("unescapedUrl"));
            listData.add(map);
          }

        }

      }

    } catch (ClientProtocolException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } catch (JSONException e) {
      e.printStackTrace();
    }

    return listData;
  }
}


image.xml と ImageActivity.java は、ListView の項目をクリックしたときに、
画像を表示させる Activity です。

image.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<ImageView
    android:id="@+id/image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    />
</LinearLayout>


ImageActivity.java
package jp.kochi.rtaki;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.ImageView;

public class ImageActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.image);
    
    Intent intent = getIntent();
    String url = intent.getStringExtra("url");
    
    Bitmap bitmap = getImage(url);
    
    ImageView image = (ImageView) findViewById(R.id.image);
    image.setImageBitmap(bitmap);
    
  }

  private Bitmap getImage(String url) {
    Bitmap bitmap = null;
    InputStream in = null;
    BufferedOutputStream out = null;
    byte[] buf = new byte[512];
    int bytes;

    try {
      in = new BufferedInputStream(new URL(url).openStream(), 512);

      final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
      out = new BufferedOutputStream(dataStream, 512);
      while ((bytes = in.read(buf)) > 0) {
        out.write(buf, 0, bytes);
      }

      out.flush();

      final byte[] data = dataStream.toByteArray();
      BitmapFactory.Options options = new BitmapFactory.Options();
      // options.inSampleSize = 1;

      bitmap = BitmapFactory.decodeByteArray(data, 0, data.length,
          options);

    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (in != null)
          in.close();
        if (out != null)
          out.close();

      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    return bitmap;
  }
}


マニフェスト・ファイルは、青字の2行を追加しています。

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="jp.kochi.rtaki"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".JsonTestActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <activity android:name=".ImageActivity" />

    </application>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-sdk android:minSdkVersion="4" />

</manifest> 


以上、はじめてマッッシュアップでした~(・o・)