2015年2月8日日曜日

アニメ番組表 API使った通信Androidアプリを作ってみた

WebAPIをまとめてるサイト(http://www.find-job.net/startup/api-2013)みてたら面白そうなのがあったので使ってみた

    WebAPIとは

    Webサイトに外部のサイトの提供する機能や情報を組み込んだり、アプリケーションソフトからWeb上で公開されている機能や情報を利用できるサービス
んで、ついでにAndroidの通信処理でよく引っかかるところについてもまとめとく
  1. メインスレッド(UIスレッド)で通信処理ができない
  2. UIスレッドでないとViewの編集ができない
  3. マニフェストに定義しないと通信処理できない
  4. JSONの使い方

アニメマップAPIの使い方

  1. WebAPIの基本的な動き
  2. 通信は基本的にXMLかJSONの2通りのやり方がある。
    違いは取得するデータの形。試しに今週のアニメ情報で比較
    XMLhttp://animemap.net/api/table/osaka.xml
    JSONhttp://animemap.net/api/table/osaka.json
    最近はJSON方式の方をよく見るので、JSONでつくってく。流れはこんな感じ

  3. アニメマップAPIへのリクエストとレスポンス
  4. アニメマップのAPIのサイト(http://animemap.net/pages/api/)を見ると地域ごとに取得できるみたい
    大阪の番組表が欲しいので、リクエスト(サーバへの要求)はコレ(http://animemap.net/api/table/osaka.json)
    実際にブラウザでこのURLに行くとJSON型がどんなデータが返ってくるのか確認できる
    Androidで書くとこんな感じでリクエストして、戻り値で JSON 型をStringにする。
    /**
     * アニメマップに大阪のテレビ情報をリクエスト.
     * 
     * @return JSON型のレスポンスをStringで返す
     */
    public static String getAnimeInfo() {
     // リクエスト
     AndroidHttpClient client = AndroidHttpClient.newInstance(null);
     HttpGet get = new HttpGet("http://animemap.net/api/table/osaka.json");
     StringBuilder sb = new StringBuilder();
     try {
      // リクエスト要求 ⇒ レスポンス
      HttpResponse response = client.execute(get);
      
      // レスポンスをStringにして返値にする
      BufferedReader br = new BufferedReader(new InputStreamReader(
        response.getEntity().getContent()));
      String line = null;
      while ((line = br.readLine()) != null) {
       sb.append(line);
      }
     } catch (IOException e) {
      e.printStackTrace();
     } finally {
      client.close();
     }
     return sb.toString();
    }
    
  5. JSONを解析
  6. JSONは{}で囲まれた領域に"変数名":"value"見たいな感じで定義される。
    ほんとの定義はこの中にもInt型とかいろいろあるけど省略
    レスポンスのJSONを改行や字下げとかで解析してみると、
    {
        "request":{
            "type":"json",
            "url":"地域ごとの一覧URL",
            "updated":"更新時間"
        },
        "response":{
            "item":[
                {
                    "title":"アニメのタイトル",
                    "url":"アニメの放送局一覧URL",
                    "time":"放送時間",
                    "station":"放送局",
                    "state":"よくわらん",
                    "next":"第X話",
                    "episode":"総話数",
                    "cable":"よくわからん",
                    "today":"よくわからん",
                    "week":"放送曜日"
                },
                {
                    "title":"アニメのタイトル",
                    ………省略………
                }
            ]
        }
    }
    
    このJSONは"request","response"の2つの連想配列を持ち、
    "response"にはitemって配列([]←この記号は配列)に1アニメずつ連想配列があることがわかる
    アニメ連想配列ごとに放送時間、放送局など色々な情報が含まれてるけど、とりあえずアニメのタイトル取得してリスト表示させようと思う
    さっきのJSONをStringにしたヤツからアニメタイトル一覧を取得する関数を作る
    /**
     * JSONからアニメのタイトルを抜き出す.
     * 
     * @param str_json
     * @return
     */
    public static String[] getTitleListForJSON(String jsonInfo) {
     final ArrayList<String> tmp = new ArrayList<String>();
     try {
      // String型をJSON型に変換
      JSONObject json = new JSONObject(jsonInfo);
      
      // "response"連想配列を取得
      JSONObject responce = json.getJSONObject("response");
      
      // "item"配列を配列型で取得
      final JSONArray animes = responce.getJSONArray("item");
      
      // "item"配列から1コずつ連想配列を取り出し、タイトルを出力用リストに入れてく
      for (int i = 0; i < animes.length(); i++) {
       JSONObject o = (JSONObject) animes.get(i);
       tmp.add(o.getString("title"));
      }
     } catch (JSONException e) {
      e.printStackTrace();
     }
     return tmp.toArray(new String[0]);
    }
    

Androidの実装

次にAndroidでの描画を実装。これがまた面倒。。 最初に述べたようにAndroidはUIスレッドでは通信できない。ので、別スレッドでデータの取得を行う。でも、取得したデータは通信のスレッドではViewに入れることができない。よって、元のUIスレッドでViewに表示させる処理がいる、、 なに言ってるのかわからなくなるので、一個ずつ処理していく
  1. ベース作り
  2. まず下準備。大本のマニフェストやレイアウトの設定
    アニメのタイトル一覧をListViewで表示させる
    • Manifest.xml
    • これ入れないと通信系動かないので注意!!
      <uses-sdk/>タグの後らへんに追記
       <uses-permission android:name="android.permission.INTERNET" />
      
    • MainActivity.java
    • public class MainActivity extends Activity {
       ListView mListView;
       @Override
       protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mListView = new ListView(this);
        mListView.setBackgroundColor(Color.BLACK);
        setContentView(mListView);
       }
      
       // 記載済みの為省略
       public static String getAnimeInfo() {……}
       public static String[] getTitleForJSON(String jsonInfo) {……}
      
      }
      
  3. 通信用スレッド立てる
  4. もはやAndroid以前にjavaの問題なんだけど、別スレッドを立てるにはThreadクラスのインスタンスがいる。 Thread#start()メソッドを実行することで、Runnable#run()メソッドが別スレッドで実行される。
    ActivityにRunnableインターフェースをくっつけて、runを実装
    public class MainActivity extends Activity implements Runnable {
     ListView mListView;
     @Override
     protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      mListView = new ListView(this);
      mListView.setBackgroundColor(Color.BLACK);
      setContentView(mListView);
      // スレッド起動!通信開始!!
      new Thread(this).start();
     }
    
     // 記載済みの為省略
     public static String getAnimeInfo() {……}
     public static String[] getTitleForJSON(String jsonInfo) {……}
    
     @Override
     public void run() {
      String jsonInfo = getAnimeInfo();
      final String[] titles = getTitleForJSON(jsonInfo);
      for(String str:titles)Log.d("TEST","title = "+str);
     }
    }
    
    ログにアニメタイトル一覧が出ればOK!

  5. Handler使ってUIスレッドで描画
  6. 現在通信スレッド上ににアニメタイトル一覧があるので、ここからViewにデータをセットしたい。そこで直面するのが「通信のスレッドではViewに入れることができない」問題。解決策はHandlerクラス
    HandlerとはHandlerのインスタンスを生成したスレッドで自信の処理を行う事ができる
    つまり、UIスレッド(Mainスレッド)でHandlerを生成しといて、別スレッドでこのHandlerを使えばUIスレッドで処理してることになる。run()をちょっと修正
    // UIスレッド上にHandler生成
    Handler mHandler =new Handler(); //Activityのメンバ変数
    @Override
    public void run() {
     String jsonInfo = getAnimeInfo();
     final String[] titles = getTitleForJSON(jsonInfo);
     for(String str:titles)Log.d("TEST","title = "+str);
     
     // Handlerはpostメソッドで引数のRunnable#run()メソッドを自信の生成スレッド上で実行させる
     mHandler.post(new Runnable() {
      @Override
      public void run() {
       // ここの処理がUIスレッドで実行される
       mListView.setAdapter(new ArrayAdapter<String>(MainActivity.this
         .getApplicationContext(),
         android.R.layout.simple_expandable_list_item_1, titles));
      }
     });
    }
    

完成!
最終的なコードはこんだけ。行数短くしたかったからコメント無、可読性落とした
public class MainActivity extends Activity implements Runnable{
 ListView mListView = null;
 Handler mHandler = new Handler();
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(mListView = new ListView(this));
  new Thread(this).start();
 }

 public static String getAnimeInfo() {
  AndroidHttpClient client = AndroidHttpClient.newInstance(null);
  HttpGet get = new HttpGet("http://animemap.net/api/table/osaka.json");
  StringBuilder sb = new StringBuilder();
  try {
   HttpResponse response = client.execute(get);
   BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
   String line = null;
   while ((line = br.readLine()) != null) sb.append(line);
  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   client.close();
  }
  return sb.toString();
 }

 public static String[] getTitleForJSON(String jsonInfo) {
  final ArrayList<String> tmp = new ArrayList<String>();
  try {
   JSONArray json = new JSONObject(jsonInfo)
   .getJSONObject("response")
   .getJSONArray("item");
   for (int i = 0; i < json.length(); i++)
    tmp.add(((JSONObject) json.get(i)).getString("title"));
  } catch (JSONException e){
   e.printStackTrace();
  }
  return tmp.toArray(new String[0]);
 }

 @Override
 public void run() {
  final String[] titles = getTitleForJSON(getAnimeInfo());
  mHandler.post(new Runnable(){
   @Override
   public void run(){
    mListView.setAdapter(new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_expandable_list_item_1,titles));
   }
  });
 }
}
※マニフェストの追記は忘れない様に!

0 件のコメント:

コメントを投稿