Google Spreadsheets Data APIを使う 参照編

**Google Spreadsheet Data APIとは
GoogleのサービスをプログラムからアクセスできるGoogle Data APIGoogle SpreadsheetsにアクセスするためのAPI

Google Data API - Google Code 
http://code.google.com/intl/ja/apis/gdata/
Google スプレッドシートAPI とツール - Google Code 
http://code.google.com/intl/ja/apis/spreadsheets/

Google Data APIサイトには各言語から簡単にアクセスするためのライブラリが公開されています。
Java JavaScript Python .NETなど

ライブラリのダウンロード

Google Data APIサイトからライブラリをダウンロードします。
Java用ライブラリはここからダウンロード

Downloads - gdata-java-client - Google Data Java Client Library - Google Project Hosting 
http://code.google.com/p/gdata-java-client/downloads/list

サンプル付きとソースコード付きの2つがあります。
ソースコード付きの gdata-src.java-1.45.0.zip をダウンロードします。

ライブラリのインポート

ダウンロードしたzipファイルを解凍し、jarファイルをビルドパスに追加します。
Google Spreadsheets Data APIにアクセスするためには以下のライブラリが必要です。

gdata-core-1.0.jar
gdata-client-meta-1.0.jar
gdata-client-1.0.jar
gdata-media-1.0.jar
gdata-spreadsheet-meta-3.0.jar
gdata-spreadsheet-3.0.jar
gdata-docs-meta-3.0.jar
gdata-docs-3.0.jar

google-collect-1.0-rc1.jar

認証

非公開のspreadsheetsにアクセスするためには認証をする必要があります。
認証方式にはOAuthとAuthStubとClientLoginがあります。
今回はデスクトップアプリケーション向けにClientLoginを使用します。
WebアプリケーションならばOAuthかAuthStubを使うべきです。
OAuthはここが参考になるかもしれません。*1

Google Data APIのOAUTHに挑戦 - 気楽なC#工房 
http://csfun.blog49.fc2.com/blog-entry-46.html
ClientLogin
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.util.AuthenticationException;

public class ClientLogin {

    private static final String APPLICATION_NAME = "tomorrowkey-" + ClientLogin.class.getSimpleName() + "-v1";

    public static void main(String[] args) {
        SpreadsheetService client = new SpreadsheetService(APPLICATION_NAME);
        String username = ArgumentUtil.getUsernameFromArgument(args);
        String password = ArgumentUtil.getPasswordFromArgument(args);
        try {
            client.setUserCredentials(username, password);
        } catch (AuthenticationException e) {
            e.printStackTrace();
        }
    }
}

アプリケーション名(APPLICATION_NAME)は
[会社名]-[アプリケーション名]-[バージョン]
という命名規則に従って設定します。
この規則に従っていなくても動作はしますが…。
Google Spreadsheetにアクセスする場合は常にSpreadsheetServiceを使用します。

スプレッドシート一覧を取得する

自分のDocsに保存されているSpreadsheetsの名前を表示します。

ソースコード
import java.io.IOException;
import java.net.URL;
import java.util.List;

import com.google.gdata.client.spreadsheet.FeedURLFactory;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

public class PrintAllSpreadsheet {

    private static final String APPLICATION_NAME = "tomorrowkey-" + PrintAllSpreadsheet.class.getSimpleName() + "-v1";

    public static final void main(String[] args) {
        String username = ArgumentUtil.getUsernameFromArgument(args);
        String password = ArgumentUtil.getPasswordFromArgument(args);
        PrintAllSpreadsheet printAllSpreadsheet = new PrintAllSpreadsheet();
        printAllSpreadsheet.start(username, password);
    }

    private SpreadsheetService client;

    private void start(String username, String password) {
        try {
            client = new SpreadsheetService(APPLICATION_NAME);
            client.setUserCredentials(username, password);

            URL url = FeedURLFactory.getDefault().getSpreadsheetsFeedUrl();
            SpreadsheetFeed feed = client.getFeed(url, SpreadsheetFeed.class);
            List<SpreadsheetEntry> spreadsheetEntryList = feed.getEntries();
            for (SpreadsheetEntry spreadsheetEntry : spreadsheetEntryList) {
                System.out.println(spreadsheetEntry.getTitle().getPlainText());
            }
        } catch (AuthenticationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServiceException e) {
            e.printStackTrace();
        }
    }
}
実行結果
TEST
投票シート
CharSettings作成シート
第2回Android温泉 会計
第2回Android温泉企画
第2回Android温泉参加者一覧
第2回Android温泉アンケート
集まれGDDのdeb仲間
デコ美統計
やることリスト
都道府県別DL数
デ部T申し込みフォーム
検索データ
ガソリン
北海道日程
北海道日程

スプレッドシート名で検索して取得する

ソースコード
import java.io.IOException;
import java.net.URL;
import java.util.List;

import com.google.gdata.client.spreadsheet.FeedURLFactory;
import com.google.gdata.client.spreadsheet.SpreadsheetQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

public class PrintSearhedSpreadsheet {

    private static final String APPLICATION_NAME = "tomorrowkey-" + PrintSearhedSpreadsheet.class.getSimpleName() + "-v1";

    public static final void main(String[] args) {
        String username = ArgumentUtil.getUsernameFromArgument(args);
        String password = ArgumentUtil.getPasswordFromArgument(args);
        PrintSearhedSpreadsheet printSearhedSpreadsheet = new PrintSearhedSpreadsheet();
        printSearhedSpreadsheet.start(username, password);
    }

    private SpreadsheetService client;

    private void start(String username, String password) {
        try {
            client = new SpreadsheetService(APPLICATION_NAME);
            client.setUserCredentials(username, password);

            URL url = FeedURLFactory.getDefault().getSpreadsheetsFeedUrl();
            SpreadsheetQuery query = new SpreadsheetQuery(url);
            query.setTitleQuery("android");
            SpreadsheetFeed feed = client.query(query, SpreadsheetFeed.class);
            List<SpreadsheetEntry> spreadsheetEntryList = feed.getEntries();
            for (SpreadsheetEntry spreadsheetEntry : spreadsheetEntryList) {
                System.out.println(spreadsheetEntry.getTitle().getPlainText());
            }
        } catch (AuthenticationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServiceException e) {
            e.printStackTrace();
        }

    }
}
実行結果
第2回Android温泉 会計
第2回Android温泉企画
第2回Android温泉参加者一覧
第2回Android温泉アンケート

