Superdry Memorandom :-p

旧「superdry memorandum :-D」です

Androidプログラミング入門 改訂2版

今回、「Google Androidプログラミング入門 改訂2版」を著者の方から献本いただきました。ありがとうございます。伝説の青本Google Androidプログラミング入門)から約3年、やっと改訂版が出版されました。私がAndroidをはじめたころ、主に青本で体系立てて勉強してきたところもあるのでとても懐かしい気持ちで読みました。

対象読者は初級技術者向けとなっています。ですが、どちらかというとアプリを初めて作る人向けというよりかは、Android技術者として名乗るのに最低限知っておくべきことがまとめられています。逆にこれがわからないとちょっと…という絶妙なバランスの内容になっていると思いました。

ソースコードも全行載っている訳ではありません。なので、サンプルコードをダウンロードするか、本文中のコードの行間を読む必要があります。またすべて説明がある訳ではないので、APIを調べながら読み進めるスキルも必要となります。そのかわり、Androidプログラミングの考え方を体系立てて正しく理解することに、重点を置いています。これ一冊を全部読めれば、初心者がAndroid技術者を名乗れるぐらいにはレベルアップすると思います。

さて、内容ですが、以下のような構成になっています。

 

  • 入門編:Androidアプリケーションの開発〜公開までの流れ
    • 開発環境を作ってみよう
    • エミュレーターを動かしてみよう
    • アプリケーションを動かしてみよう
    • デバッグをしてみよう
    • アプリケーションをさらに改造してみよう
    • 作ったアプリケーションをマーケットに公開してみよう
  • 基本編:アプリケーションの基本概念
    • アプリケーションを構成する要素
    • レイアウト
    • 画面部品
    • 画面操作に依存しない処理
    • データをずっと記憶しておく
    • データとUIのバインディング
    • アプリケーションリソース
    • 応用編:実践テクニック
    • さまざまなでデバイスに対応する
    • アプリケーションの基礎
    • 効率的な開発のためのテストと性能評価
    • Androidアプリケーションにおけるセキュリティの基礎
    • GCM

 

個人的に、応用編の

  • 効率的な開発のためのテストと性能評価
  • Androidアプリケーションにおけるセキュリティの基礎

あたりは、自分の知識が不足していたことも分かり、とても勉強になりました。今まで開発をしてきた人も、スキルの穴埋めや知識の体系化のために、一度読まれてみる事をオススメします。

余談ですが、Erlangによるサーバ構築もこっそり少しだけでてきたりして、ちょっと面白かったです。想像するに、読者がより技術に興味を持ち自主的に勉強するような仕組みを、著者の方々が(おそらく意識的に)随所にしこんでいるのかなあ。その姿勢がわりとステキでした。

tab APIを使ったAndroidサンプルアプリ(2)

前回のエントリの続きです。今回はtabAndroidサンプルアプリケーションの詳細について説明します。

OAuthの認可コードの取得

tabの認証系・更新系APIを使うためにはアクセストークンが必要ですが、その前にまず、アクセストークンを取得するための認可コードの発行が必要です。これはGetAccessTokenActivity.javaのgetAuthorizationCode内で実装しています。

private final OnClickListener getAuthorizationCode = new View.OnClickListener() {

  @Override
  public void onClick(View v) {

    // 認可コードの取得
    OAuthClientRequest request = null;
    try {
      request = OAuthClientRequest
          .authorizationLocation(
              CommonConst.AUTHORIZATION_LOCATION)
          .setResponseType("code")
          .setClientId(CommonConst.CLIENT_ID)
          .setRedirectURI(CommonConst.REDIRECT_URI)
          .buildQueryMessage();
    } catch (OAuthSystemException e) {
      e.printStackTrace();
    }

    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(request
        .getLocationUri()));
    startActivity(intent);
  }
};

認可コードの取得のUriを組み立て(L9〜L15)、Uriをブラウザに対し暗黙的インテントを投げています(L20〜22)。ブラウザに表示されるtabの認可画面で許可すると、ブラウザはコールバックURLへリダイレクトします。GetAccessTokenActivityは、コールバックURLに対応するサービスとしても登録されています。コールバックURLを受け取ったGetAccessTokenActivityは、getAccessToken内で認可コードを取得しています。

private void getAccessToken(Intent intent) {
  Uri uri = intent.getData();

  if (uri != null && uri.toString().startsWith(CommonConst.REDIRECT_URI)) {

    // エラーチェック
    String error = uri.getQueryParameter("error");
    if (error != null) {
      // 中略
    } else {

      // 認可コードの取得
      String code = uri.getQueryParameter("code");
      GetAccessTokenTask task = new GetAccessTokenTask(this, code);
      task.execute();
    }
  }
}