大文字/小文字は識別されないようですね。

ワークシート一覧を取得する

テスト用の「TEST」という名前のSpreadsheetと作成しました。

ソースコード

import java.io.IOException;
import java.net.URL;
import java.util.List;

import com.google.gdata.client.spreadsheet.FeedURLFactory;
import com.google.gdata.client.spreadsheet.SpreadsheetQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.data.spreadsheet.WorksheetEntry;
import com.google.gdata.data.spreadsheet.WorksheetFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

public class PrintAllWorksheet {

    private static final String APPLICATION_NAME = "tomorrowkey-" + PrintAllWorksheet.class.getSimpleName() + "-v1";

    public static final void main(String[] args) {
        String username = ArgumentUtil.getUsernameFromArgument(args);
        String password = ArgumentUtil.getPasswordFromArgument(args);
        PrintAllWorksheet printAllWorksheet = new PrintAllWorksheet();
        printAllWorksheet.start(username, password);
    }

    private SpreadsheetService client;

    private void start(String username, String password) {
        try {
            client = new SpreadsheetService(APPLICATION_NAME);
            client.setUserCredentials(username, password);
            SpreadsheetEntry spreadsheet = getSpreadsheet("TEST");

            URL url = spreadsheet.getWorksheetFeedUrl();
            WorksheetFeed feed = client.getFeed(url, WorksheetFeed.class);
            List<WorksheetEntry> worksheetEntryList = feed.getEntries();
            for (WorksheetEntry worksheetEntry : worksheetEntryList) {
                System.out.println(worksheetEntry.getTitle().getPlainText());
            }
        } catch (AuthenticationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServiceException e) {
            e.printStackTrace();
        }

    }

    private SpreadsheetEntry getSpreadsheet(String spreadsheetName) throws IOException,
            ServiceException {
        URL url = FeedURLFactory.getDefault().getSpreadsheetsFeedUrl();
        SpreadsheetQuery query = new SpreadsheetQuery(url);
        query.setTitleQuery(spreadsheetName);
        SpreadsheetFeed feed = client.query(query, SpreadsheetFeed.class);
        List<SpreadsheetEntry> spreadsheetEntryList = feed.getEntries();
        if (spreadsheetEntryList.isEmpty()) {
            throw new RuntimeException("not found spreadsheet '" + spreadsheetName + "'");
        }
        return spreadsheetEntryList.get(0);
    }
}
実行結果
東京
品川
新宿

ワークシート名で検索して取得する

ソースコード
import java.io.IOException;
import java.net.URL;
import java.util.List;

import com.google.gdata.client.spreadsheet.FeedURLFactory;
import com.google.gdata.client.spreadsheet.SpreadsheetQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.client.spreadsheet.WorksheetQuery;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.data.spreadsheet.WorksheetEntry;
import com.google.gdata.data.spreadsheet.WorksheetFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

public class PrintSearchedWorksheet {

    private static final String APPLICATION_NAME = "tomorrowkey-" + PrintSearchedWorksheet.class.getSimpleName() + "-v1";

    public static final void main(String[] args) {
        String username = ArgumentUtil.getUsernameFromArgument(args);
        String password = ArgumentUtil.getPasswordFromArgument(args);
        PrintSearchedWorksheet printAllWorksheet = new PrintSearchedWorksheet();
        printAllWorksheet.start(username, password);
    }

    private SpreadsheetService client;

    private void start(String username, String password) {
        try {
            client = new SpreadsheetService(APPLICATION_NAME);
            client.setUserCredentials(username, password);
            SpreadsheetEntry spreadsheet = getSpreadsheet("TEST");

            URL url = spreadsheet.getWorksheetFeedUrl();
            WorksheetQuery query = new WorksheetQuery(url);
            query.setTitleQuery("品川");
            WorksheetFeed feed = client.query(query, WorksheetFeed.class);
            List<WorksheetEntry> worksheetEntryList = feed.getEntries();
            for (WorksheetEntry worksheetEntry : worksheetEntryList) {
                System.out.println(worksheetEntry.getTitle().getPlainText());
            }
        } catch (AuthenticationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServiceException e) {
            e.printStackTrace();
        }
    }

    private SpreadsheetEntry getSpreadsheet(String spreadsheetName) throws IOException,
            ServiceException {
        URL url = FeedURLFactory.getDefault().getSpreadsheetsFeedUrl();
        SpreadsheetQuery query = new SpreadsheetQuery(url);
        query.setTitleQuery(spreadsheetName);
        SpreadsheetFeed feed = client.query(query, SpreadsheetFeed.class);
        List<SpreadsheetEntry> spreadsheetEntryList = feed.getEntries();
        if (spreadsheetEntryList.isEmpty()) {
            throw new RuntimeException("not found spreadsheet '" + spreadsheetName + "'");
        }
        return spreadsheetEntryList.get(0);
    }
}
実行結果
品川

ワークシートからリスト一覧を取得する

リストとは行のことであり、spreadsheetをデータベースのように扱う場合、レコードのようなイメージになります。

import java.io.IOException;
import java.net.URL;
import java.util.List;

import com.google.gdata.client.spreadsheet.FeedURLFactory;
import com.google.gdata.client.spreadsheet.SpreadsheetQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.client.spreadsheet.WorksheetQuery;
import com.google.gdata.data.spreadsheet.CustomElementCollection;
import com.google.gdata.data.spreadsheet.ListEntry;
import com.google.gdata.data.spreadsheet.ListFeed;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.data.spreadsheet.WorksheetEntry;
import com.google.gdata.data.spreadsheet.WorksheetFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

public class PrintAllList {

    private static final String APPLICATION_NAME = "tomorrowkey-" + PrintAllList.class.getSimpleName() + "-v1";

    public static final void main(String[] args) {
        String username = ArgumentUtil.getUsernameFromArgument(args);
        String password = ArgumentUtil.getPasswordFromArgument(args);
        PrintAllList printAllWorksheet = new PrintAllList();
        printAllWorksheet.start(username, password);
    }

    private SpreadsheetService client;

    private void start(String username, String password) {
        try {
            client = new SpreadsheetService(APPLICATION_NAME);
            client.setUserCredentials(username, password);
            WorksheetEntry worksheet = getWorksheet("TEST", "東京");

            URL url = worksheet.getListFeedUrl();
            ListFeed feed = client.getFeed(url, ListFeed.class);
            List<ListEntry> listEntryList = feed.getEntries();
            for (ListEntry listEntry : listEntryList) {
                CustomElementCollection customElements = listEntry.getCustomElements();
                StringBuilder sb = new StringBuilder();
                sb.append("品目:").append(customElements.getValue("品目")).append(",");
                sb.append("価格:").append(customElements.getValue("価格")).append(",");
                sb.append("在庫:").append(customElements.getValue("在庫"));
                System.out.println(sb.toString());
            }
        } catch (AuthenticationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServiceException e) {
            e.printStackTrace();
        }

    }

    private WorksheetEntry getWorksheet(String spreadsheetName, String worksheetName)
            throws IOException, ServiceException {
        SpreadsheetEntry spreadsheet = getSpreadsheet(spreadsheetName);
        URL url = spreadsheet.getWorksheetFeedUrl();
        WorksheetQuery query = new WorksheetQuery(url);
        WorksheetFeed feed = client.query(query, WorksheetFeed.class);
        List<WorksheetEntry> worksheetEntryList = feed.getEntries();
        if (worksheetEntryList.isEmpty()) {
            throw new RuntimeException("not found worksheet '" + worksheetName + "' in '"
                    + spreadsheetName + "'");
        }
        return worksheetEntryList.get(0);
    }

    private SpreadsheetEntry getSpreadsheet(String spreadsheetName) throws IOException,
            ServiceException {
        URL url = FeedURLFactory.getDefault().getSpreadsheetsFeedUrl();
        SpreadsheetQuery query = new SpreadsheetQuery(url);
        query.setTitleQuery(spreadsheetName);
        SpreadsheetFeed feed = client.query(query, SpreadsheetFeed.class);
        List<SpreadsheetEntry> spreadsheetEntryList = feed.getEntries();
        if (spreadsheetEntryList.isEmpty()) {
            throw new RuntimeException("not found spreadsheet '" + spreadsheetName + "'");
        }
        return spreadsheetEntryList.get(0);
    }
}
実行結果
品目:りんご,価格:100,在庫:10
品目:みかん,価格:80,在庫:20
品目:もも,価格:160,在庫:3

ワークシートを検索してリストを取得する

ソースコード
import java.io.IOException;
import java.net.URL;
import java.util.List;

import com.google.gdata.client.spreadsheet.FeedURLFactory;
import com.google.gdata.client.spreadsheet.ListQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.client.spreadsheet.WorksheetQuery;
import com.google.gdata.data.spreadsheet.CustomElementCollection;
import com.google.gdata.data.spreadsheet.ListEntry;
import com.google.gdata.data.spreadsheet.ListFeed;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.data.spreadsheet.WorksheetEntry;
import com.google.gdata.data.spreadsheet.WorksheetFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

public class PrintSearchedList {

    private static final String APPLICATION_NAME = "tomorrowkey-"
            + PrintSearchedList.class.getSimpleName() + "-v1";

    public static final void main(String[] args) {
        String username = ArgumentUtil.getUsernameFromArgument(args);
        String password = ArgumentUtil.getPasswordFromArgument(args);
        PrintSearchedList printAllWorksheet = new PrintSearchedList();
        printAllWorksheet.start(username, password);
    }

    private SpreadsheetService client;

    private void start(String username, String password) {
        try {
            client = new SpreadsheetService(APPLICATION_NAME);
            client.setUserCredentials(username, password);
            WorksheetEntry worksheet = getWorksheet("TEST", "東京");

            URL url = worksheet.getListFeedUrl();
            ListQuery query = new ListQuery(url);
            query.setFullTextQuery("*ん");
            ListFeed feed = client.getFeed(query, ListFeed.class);
            List<ListEntry> listEntryList = feed.getEntries();
            for (ListEntry listEntry : listEntryList) {
                CustomElementCollection customElements = listEntry.getCustomElements();
                StringBuilder sb = new StringBuilder();
                sb.append("品目:").append(customElements.getValue("品目")).append(",");
                sb.append("価格:").append(customElements.getValue("価格")).append(",");
                sb.append("在庫:").append(customElements.getValue("在庫"));
                System.out.println(sb.toString());
            }
        } catch (AuthenticationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServiceException e) {
            e.printStackTrace();
        }

    }

    private WorksheetEntry getWorksheet(String spreadsheetName, String worksheetName)
            throws IOException, ServiceException {
        SpreadsheetEntry spreadsheet = getSpreadsheet(spreadsheetName);
        URL url = spreadsheet.getWorksheetFeedUrl();
        WorksheetQuery query = new WorksheetQuery(url);
        WorksheetFeed feed = client.query(query, WorksheetFeed.class);
        List<WorksheetEntry> worksheetEntryList = feed.getEntries();
        if (worksheetEntryList.isEmpty()) {
            throw new RuntimeException("not found worksheet '" + worksheetName + "' in '"
                    + spreadsheetName + "'");
        }
        return worksheetEntryList.get(0);
    }