ここで、認可コードを取得し(L13)、アクセストークンの取得処理(L14〜L15)が実行されます。

アクセストークンの取得

アクセストークンの取得は、GetAccessTokenTask.javaのdoInBackground内で実装しています。

@Override
  protected Void doInBackground(Void... arg0) {
    OAuthClientRequest request = null;
    OAuthJSONAccessTokenResponse response = null;

    try {
      request = OAuthClientRequest
          .tokenLocation(CommonConst.TOKEN_LOCATION)
          .setGrantType(GrantType.AUTHORIZATION_CODE)
          .setClientId(CommonConst.CLIENT_ID)
          .setClientSecret(CommonConst.CLIENT_SECRET)
          .setRedirectURI(CommonConst.REDIRECT_URI)
          .setCode(mCode).buildBodyMessage();

      OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());

      response = oAuthClient.accessToken(request);
    } catch (OAuthSystemException e) {
      e.printStackTrace();
    } catch (OAuthProblemException e) {
      e.printStackTrace();
    }

    if (response != null) {
      SharedPreferences pref = mContext.getSharedPreferences(CommonConst.PREF_NAME,
          Context.MODE_PRIVATE);
      SharedPreferences.Editor editor = pref.edit();
      editor.putString(CommonConst.PREF_ACCESS_TOKEN, response.getAccessToken());
      editor.putString(CommonConst.PREF_REFRESH_TOKEN, response.getRefreshToken());
      editor.putString(CommonConst.PREF_EXPIRES_IN, response.getExpiresIn());
      editor.putString(CommonConst.PREF_TOKEN_TYPE, response.getParam("token_type"));
      editor.apply();
    }

    return null;
  }

OAuthClientRequestを組み立て(L7〜L13)、アクセストークンを再取得(L17)しています。

なお、このサンプルアプリケーションでは取得したアクセストークンはSharedPreferencesに保存しています(L25〜L32)。セキュリティが気になる人はここでトークンを暗号化して保存するとなおよいでしょう。SharedPreferencesに保存されたアクセストークンをつかって、tabの認証系・更新系APIにアクセスすることができます。

リフレッシュトークンによるアクセストークンの再取得

アクセストークンには期限があるため、期限切れの場合はAPIアクセスのタイミングで401(Unauthorized)を返します。その場合は、以前アクセストークンを取得したときについてきたリフレッシュトークンを使い、アクセストークンを再取得する必要があります。

サンプルアプリケーションでは、RefreshTokenTask.javaのdoInBackground内で実装しています。

@Override
protected Void doInBackground(Void... arg0) {
  OAuthClientRequest request = null;
  OAuthJSONAccessTokenResponse response = null;

  try {
    request = OAuthClientRequest
        .tokenLocation(CommonConst.TOKEN_LOCATION)
        .setGrantType(GrantType.REFRESH_TOKEN)
        .setClientId(CommonConst.CLIENT_ID)
        .setClientSecret(CommonConst.CLIENT_SECRET)
        .setRefreshToken(mToken).buildBodyMessage();

    OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());

    response = oAuthClient.accessToken(request);
  } catch (OAuthSystemException e) {
    e.printStackTrace();
  } catch (OAuthProblemException e) {
    e.printStackTrace();
  }

  if (response != null) {
    SharedPreferences pref = mContext.getSharedPreferences(CommonConst.PREF_NAME,
        Context.MODE_PRIVATE);
    SharedPreferences.Editor editor = pref.edit();
    editor.putString(CommonConst.PREF_ACCESS_TOKEN, response.getAccessToken());
    editor.putString(CommonConst.PREF_REFRESH_TOKEN, response.getRefreshToken());
    editor.putString(CommonConst.PREF_EXPIRES_IN, response.getExpiresIn());
    editor.putString(CommonConst.PREF_TOKEN_TYPE, response.getParam("token_type"));
    editor.apply();
  }

  return null;
}

アクセストークンと同じように、OAuthClientRequestを組み立て(L7〜L12)、アクセストークンを再取得(L16)しています。違うのはOAuthClientRequestを組み立て方のみです。取得した情報は、これも同様に、SharedPreferencesに上書き保存しています(L24〜L31)。

まとめ

以上で、tab APIを使ったAndroidサンプルアプリケーションの説明は終わります。現時点で、tab APIを使っているアプリはこちらでご紹介しています。今後もいろいろなAPIを充実させていく予定ですので、ぜひ使ってみてください!