    private SpreadsheetEntry getSpreadsheet(String spreadsheetName) throws IOException,
            ServiceException {
        URL url = FeedURLFactory.getDefault().getSpreadsheetsFeedUrl();
        SpreadsheetQuery query = new SpreadsheetQuery(url);
        query.setTitleQuery(spreadsheetName);
        SpreadsheetFeed feed = client.query(query, SpreadsheetFeed.class);
        List<SpreadsheetEntry> spreadsheetEntryList = feed.getEntries();
        if (spreadsheetEntryList.isEmpty()) {
            throw new RuntimeException("not found spreadsheet '" + spreadsheetName + "'");
        }
        return spreadsheetEntryList.get(0);
    }
}
実行結果
品目:みかん,価格:80,在庫:20

検索条件には完全一致はもちろん、"*"を使って簡単なあいまい検索もできるみたいです。

ワークシートからすべてのセルを取得する

ソースコード
import java.io.IOException;
import java.net.URL;
import java.util.List;

import com.google.gdata.client.spreadsheet.FeedURLFactory;
import com.google.gdata.client.spreadsheet.SpreadsheetQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.client.spreadsheet.WorksheetQuery;
import com.google.gdata.data.spreadsheet.CellEntry;
import com.google.gdata.data.spreadsheet.CellFeed;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.data.spreadsheet.WorksheetEntry;
import com.google.gdata.data.spreadsheet.WorksheetFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

public class PrintAllCell {

    private static final String APPLICATION_NAME = "tomorrowkey-"
            + PrintAllCell.class.getSimpleName() + "-v1";

    public static final void main(String[] args) {
        String username = ArgumentUtil.getUsernameFromArgument(args);
        String password = ArgumentUtil.getPasswordFromArgument(args);
        PrintAllCell printAllWorksheet = new PrintAllCell();
        printAllWorksheet.start(username, password);
    }

    private SpreadsheetService client;

    private void start(String username, String password) {
        try {
            client = new SpreadsheetService(APPLICATION_NAME);
            client.setUserCredentials(username, password);
            WorksheetEntry worksheet = getWorksheet("TEST", "東京");

            URL url = worksheet.getCellFeedUrl();
            CellFeed feed = client.getFeed(url, CellFeed.class);
            List<CellEntry> cellEntryList = feed.getEntries();
            for (CellEntry cellEntry : cellEntryList) {
                System.out.println(cellEntry.getCell().getValue());
            }
        } catch (AuthenticationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServiceException e) {
            e.printStackTrace();
        }
    }

    private WorksheetEntry getWorksheet(String spreadsheetName, String worksheetName)
            throws IOException, ServiceException {
        SpreadsheetEntry spreadsheet = getSpreadsheet(spreadsheetName);
        URL url = spreadsheet.getWorksheetFeedUrl();
        WorksheetQuery query = new WorksheetQuery(url);
        WorksheetFeed feed = client.query(query, WorksheetFeed.class);
        List<WorksheetEntry> worksheetEntryList = feed.getEntries();
        if (worksheetEntryList.isEmpty()) {
            throw new RuntimeException("not found worksheet '" + worksheetName + "' in '"
                    + spreadsheetName + "'");
        }
        return worksheetEntryList.get(0);
    }

    private SpreadsheetEntry getSpreadsheet(String spreadsheetName) throws IOException,
            ServiceException {
        URL url = FeedURLFactory.getDefault().getSpreadsheetsFeedUrl();
        SpreadsheetQuery query = new SpreadsheetQuery(url);
        query.setTitleQuery("TEST");
        SpreadsheetFeed feed = client.query(query, SpreadsheetFeed.class);
        List<SpreadsheetEntry> spreadsheetEntryList = feed.getEntries();
        if (spreadsheetEntryList.isEmpty()) {
            throw new RuntimeException("not found spreadsheet '" + spreadsheetName + "'");
        }
        return spreadsheetEntryList.get(0);
    }
}
実行結果
品目
価格
在庫
りんご
100
10
みかん
80
20
もも
160
3

ワークシートを検索してセルを取得する

ソースコード
import java.io.IOException;
import java.net.URL;
import java.util.List;

import com.google.gdata.client.spreadsheet.CellQuery;
import com.google.gdata.client.spreadsheet.FeedURLFactory;
import com.google.gdata.client.spreadsheet.SpreadsheetQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.client.spreadsheet.WorksheetQuery;
import com.google.gdata.data.spreadsheet.CellEntry;
import com.google.gdata.data.spreadsheet.CellFeed;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.data.spreadsheet.WorksheetEntry;
import com.google.gdata.data.spreadsheet.WorksheetFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

public class PrintSearchedCell {

    private static final String APPLICATION_NAME = "tomorrowkey-"
            + PrintSearchedCell.class.getSimpleName() + "-v1";

    public static final void main(String[] args) {
        String username = ArgumentUtil.getUsernameFromArgument(args);
        String password = ArgumentUtil.getPasswordFromArgument(args);
        PrintSearchedCell printAllWorksheet = new PrintSearchedCell();
        printAllWorksheet.start(username, password);
    }

    private SpreadsheetService client;

    private void start(String username, String password) {
        try {
            client = new SpreadsheetService(APPLICATION_NAME);
            client.setUserCredentials(username, password);
            WorksheetEntry worksheet = getWorksheet("TEST", "東京");

            URL url = worksheet.getCellFeedUrl();
            CellQuery query = new CellQuery(url);
            query.setFullTextQuery("*0");
            CellFeed feed = client.getFeed(query, CellFeed.class);
            List<CellEntry> cellEntryList = feed.getEntries();
            for (CellEntry cellEntry : cellEntryList) {
                System.out.println(cellEntry.getCell().getValue());
            }
        } catch (AuthenticationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServiceException e) {
            e.printStackTrace();
        }
    }

    private WorksheetEntry getWorksheet(String spreadsheetName, String worksheetName)
            throws IOException, ServiceException {
        SpreadsheetEntry spreadsheet = getSpreadsheet(spreadsheetName);
        URL url = spreadsheet.getWorksheetFeedUrl();
        WorksheetQuery query = new WorksheetQuery(url);
        WorksheetFeed feed = client.query(query, WorksheetFeed.class);
        List<WorksheetEntry> worksheetEntryList = feed.getEntries();
        if (worksheetEntryList.isEmpty()) {
            throw new RuntimeException("not found worksheet '" + worksheetName + "' in '"
                    + spreadsheetName + "'");
        }
        return worksheetEntryList.get(0);
    }