tab APIを使ったAndroidサンプルアプリ(1)

今回はAndroid用のサンプルアプリケーションをご紹介します。開発作業の基本的な流れはWEBやiPhoneアプリケーションと変わりませんので、tab APIの紹介エントリも合わせて見てください。

STEP1 アプリケーションを登録してクライアントIDを取る

基本的には、iOSの場合とほぼ同じようにtabの開発者用サイトから入手してください。

STEP2 アプリケーションを開発する

大まかな流れはREADMEに書いてあるのでそちらを見てください。

開発環境を準備する

まずは、Androidの開発環境を準備してください。開発環境の準備方法は、Androidの公式サイトを参照してください。

また、Android用のサンプルアプリケーションGithubで公開しています。これをダウンロードし、Eclipseでプロジェクトフォルダをインポートしてください。具体的には、Eclipseのメニューバーより、[File] - [New] - [Other] - [Android Project from Existing Code] を選択し、

<<ダウンロードフォルダ>>/oauth/android/SampleApp

を指定します。

f:id:Superdry:20191111095554p:plain

最後に、OAuthのライブラリをダウンロードします。このサンプルアプリケーションでは、leelooのライブラリを使用します。

leelooのサイトからOAuthのライブラリをダウンロードします。今回使用するjarファイルは以下の4つになります。

  • jettison-1.2.jar

  • oauth2-client.jar

  • oauth2-common-0.1.jar

  • slf4j-api.1.6.1.jar

この4つのjarファイルを入手したら、libsフォルダの下ににこれらjarファイルをコピーします。

サンプルアプリケーションのカスタマイズ

まずは、CommonConst.javaに、入手したクライアントID等を埋め込みます。

package com.tonchidot.tab.sample.oauth;

import android.os.Build;

public final class CommonConst {

    public static final String TAG = "tabsamle";

    public static final String PREF_NAME = "tab";
    public static final String PREF_ACCESS_TOKEN = "access_token";
    public static final String PREF_REFRESH_TOKEN = "refresh_token";
    public static final String PREF_EXPIRES_IN = "expires_in";
    public static final String PREF_TOKEN_TYPE = "token_type";

    public final static String AUTHORIZATION_LOCATION = "https://tab.do/oauth2/authorize";
    public final static String TOKEN_LOCATION = "https://tab.do/api/1/oauth2/token";
    public static final String ACCESS_URI = "https://tab.do/api/1/users/me.json";

    public final static String CLIENT_ID = "<<Your Client ID>>";
    public final static String CLIENT_SECRET = "<<Your Client Secret>>";
    public final static String REDIRECT_URI = "<<Your Redirect Uri>>";

    public static boolean isHoneycomb() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
    }

}

L19〜L21にそれぞれ入手したクライアントID、クライアントシークレット、コールバックURLに置き換えます。コールバックURLはデフォルトのままだと"tab://callback/oauth2"になります。もしここで独自のURIスキーマを設定した場合は、AndroidManifest.xmlも修正します。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tonchidot.tab.sample.oauth"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="15" />

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

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main"
            android:screenOrientation="portrait" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="android.support.v4.app.FragmentActivity" />

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".GetAccessTokenActivity"
            android:exported="true"
            android:label="@string/title_activity_get_accesstoken"
            android:screenOrientation="portrait" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:host="callback"
                    android:scheme="tab" />
            </intent-filter>
        </activity>
    </application>

</manifest>

L43行目のIntent filterのandroid:schemaの設定を変更します。

これでアプリケーションのカスタマイズは終了です。ビルドののちエミュレータまたは実機で起動してください。

STEP3 アプリケーションを起動する

アプリケーションを起動すると、OAuth認証というボタンが表示されます。

f:id:Superdry:20191111100428p:plain

ボタンをクリックすると、ブラウザでtabの認可画面が開きます。

f:id:Superdry:20191111100442p:plain

「許可する」をタップします。すると、アプリに戻り、認証コードの取得、アクセストークンの取得処理が始まり、認証後プロフィール情報が取得できます。(このサンプルアプリケーションではログインしているユーザ自身の情報を取得します。)

f:id:Superdry:20191111100456p:plain

次回は、サンプルアプリケーションの認証の実装部分についてご紹介します。

ギャラリーからpicasa画像を共有で取得できるuriの変遷

メモ。詳しい方フォローください。個人的には3.x→4.xの変更はちょっとぐぬぬです。

  • Android 3.x
    • mimetype="image/*" で、picasa画像のGalleryProviderでの場所のuriが返ってくる
    • content://com.android.gallery3d.provider/picasa/item/*****
  • Android 2.x
    • mimetype="text/plain" でpicasaの直リンクが返ってくる
    • http://lh*.ggpht.com/*****/.../*****.jpg

Google Web Fonts APIをAndroidでつかってみる

本エントリは、「Android Advent Calendar*1」という企画の1エントリにもなっています。他にも色々楽しいエントリが上がる予定なのでこちらの方もチェックしてみてください☆

Google Web Fonts APIってなに?

Google Web Fontsとは、Googleが提供するWeb Fontのホスティングサービスです。本日時点で、欧文フォントで302字体あります。ざっとこんな感じ。こんな種類の多いのでAndroidでも使ってみたくなってきますよね。


Androidで使ってみる。

Google Web Fonts は Android 2.2 以上対応となっています。通信が発生するのでパーミッションも必要です。AndroidManifest.xmlに追加します。

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

WebViewに表示させます。WebViewのレイアウトをレイアウトファイルmain.xmlに追加します。

    <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </WebView>

Google Web FontsはHTML内で以下のように指定することで使用できます。HEADタグの中がGoogle Web Fonts APIを使う為に必要です。装飾したい文字(INPUT_TEXT)を指定されたフォント(CSS_FONTTYPE)、指定されたサイズ(FONT_SIZE)で装飾します。大文字のところが任意で指定する部分です。

<html>
    <head>
        <link href='http://fonts.googleapis.com/css?family=FONT_TYPE' rel='stylesheet' type='text/css'>
        <style>body {color:#000;font-family: 'CSS_FONTTYPE', serif;font-size: FONT_SIZE;}</style>
    </head>
    <body>
        <p>INPUT_TEXT</p>
    </body>
</html>

このようなHTMLソースをロードするには、

  • Androidではassetsにhtmlファイルをおいて読み出す方法
  • ハードコーディングでデータをロードする方法

があります。今回は汎用性を考えて後者で実装します。ベースとなるHTMLソースをStringで定義しておき、指定箇所を置換します。

    private static final String FONT_BASEURL = "<html><head><link href='http://fonts.googleapis.com/css"
            + "?family=FONT_TYPE' rel='stylesheet' type='text/css'>"
            + "<style>body {color:#000;font-family: 'CSS_FONTTYPE', serif;font-size: FONT_SIZE;}</style>"
            + "</head><body><p>INPUT_TEXT</p></body></html>";

    private String fontType = "Ribeye Marrow";
    private String fontSize = "48px";
    private String inputText = "Shall we use Google Web Fonts API?";

「Ribeye Marrow」というフォントを使いたい場合には、linkタグの中では「Ribeye Marrow」と定義すればいいのですが、cssの中では「Ribeye+Marrow」とする必要があります。これを考慮して置換メソッドを用意し実装します。

    // 置換処理
    private String createUrl() {
        String output = FONT_BASEURL.replace("FONT_TYPE",fontType.replace(' ', '+'))
            .replace("CSS_FONTTYPE", fontType)
            .replace("FONT_SIZE", fontSize)
            .replace("INPUT_TEXT", inputText);
        return output;
    }

そしてこのデータをWebViewにロードします。

        WebView webview = (WebView) findViewById(R.id.webview);
        webview.setBackgroundColor(Color.TRANSPARENT);
        webview.loadDataWithBaseURL(null, createUrl(), "text/html", "utf-8", null);

実行するとWeb Fontが表示されました!

フォント一覧の取得方法

Web Fontの表示だけならAPI_KEYもいらないのですが、一覧を取得する際には必要となります。RESTFulなので、以下URLでリクエストするとJSONでレスポンスが返ってきます。

https://www.googleapis.com/webfonts/v1/webfonts?key=YOUR-API-KEY

JSONの構造はこんな感じです。

{
 "kind": "webfonts#webfontList",
 "items": [

  [...]

  {
   "kind": "webfonts#webfont",
   "family": "Anonymous Pro",
   "variants": [
    "regular",
    "italic",
    "bold",
    "bolditalic"
   ],
   "subsets": [
    "cyrillic",
    "cyrillic-ext",
    "greek",
    "greek-ext",
    "latin",
    "latin-ext"
   ]
  },
  {
   "kind": "webfonts#webfont",
   "family": "Anton",
   "variants": [
    "regular"
   ],
   "subsets": [
    "latin",
    "latin-ext"
   ]
  },

  [...]

 ]
}

これをごりごりパースします。JsonPullParserとか使うと幸せになれるかもしれません。

API KEYの取得方法

API KEYを取得してない人のために取得方法も残しておきます。

  • API Consoleにアクセス
  • プロジェクトを作成しますか?と聞かれるので作成する

  • プロジェクトのサービス一覧が表示されるので下のほうの「Web Fonts Developer API」をONに。ちなみにここに利用制限が書いており、Web Fonts Developer APIの場合は1日10000クエリ。


メリットとデメリット

Androidでは普通アプリでシステムフォントと別のフォントを使いたい場合、assetsフォルダ配下におくのが定石です。しかしフォントを組み込むと、どうしてもアプリの容量が大きくなってしまいます。一般的に、アプリは5MBを超えると、ユーザはストレスを感じるといわれてます。そのためデベロッパーは、容量を犠牲にして美麗なアプリにするよりも、容量が大きくなることを避けがちな傾向にあります。

しかし、その点、Web Fontsだとアプリが軽量ですみます。ただデメリットとしては、通信が必要でローディングに少し時間はかかることがあげられます。またWebViewかブラウザでしか使用できません。メリット/デメリットに応じて使い分けてください。

最後に

今回初心者向けの内容になってますが、Google Web Fonts APIをいろんなところで積極的に使っていただきたくて、あえて書きました。個人的な意見ですが、Androidアプリはもっときれいで心地よいものになる余地が残されていると思います。

ていうか、もっと使おうぜ!チープだけどViewerアプリ作ったからさ!(ダウンロードはコチラ)これで電車の中でもAndroid端末からFont眺めてアプリの構想練れるぜ!作りが粗いのは勘弁してください時間がなかったんだもん。

サンプル

今回のサンプルソースを全文掲載しておきます。

*1:Advent Calendarについてはココ参照。

続きを読む

MecabのJavaバインディング

注意)今回のpostはめずらしくAndroidはぜんっぜん関係ありません。また自分用メモとして残しただけなので間違いがあってもご容赦願います。