    private SpreadsheetEntry getSpreadsheet(String spreadsheetName) throws IOException,
            ServiceException {
        URL url = FeedURLFactory.getDefault().getSpreadsheetsFeedUrl();
        SpreadsheetQuery query = new SpreadsheetQuery(url);
        query.setTitleQuery("TEST");
        SpreadsheetFeed feed = client.query(query, SpreadsheetFeed.class);
        List<SpreadsheetEntry> spreadsheetEntryList = feed.getEntries();
        if (spreadsheetEntryList.isEmpty()) {
            throw new RuntimeException("not found spreadsheet '" + spreadsheetName + "'");
        }
        return spreadsheetEntryList.get(0);
    }
}
実行結果
100
10
80
20
160

まとめ

1. FeedのURLを取得する
・Spreadsheet
→FeedURLFactory.getDefault().getSpreadsheetsFeedUrl();
・Worksheet
→SpreadsheetEntry#getWorksheetFeedUrl();
・List
→WorksheetEntry#getListFeedUrl();
・Cell
→WorksheetEntry#getCellFeedUrl();
2. 検索する場合はQueryを作成する
・Spreadsheet
→SpreadsheetQuery
・Worksheet
→WorksheetQuery
・List
→ListQuery
・Cell
→CellQuery
3. URL/Queryを使い、Feedを作成する
・Spreadsheet
→SpreadsheetService#getFeed(#URL, SpreadsheetFeed.class);
→SpreadsheetService#getFeed(#SpreadsheetQuery, SpreadsheetFeed.class);
・Worksheet
→SpreadsheetService#getFeed(#URL, WorksheetFeed.class);
→SpreadsheetService#getFeed(#WorksheetQuery, WorksheetFeed.class);
・List
→SpreadsheetService#getFeed(#URL, ListFeed.class);
→SpreadsheetService#getFeed(#ListQuery, ListFeed.class);
・Cell
→SpreadsheetService#getFeed(#URL, CellFeed.class);
→SpreadsheetService#getFeed(#CellQuery, CellFeed.class);
4. Listを取得する
Feed#getEntries();

所感

同じようなコードをたくさん書いて疲れた、、。
とりあえず参照はこれだけあればできるはず。
他にRecordとTableってクラスがあるけど使ったことないなあ。

*1:試していません。いつかやりたいな

ブラウザからlogcatを見る

こちらが最新です
ブラウザからlogcatを見る(アップデートしました) - 明日の鍵 
http://d.hatena.ne.jp/tomorrowkey/20111001/1317451235

充電していない状態でlogcatを確認したくて、どうすればいいか悩んでました。
思いついたのが、表示するアプリを作ればいいんじゃね!って事。
しかし、端末に表示するのでは画面が小さすぎる、読みづらいので
Socketでパソコンとつないで表示することに
ついでに気になってたWebSocketを使うようにすれば、新しい事もできて一石二鳥!わーい!

構成

サーバ側

Androidアプリ
画面は一つでサーバの起動と停止のみ行う
サーバが起動するのはWi-Fiで通信中のみ

クライアント

Chrome(11.0.696.68)
Android端末のIPアドレスとポート番号を指定することでソケットで接続を試みる
接続後はサーバから送られてくるテキストをhtmlで垂れ流す

通常のSocketについて調べる

Socketはさんざん触ったことあるので、大丈夫。
勉強したい人はTECHSCOREがオススメ

ソケットネットワークプログラミング-TECHSCORE- 
http://legacy.techscore.com/tech/J2SE/Network/2.html

そういや本も一冊買ってた
Socketの他にStreamについても勉強になった

TCP/IPソケットプログラミング Java編

TCP/IPソケットプログラミング Java編

WebSocketを調べる

The WebSocket API 
http://www.w3.org/TR/2011/WD-websockets-20110419/

WebSocketの一次情報
draftと書かれているので、仕様が変わるかもしれないってヤツですね。
英語分からん。

W3C - 『The WebSocket API』日本語訳 - HTML5.JP 
http://www.html5.jp/trans/w3c_websockets.html

日本語訳バージョン
日本語分からん。

WebSocket - Wikipedia 
http://ja.wikipedia.org/wiki/WebSocket

Wikipedia
通常のSocketみたく、繋げたら何してもおっけーってわけではなく
ハンドシェイクというやりとりが必要という情報が

WebSocketのプロトコル ― ありえるえりあ 
http://dev.ariel-networks.com/Members/inoue/websocket/

1年くらい前の記事だけど
WebSocketについてまとまってる
ハンドシェイクのやり方も丁寧に解説してある
意味あんのかって突っ込んでるけどw

http://www.google.co.jp/codesearch/p?hl=ja#CwgqP48SYDw/trunk/shared/J2SE/jWebSocketServer/org/jwebsocket/netty/engines/NettyEngineHandler.java

google code searchでjavaでのサーバ側の実装を見つけた
constructHandShakeResponseというメソッドでごにょごにょしてる
一番参考になった。コードが一番分かりやすい。

jWebSocket - The Open Source Java WebSocket Server 
http://jwebsocket.org/

どうやらこれのソースみたいだ

HTML5 WebSockets Tutorial 
http://www.tutorialspoint.com/html5/html5_websocket.htm

クライアントサイドはここを参照
簡単すなぁ…

作った

適当な実装だけど動くものはできた

実行イメージ


なんかそれっぽいの見えてるよね!

試すには

http://tomorrowkey.googlecode.com/svn/tags/LogcatSocketServer/1.0.0/bin/LogcatSocketServer.apk
このアプリをAndroid端末にインストール(※クリックするとapkファイルがダウンロードされます)
アプリ起動するとIPアドレスとポート番号が表示されます。*1

http://tomorrowkey.googlecode.com/svn/trunk/LogcatOnBrowser/Client/logcat.html
同じLAN内につながってるパソコンにこのHTMLを保存する
ブラウザから保存したhtmlファイルにアクセスする*2
IPアドレスとポート番号を入れるテキストがあるので、アプリに表示されているものを入力
connectボタンを押せばlogcatが読み込まれます


動くようには作りましたが、けっこう適当実装です。
自己責任で実行してください。責任は一切負いません。

やってみて

javascriptとかhtmlとか普段ぜんぜんやらないから楽しかった!
今は情報がたくさんあって、知識がなくてもなんとなくで作れてしまうね
なんか実装の悪いところあったら指摘ください。
WebSocketはすごく楽しい。
可能性を感じる。

*1:Wi-Fiに接続していない場合、そのまま終了します

*2:ここでアラートが表示された場合、使っているブラウザがWebSocketに対応していません

9patchを覚えよう!

角丸が綺麗に表示されない!

ボタンを作りました!
角丸のボタンです。

早速ボタンの背景に設定しました。

なんてこったい
設定するボタンが大きすぎて背景画像が引き伸ばされてしまいます。

9patchを使おう!

そこで9patchの出番です。
9patchは、画像よりモノが大きかった場合(今回は画像よりボタンが大きかった)
引き伸ばす部分を指定することで、画像の崩れを無くす事ができるのです!
Fireworksでいう所の9スライスという機能に似ています。
先ほどのボタン画像に9patchの処理を施してみます。

できました!
上側と左側に黒い1ピクセルが見えますでしょうか?
ちょっと拡大します。