日本語形態素解析ライブラリを使おうと思い立ちました。Java形態素解析するにはSenやGoSen、lgoなどがあるようですが、まずはMecabを使ってみるということで。mecab-0.98.tar.gzをダウンロード。

Mecabのインストールはこの辺を参考にしました。

Mecabの辞書作成はこの辺。

私はJavaが一番使い慣れてるので、Javaバインディングすることに。でもオフィシャルではぜんっぜん詳しく書いてません。まずはここでmecab-java-0.97.tar.gzをダウンロードします。

mecab-java-0.97.tar.gzを解凍し、makeしてやるとjavaバインディングに必要なファイルが作成できます。私の環境(Mac OS X(Lion))では、Makefileを以下の赤字のように書き換えるとmakeがうまく行きました。環境に応じて変えてやってください。


TARGET=MeCab
JAVAC=javac
JAVA=java
JAR=jar
CXX=c++
INCLUDE=/System/Library/Frameworks/JavaVM.framework/Headers

PACKAGE=org/chasen/mecab

LIBS=`mecab-config --libs`
INC=`mecab-config --cflags` -I$(INCLUDE) -I$(INCLUDE)/linux

all:
$(CXX) -O3 -c -fpic $(TARGET)_wrap.cxx $(INC)
$(CXX) -shared $(TARGET)_wrap.o -o lib$(TARGET).so $(LIBS)
$(JAVAC) $(PACKAGE)/*.java -J-Dfile.encoding=UTF8
$(JAVAC) test.java -J-Dfile.encoding=UTF8
$(JAR) cfv $(TARGET).jar $(PACKAGE)/*.class -J-Dfile.encoding=UTF8

test:
env LD_LIBRARY_PATH=. $(JAVA) test

clean:
rm -fr *.jar *.o *.so *.class $(PACKAGE)/*.class

cleanall:
rm -fr $(TARGET).java *.cxx

これでmakeすると、新規に2つのファイルが作成されます。

Eclipseで実行する時はJavaプロジェクトを作成しMeCab.jarを外部ライブラリとして追加します。そして、mecab-java-097フォルダ内のtest.javaEclipseを取り込み、以下のように書き換えます。

File f = new File("/Users/hogehoge/mecab-java-097/libMeCab.so");
System.load(f.toString());

これで、ライブラリがロードでき、実行できます。

企業別UX&UIガイドラインのまとめ

下記ブログ記事の内容をそのまま転載します。長くて見づらかったのでコンパクトにした。