拡大したのでボケてますが、これなら分かりますね。
9patchは上下左右に、アルファ無しの黒(#000000)の印を付けることで伸ばす部分を指定します。
上下左右それぞれ意味があります。
上と左の印は必須です。
下と右の印は省略することができます。
下と右の印の説明は後でやります。

図解9patch

さきほどの画像を解説すると

上に指定したピクセルの列(赤)が

横に伸びる時にコピーされます。

左に指定したピクセルの行(青)が

縦に伸びる時にコピーされます。
こうやって角丸部分が拡大されないようにします。
端末に設定してみましょう。

綺麗に角丸が表示されました。
さらに、どの部分にコピーされているのか確認したいため、こんな画像も用意してみました。

端末で表示してみましょう。

なるほどー、さっきのサンプルみたいにコピーされました。

さらに挙動を調べる

通常9patchの上と左の指定は1ピクセルだけで済みますが、実際には複数ピクセル指定することができます。
端末で表示させて挙動をたしかめてみましょう。

たとえば2ピクセル指定した場合はどうなるんでしょう。
端末で表示して見てみます。

半分ずつコピーされました。
おもしろいですね。

次はこんな画像はどうでしょう。

各々近いところを補うんですかね。

さらに活用する

挙動が分かれば、それを利用します。

この画像を使って

こんな風に表示したり*1

この画像を使って

こんな風に表示したり*2

グラデーションなら左側をほとんど塗っちゃってもいいんじゃないでしょうか

段差が見えちゃってますが、多少の拡大ならごまかせます。


ベタ塗りなのにこういう9patchの指定の仕方をしている人がいて、本当に理解しているのかなーって心配になります…。
ちゃんと表示はされるんだけどね…。

文字が変な位置に表示される

ボタンにはラベルが必要です。
ボタンを押した時にどんな機能が働くのかを、ラベルの文字によって表現しましょう。
今回は"こんにちは!"というラベルにします。
きっと押したら"ぽぽぽぽーん!"と音が鳴るに違いありません。
さっき作ったボタンにラベルを表示するよう変更してみます。

こんにちは!って表示されたけど、位置がおかしいです。
下に表示しているデフォルトのボタンではセンタリングされているのに、今回作ったボタンは変な位置に表示されています。

さらに9patch

これも9patchの機能を使って解決します。
上と左の印を使う事によって伸ばす部分を指定しましたが、
下と右の印を使う事によってコンテンツの位置を指定することができます。
ボタンの画像に対してコンテンツの位置を指定してみます。

できました!
例によって拡大します。

下と右に黒いピクセルがたくさんあるかと思います。
解説すると

下の黒いピクセルの列(赤)と
右の黒いピクセルの行(青)の
交わった場所(紫)がコンテンツの入る場所になります。
今回だと文字列の入る場所です。
端末で表示してみましょう。

ちゃんとセンタリングされました。
さっきの赤/青の画像でも表示してみましょう。

ちゃんと紫のところにラベルが表示されています。

さらに挙動をしらべる

調べようかと思ったのですが
何もできません。
下と右はこれですべてみたいです。

例えばこんな画像を用意して端末で表示してみようかと思ったのですが
eclipseさんに怒られてしまい、コンパイルが通りません。

さらに活用する


この画像を使って

こんな風に表示したり

この画像を使って

こんな風に表示したりできます。

9patchが苦手な画像

9patchには苦手な画像があります。
たとえば

ストライプ*3

グラデーション
例に出しましたが、段差が見えてしまうのでオススメできません。

その他連続した柄*4
他にもいろいろパターンはあると思うので、想像しながら作りましょう。
AndroidSDKの中に入ってるdraw9patchというツールを使うと、どういう風に表示されるか見ながら9patchのマークを付ける事ができます。
慣れないうちはそれを使うのも手でしょう。*5

さらに9patchを理解する

読むだけだと完全に理解はできません。
これ以上は実際に作って、端末に表示させてみて、挙動を自分で確かめてみるといいと思います。

その他9patch記事

Draw 9-patch | Android Developers 
http://developer.android.com/guide/developing/tools/draw9patch.html
公式の9patch解説
Draw 9-patch - ソフトウェア技術ドキュメントを勝手に翻訳
https://sites.google.com/a/techdoctranslator.com/jp/android/developing/tools/draw9patch
公式の9patch解説の日本語翻訳
9-patch - 3156note 
https://sites.google.com/site/3156note/home/android/9-patch
draw9patchの使い方を詳しく解説
チュートリアル:9patchで画像を作る « Tech Booster 
http://techbooster.jpn.org/andriod/environment/3996/
draw9patchの使い方を詳しく解説
draw9patch で NinePatch をつくる方法 - Hacking My Way 〜 itogのhack日記
http://d.hatena.ne.jp/itog/20100209/1265684439
draw9patchの使い方を詳しく解説

まとめ

  • 単純に拡大されたくない時は9patchを使う。
  • コピーされる場所を指定するには上と左に9patchのマークをつける。
  • 中に何かコンテンツを入れる時は下と右に9patchのマークをつける。
  • どんな画像になるか想像しながら作る。

*1:xmlで指定することにより、ボタンとアイコン(ハート、星)を分離することができます。分離できた方がデザイナさんもプログラマーさんも幸せなので、そちらを使った方がいいです。

*2:xmlで指定することにより、ボタンとアイコン(ハート、星)を分離することができます。分離できた方がデザイナさんもプログラマーさんも幸せなので、そちらを使った方がいいです。

*3:9patchではなく、これを使うと解決できるかもしれません http://developer.android.com/guide/topics/resources/drawable-resource.html#Bitmap

*4:9patchではなく、これを使うと解決できるかもしれません http://developer.android.com/guide/topics/resources/drawable-resource.html#Bitmap

*5:個人的にはすごく使いづらいのでFireworksで全部描いちゃってます

Protocol Buffersを使う

ProtocolBuffers

protobuf - Project Hosting on Google Code
http://code.google.com/p/protobuf/
ProtocolBuffersはシリアライズライブラリです。
バージョン2.4.0aを使ってみます。

ダウンロード

http://code.google.com/p/protobuf/downloads/list
ここからダウンロード

ビルド

C++のところをビルド

$ ./configure
$ make
$ make check
$ sudo make install

jarつくる

cd java
mvn package

targetフォルダ内にprotobuf-java-2.4.0a.jarというファイルができていると思います

eclipseでプロジェクト作成

普通にjavaプロジェクトを作成
libsフォルダを作成し、中にprotobuf-java-2.4.0a.jarを入れてビルドパスに追加する

シリアライズ化するクラスの作成

ProtocolBuffersはシリアル化するクラスのprotoファイルを書く必要があります

Language Guide - Protocol Buffers - Google Code 
http://code.google.com/intl/ja/apis/protocolbuffers/docs/proto.html

このprotoファイルはIDLって言語で書くみたいです。
androidでserviceとの通信インターフェイスを定義するaidlファイルもIDLですね

インタフェース記述言語 - Wikipedia
http://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9%E8%A8%98%E8%BF%B0%E8%A8%80%E8%AA%9E
EmployeeTranslater.proto
package jp.tomorrowkey;

message Employee {
  required string name;
  required int32 age;
}

ファイル名とメッセージ名は違うやつにしないと怒られます

クラスに変換

プロジェクトルートで以下をコマンドする

protoc --java_out=src src/jp/tomorrowkey/EmployeeTranslater.proto

するとsrc/jp/tomorrowkeyにEmployeeTranslater.javaができあがります。

クラス変換を楽にするために

protoclipseというeclipse pluginがあります
これを使うとproto→javaの変換をjavaのビルドのタイミングで自動でやってくれます。

protoclipse - Project Hosting on Google Code 
http://code.google.com/p/protoclipse/

インストールが終わったらprotocの場所を設定したあとProtocolBuffersを使いたいプロジェクトで
右クリックして[Add Google ProtoBuf Compiler Nature]を選べば完了です。
Buildersの中にProtocolBuffersの変換する処理が追加されてると思います。

コードを書く

import com.google.protobuf.InvalidProtocolBufferException;

public class Main {
  public static void main(String[] args) {
    EmployeeTranslater.Employee.Builder builder = EmployeeTranslater.Employee.newBuilder();
    builder.setName("taro");
    builder.setAge(25);
    EmployeeTranslater.Employee src = builder.build();

    // シリアライズ
    byte[] buffer = src.toByteArray();

    // 表示してみる
    for (byte b : buffer) {
      System.out.print(Integer.toHexString(b & 0xFF) + " ");
    }
    System.out.println();

    // デシリアライズ
    EmployeeTranslater.Employee dst = null;
    try {
      dst = EmployeeTranslater.Employee.parseFrom(buffer);
    } catch (InvalidProtocolBufferException e) {
      dst = null;
    }

    // ちゃんと復元されたかな?
    if (dst != null) {
      System.out.println("name:" + dst.getName());
      System.out.println("age:" + dst.getAge());
    } else {
      System.out.println("dst is null");
    }
  }
}

実行

a 4 74 61 72 6f 10 19 
name:taro
age:25

やったー!シリアライズされたー!

MessagePack for Javaを使う

MessagePack

The MessagePack Project
http://msgpack.org/

MessagePackはシリアライズライブラリです。

バージョン0.5.1を使ってみます

ビルド

ビルドにはMavenが必要です。
ダウンロードしてパス通せば使えます。

Maven - Welcome to Apache Maven 
http://maven.apache.org/

あとはこれを打てばおっけー

$ git clone https://github.com/msgpack/msgpack.git
$ cd msgpack/java/
$ mvn package
$ mvn install

targetフォルダの中にmsgpack-0.5.1-devel.jarができてると思います

eclipseの設定

[環境設定][java][Build Path][Classpath Variables][New...]

$HOME/.m2/repository
を追加

eclipseでプロジェクト作成

普通にjavaプロジェクトを作成
libsフォルダを作成し、中にmsgpack-0.5.1-devel.jarを入れてビルドパスに追加する
.classpathを開き以下を追加する





コードを書く

import org.msgpack.MessagePack;
import org.msgpack.annotation.MessagePackMessage;

public class Main {

  /**
   * シリアライズするクラス
   * MessagePackMessageアノテーションをつけるだけでおっけー
   */
  @MessagePackMessage
  public static class Employee {

    // publicなフィールドでないとシリアライズされない
    public String name;

    // publicなフィ(ry
    public int age;

    public Employee() {
      // デフォルトコンストラクタないと怒られます
    }

    public Employee(String name, int age) {
      this.name = name;
      this.age = age;
    }
  }

  public static void main(String[] args) {
    Employee src = new Employee("taro", 25);

    // シリアライズ
    byte[] buffer = MessagePack.pack(src);

    // 表示してみる
    for (byte b : buffer) {
      System.out.print(Integer.toHexString(b & 0xFF) + " ");
    }
    System.out.println();

    // デシリアライズ
    Employee dst = MessagePack.unpack(buffer, Employee.class);

    // ちゃんと復元されたかな?
    System.out.println("name:" + dst.name);
    System.out.println("age:" + dst.age);
  }
}

実行

92 a4 74 61 72 6f 19 
name:taro
age:25

やったー!シリアライズされたー!

キーリピートを実装する

Buttonクラスを拡張して、長押しされている場合クリック動作を呼び出すようにします。

RepeatButton.java
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.widget.Button;

public class RepeatButton extends Button implements OnLongClickListener {

  /**
   * 連続してボタンを押す間隔(ms)
   */
  private static final int REPEAT_INTERVAL = 100;

  /**
   * 連打フラグ
   */
  private boolean isContinue = true;

  /**
   * ハンドラ
   */
  private Handler handler;

  public RepeatButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    setOnLongClickListener(this);
    handler = new Handler();
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    super.onTouchEvent(event);

    // キーから指が離されたら連打をオフにする
    if (event.getAction() == MotionEvent.ACTION_UP) {
      isContinue = false;
    }

    return true;
  }

  @Override
  public boolean onLongClick(View v) {
    isContinue = true;

    // 長押しをきっかけに連打を開始する
    handler.post(repeatRunnable);

    return true;
  }

  Runnable repeatRunnable = new Runnable() {
    @Override
    public void run() {
      // 連打フラグをみて処理を続けるか判断する
      if (!isContinue) {
        return;
      }

      // クリック処理を実行する
      performClick();

      // 連打間隔を過ぎた後に、再び自分を呼び出す
      handler.postDelayed(this, REPEAT_INTERVAL);
    }
  };
}

使用例

こんな感じで使えばNumberPicker的に使えます

イメージ

MainActivity.java
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;

public class MainActivity extends Activity implements OnClickListener {

  private TextView txtNumber;

  private RepeatButton btnIncrement;

  private RepeatButton btnDecrement;

  private int number = 0;

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

    txtNumber = (TextView)findViewById(R.id.txtNumber);
    txtNumber.setText(String.valueOf(number));
    btnIncrement = (RepeatButton)findViewById(R.id.btnIncrement);
    btnIncrement.setOnClickListener(this);
    btnDecrement = (RepeatButton)findViewById(R.id.btnDecrement);
    btnDecrement.setOnClickListener(this);
  }

  @Override
  public void onClick(View v) {
    switch (v.getId()) {
      case R.id.btnIncrement: {
        increment();
        break;
      }
      case R.id.btnDecrement: {
        decrement();
        break;
      }
    }
  }

  private void increment() {
    number++;
    txtNumber.setText(String.valueOf(number));
  }

  private void decrement() {
    number--;
    txtNumber.setText(String.valueOf(number));
  }
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:gravity="center_vertical|center_horizontal"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <view
    class="jp.tomorrowkey.android.repeatpushbutton.RepeatButton"
    android:id="@+id/btnIncrement"
    android:text="Increment"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
  <TextView
    android:id="@+id/txtNumber"
    android:text="0"
    android:textSize="32sp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
  <view
    class="jp.tomorrowkey.android.repeatpushbutton.RepeatButton"
    android:id="@+id/btnDecrement"
    android:text="Decrement"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
</LinearLayout>

独自Viewの定義の仕方

独自ViewのXMLの定義の仕方は2つあります

viewタグを使う場合
<view
  class="jp.tomorrowkey.android.repeatpushbutton.RepeatButton"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content" />
クラス名でタグを書く場合
<jp.tomorrowkey.android.repeatpushbutton.RepeatButton
  android:layout_width="wrap_content"
  android:layout_height="wrap_content" />

サンプルソースではクラス名をタグに書くパターンをよく見ます。
2つの違いはというと
viewタグを使う場合ではviewのもっている属性名(android:layout_widthやandroid:idなど)を補完してくれます。
機能的に違いはないのでviewタグを使った方がコーディングは楽です。

暗号本メモ

[twitter:@inuchin] さんオススメ

図解入門よくわかる最新暗号技術の基本と仕組み (How‐nual Visual Guide Book)

図解入門よくわかる最新暗号技術の基本と仕組み (How‐nual Visual Guide Book)

  • 古いけど入門書にいいらしい

新版暗号技術入門 秘密の国のアリス

新版暗号技術入門 秘密の国のアリス

  • レビューもいい感じだし、ポチッた

[twitter:@hamatz] さんオススメ

暗号技術大全

暗号技術大全

  • 辞書的に使える
  • かなりオススメらしい
  • 詳しくなったらほしいな
  • 欠点:重い

最新 暗号技術 (NTT R&D 情報セキュリティシリーズ)

最新 暗号技術 (NTT R&D 情報セキュリティシリーズ)

  • 入門に良さそう?
  • レビューがなくて判断しづらい

暗号解読―ロゼッタストーンから量子暗号まで

暗号解読―ロゼッタストーンから量子暗号まで

  • 暗号の勉強というより暗号の歴史を読む
  • モチベーションあげるための本

[twitter:@keiji_ariyama] さんオススメ

Javaで作って学ぶ暗号技術 - RSA,AES,SHAの基礎からSSLまで

Javaで作って学ぶ暗号技術 - RSA,AES,SHAの基礎からSSLまで

  • javaで実装されてるならコード読めるし、分かりやすいかも

改訂新版 暗号の数理―作り方と解読の原理 (ブルーバックス)

改訂新版 暗号の数理―作り方と解読の原理 (ブルーバックス)

  • 内容がきになる。
  • 暗号解読と同じく歴史が読めるかも?