@blog.justoneplanet.info

日々勉強

androidのWebViewで取得したCookieをHTTP通信に使う

そろそろ忘れそうだからメモしておく。

■WebViewで取得したCookieの保存

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.web);
        
        webView = (WebView) findViewById(R.id.webview);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setBuiltInZoomControls(false);
        webView.setHorizontalScrollBarEnabled(false);
        webView.setWebViewClient(new WebViewClient(){
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                // cookie取得後にwebviewのloadingをstopしたい場合などはこちらで処理した方が良い
            }
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon){
                // URLがログアウトの時SharedPreferenceに保存されているCookieを削除する
                if(url.indexOf(LOGOUT) > -1){
                    SharedPreferences.Editor editor = getSharedPreferences(PREF_KEY, 0).edit();
                    editor.putString(LOGIN_KEY, "");
                    editor.commit();
                }
            }
            
            @Override
            public void onPageFinished(WebView view, String url){
                // 自ドメインの時CookieをCheckする
                if(url.indexOf(DOMAIN) > -1){
                    String cookie = CookieManager.getInstance().getCookie(url);// 文字列でCookieを取得
                    String[] oneCookie = cookie.split(";");
                    for(String pair : oneCookie){
                        pair = pair.trim();
                        String[] set = pair.split("=");
                        set[0] = set[0].trim();
                        // ログイン用Cookie名で
                        // 値がセットされているとき
                        // SharedPreferenceにログイン用tokenを保存する
                        if(set.length > 1 && set[0].equals(LOGIN_KEY) && !set[1].equals("")){
                            set[1] = set[1].trim();// 前後空白文字の削除
                            Editor editor = getSharedPreferences(PREF_KEY, MODE_PRIVATE).edit();
                            editor.putString(LOGIN_KEY, set[1]);
                            editor.commit();
                        }
                    }
                }
            }
        });
        webView.loadUrl(URL);
    }

■保存したCookieを使ってHTTP通信

DefaultHttpClient httpClient = new DefaultHttpClient();
BasicClientCookie bCookie = new BasicClientCookie(
    LOGIN_KEY,
    getSharedPreferences(
        PREF_KEY,
        MODE_PRIVATE
    ).getString(LOGIN_KEY, "")
);
HttpGet get = new HttpGet(URL);
byte[] result = null;
String str = "";
try {
    // Cookieを取得してhttpにbind
    CookieStore store = new BasicCookieStore();
    bCookie.setDomain(DOMAIN);
    bCookie.setPath("/");
    store = httpClient.getCookieStore();
    store.addCookie(bCookie);
    HttpContext httpContext = new BasicHttpContext();
    httpContext.setAttribute(ClientContext.COOKIE_STORE, store);
    
    // 通信->結果の取得
    HttpResponse response = httpClient.execute(get, httpContext);
    StatusLine statusLine = response.getStatusLine();
    if (statusLine.getStatusCode() == HttpURLConnection.HTTP_OK) {
        result = EntityUtils.toByteArray(response.getEntity());
        str = new String(result, "UTF-8");
    }
}
catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}
catch (ClientProtocolException e) {
    e.printStackTrace();
}
catch (IOException e) {
    e.printStackTrace();
}
return str;

HTTPヘッダを読み取る

public class WebViewActivity extends Activity {
    WebView webView;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        webView = new WebView(this);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setBuiltInZoomControls(false);
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean  shouldOverrideUrlLoading (WebView  view, String urlStr) {
                Log.e("shouldOverrideUrlLoading", urlStr);
                URL url;
                URLConnection connection;
                try {
                    url = new URL(urlStr);
                    connection = url.openConnection();
                    connection.setConnectTimeout(3000);
                    connection.connect();
                    int size = connection.getContentLength();
                }
                catch (MalformedURLException e) {
                    e.printStackTrace();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
                
                /**
                 * HTTP通信を実行して
                 * responseからheaderを読み取り
                 * ストリームから内容を読み出す
                 */
                String htmlContent = "";
                HttpGet httpGet = new HttpGet(urlStr);
                HttpResponse response;
                HttpClient client = new DefaultHttpClient();
                try {
                    response = client.execute(httpGet);
                    Header[] headers = response.getAllHeaders();
                    for (int i = 0; i < headers.length; i++) {
                        Log.e("header", headers[i].getName() + ":" + headers[i].getValue());
                    }
                    if (response.getStatusLine().getStatusCode() == 200) {
                        HttpEntity entity = response.getEntity();
                        if (entity != null) {
                            InputStream inputStream = entity.getContent();
                            htmlContent = convertToString(inputStream);
                        }
                    }
                }
                catch (ClientProtocolException e) {
                    e.printStackTrace();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
                view.loadDataWithBaseURL(urlStr, htmlContent, "text/html", "utf-8", "");
                return true;
            }
            
            private String convertToString(InputStream inputStream) {
                StringBuffer buffer = new StringBuffer();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                String line;
                try {
                    while((line = reader.readLine()) != null) {
                        buffer.append(line + "\n");
                    }
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
                return buffer.toString();
            }
            
            @Override
            public void onPageStarted(WebView webView, String url, Bitmap favicon) {
                super.onPageStarted(webView, url, favicon);
                Log.e("onPageStarted", url);
            }
        });
        webView.loadUrl("http://www.google.co.jp");
        setContentView(webView);
    }

    /**
     * キーイベントが発火した時
     */
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {// 戻るボタン
            if (event.getAction() == KeyEvent.ACTION_DOWN) {// 押された時
                webView.goBack();// WebViewを戻す
            }
            return true;
        }
        return super.dispatchKeyEvent(event);
    }
}
public boolean shouldOverrideUrlLoading (WebView view, String url)

Give the host application a chance to take over the control when a new url is about to be loaded in the current WebView. If WebViewClient is not provided, by default WebView will ask Activity Manager to choose the proper handler for the url. If WebViewClient is provided, return true means the host application handles the url, while return false means the current WebView handles the url.

ホストアプリケーションに現在のWebViewに新しいURLが読み込まれる時にコントロールを取るチャンスを与える。もし、WebViewClientが与えられていない場合、デフォルトではURLに適切なハンドラを選択するようにActivity Managerに依頼する。もし、WebViewClientが与えられている場合、trueを返すとホストアプリケーションがURLを操作し、falseを返すと現在のWebViewがURLを操作する。

ちなみにここで、loadUrl、loadData、loadDataWithBaseURLなどの処理を行わない場合、WebViewに遷移先のページがロードされない。

loadData(String data, String mimeType, String encoding)

Load the given data into the WebView using a ‘data’ scheme URL.
Note that JavaScript’s same origin policy means that script running in a page loaded using this method will be unable to access content loaded using any scheme other than ‘data’, including ‘http(s)’. To avoid this restriction, use loadDataWithBaseURL() with an appropriate base URL.
If the value of the encoding parameter is ‘base64’, then the data must be encoded as base64. Otherwise, the data must use ASCII encoding for octets inside the range of safe URL characters and use the standard %xx hex encoding of URLs for octets outside that range. For example, ‘#’, ‘%’, ‘\’, ‘?’ should be replaced by %23, %25, %27, %3f respectively.
The ‘data’ scheme URL formed by this method uses the default US-ASCII charset.
If you need to set a different charset, you should form a ‘data’ scheme URL which explicitly specifies a charset parameter in the mediatype portion of the URL and call loadUrl(String) instead. Note that the charset obtained from the mediatype portion of a data URL always overrides that specified in the HTML or XML document itself.

与えられたデータをdataスキームURLを使ってWebViewに読み込む。
JavaScriptのsame origin policyは、このメソッドを使って読込されたスクリプトが走っているページが、dataやhttp(s)以外のどんなスキームを使ったコンテンツにアクセスできないであろう事を意味する。この制限を避けるために、適切なURLとともにloadDataWithBaseURL()を使うように。もし、エンコーディングがbase64の場合、データはbase64としてエンコードされる必要がある。その他にも、データが安全なURL文字の範囲で8ビットのASCIIエンコーディングを使わなければならず、安全な範囲外の文字列に対してはスタンダードな%xx hexエンコーディングを用いなければならない。例えば’#’, ‘%’, ‘\’, ‘?’は%23, %25, %27, %3fに置き換えられる必要がある。
このメソッドでdataスキームはUS-ASCIIを使う。もし、他の文字コードをセットする場合、URLのmediatype部分の文字コードを明記するdataスキームURLを用いて、loadUrlを用いるべきである。data URLのmediatype部分から手にいれた文字コードはHTMLやXMLのドキュメントに明記された(文字コード)をいつも上書きすることを覚えておくように。

loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl)

Load the given data into the WebView, using baseUrl as the base URL for the content. The base URL is used both to resolve relative URLs and when applying JavaScript’s same origin policy. The historyUrl is used for the history entry.
Note that content specified in this way can access local device files (via ‘file’ scheme URLs) only if baseUrl specifies a scheme other than ‘http’, ‘https’, ‘ftp’, ‘ftps’, ‘about’ or ‘javascript’.
If the base URL uses the data scheme, this method is equivalent to calling loadData() and the historyUrl is ignored.

baseUrlをコンテンツのURLとして、与えられたデータをWebViewに読み込む。base URLは相対URLを解決したりJavaScriptのsame originポリシーを適用するときに用いられる。historyUrlは履歴に使用される。
baseUrlが’http’, ‘https’, ‘ftp’, ‘ftps’, ‘about’ or ‘javascript’うがいのスキームで明記している場合に限って、この手法で明記されたコンテントはローカルのデバイスのファイルにアクセスできる。
base URLがdata schemeを使っている場合、このメソッドはloadDataを呼ぶのと等価でありhistoryUrlは無視される。

loadUrl(String url)

Load the given url.

与えられたURLを読み込む。

SimpleCursorTreeAdapterのgetChildrenCursorが生成時にも実行される

android 2.3未満で起こった挙動のメモ。android 2.3以降では起こらない。

メモ

以下のようにして開閉可能なListActivityを定義する。

public class KaomojiFavorite extends ExpandableListActivity implements FavoriteHelper.Listener {
    private FavoriteHelper favoriteHelper;
    private CategoryHelper categoryHelper;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getExpandableListView().setCacheColorHint(0);// scroll時の背景
        
        // カテゴリを取得
        categoryHelper = new CategoryHelper(getApplicationContext(), null);
        Cursor categoryCursor = categoryHelper.getCursor();
        startManagingCursor(categoryCursor);
        
        // お気に入りを取得してlistにセット
        favoriteHelper = new FavoriteHelper(getApplicationContext(), null);
        ExpandableListAdapter adapter = favoriteHelper.getSimpleAdapter(this, categoryCursor);
        setListAdapter(adapter);

    }

カテゴリ毎にお気に入りを表示する仕組みだ。FavoriteHelperクラスの実装は以下のようになっている。

    public ExpandableListAdapter getSimpleAdapter(Listener listener, Cursor categoryCursor)
    {
        mListener = listener;
        ExpandableListAdapter adapter = new ExpandableListAdapter(
                mContext,
                categoryCursor,
                R.layout.category,
                new String []{"_id", "name"},
                new int []{R.id.list_id, R.id.list_category},
                R.layout.list,
                new String []{"item", "_id"},
                new int []{R.id.list_item, R.id.list_id}
        );
        return adapter;
    }
    
    /**
     * categoryデータ(Cursor)を元にしてExpandableListActivity用のAdapterを生成する
     * @author justoneplanet
     */
    public class ExpandableListAdapter extends SimpleCursorTreeAdapter {
        
        public ExpandableListAdapter(Context context, Cursor groupCursor,
                int groupLayout, String[] groupFrom, int[] groupTo,
                int childLayout, String[] childFrom, int[] childTo) {
            super(context, groupCursor, groupLayout, groupFrom, groupTo, childLayout, childFrom, childTo);
        }
        
        /**
         * カテゴリ用のCursorからIDを取得して子データを検索取得する
         * android 2.2以前ではアプリ起動時にも実行される
         */
        @Override
        protected Cursor getChildrenCursor(Cursor groupCursor) {
            final long idCategory = groupCursor.getLong(groupCursor.getColumnIndex("_id"));
            SQLiteDatabase sdb = getReadableDatabase();
            Cursor cursor = sdb.query(
                    "favorite",
                    new String[]{"_id", "id_category", "item"},
                    "id_category = ?",
                    new String[]{String.valueOf(idCategory)},
                    null,
                    null,
                    null,
                    null
            );
            mListener.onGetChildrenCursor(cursor);
            return cursor;
        }
    }

以上。めも。

Objective-CでSingleton

■実装

#import <Foundation/Foundation.h>

@interface TapManager : NSObject {
}
+ (id)instance;
+ (id)allocWithZone:(NSZone *)zone;
- (id)copyWithZone:(NSZone *)zone;
- (id)retain;
- (unsigned)retainCount;
- (void)release;
- (id)autorelease;
- (void)registerWithFace:(NSString *)face withTag:(NSString *)tag;
@end
#import "TapManager.h"
#import "HistoryDB.h"
#import "Util.h"

@implementation TapManager
static TapManager *_instance = nil;

+ (TapManager *)instance {
    @synchronized(self) {
        if (_instance == nil) {
            [[self alloc] init];//代入はしない
        }
    }
    return _instance;
}

// zoneからオブジェクトを生成する
+ (id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];// 最初のみ代入する
            return _instance;// 最初のみ値を返す
        }
    }
    return nil;
}

- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (id)retain {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;
}

- (void)release {
}

- (id)autorelease {
    return self;
}

- (void)registerWithFace:(NSString *)face withTag:(NSString *)tag {
    UIPasteboard *pb = [UIPasteboard generalPasteboard];
    [pb setValue:face forPasteboardType:@"public.utf8-plain-text"];
    [Util showAlert:@"Hello" text:@"World"];
    // ...その他の処理
}
@end

NSNullとnilを判定する

■失敗例1

UITableViewのcellを設定する部分で以下のようなコードを書きクラッシュした。

NSString *tag = (NSString *)[row objectForKey:@"tag"];
[cell.tag setText:tag];
return cell;

■失敗例2

nilの場合があるなと考えて以下のようにする。

NSString *tag = (NSString *)[row objectForKey:@"tag"];
if (tag == nil) {
    [cell.tag setText:@""];
}
else {
    [cell.tag setText:tag];
}
return cell;

残念ながらクラッシュは依然として起こる。

■最終版

tagはNSNullであり以下の判定に修正。

NSString *tag = (NSString *)[row objectForKey:@"tag"];
if (tag == nil || [tag isEqual:[NSNull null]]) {
    [cell.tag setText:@""];
}
else {
    [cell.tag setText:tag];
}
return cell;

参考

NSNullは配列の要素のようなオブジェクトしか許されない所でnilを表すのに用いられる

CentOSにjenkinsをインストールする

以下のコマンドでインストールできる。

yum install java-1.6.0-openjdk
wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
yum install jenkins

デフォルトのJENKINS_HOMEは以下のようになる。

/var/lib/jenkins

■ポート変更

以下のコマンドで設定ファイルを編集する。

vim /etc/sysconfig/jenkins

デフォルトでは8080番を使用しているので以下のように変更する。

JENKINS_PORT="1234"

■起動

/sbin/service jenkins start

以下のアドレスでアクセスできる。

http://localhost:8080/

■セキュリティ

以下のURLにアクセスする。

http://localhost:8080/configure

デフォルトではすべてのユーザが閲覧可能でありジョブを追加することができるので、「セキュリティを有効化」の項目にチェックを入れる。

特定のユーザのみが操作可能にする

Jenkinsのユーザーデータベース

Jenkinsのユーザーデータベースを選択し、ユーザーにサインアップを許可を外して一度保存する。

権限管理

行列による権限設定を選択し、上述で追加したユーザ名に対して全権限を与える。また、匿名ユーザには必要な権限だけ付加する。

Unixユーザを使用した管理の方が便利かもしれない。

■E-mail通知

以下のURLにアクセスする。

http://localhost:8080/configure

SMTP認証にチェックを入れ以下のように入力する。

SMTPサーバー
mail.example.com
E-mailのサフィックス
@example.com
送信元メールアドレス
john@sample.com
ユーザ名
mike
パスワード
mike’s_pasword
SMTPポート
587

メールを送信して設定を確認にチェックを入れて実際にメールが送信できるか確認する。

■JDKのインストール

以下のURLにアクセスする。

http://localhost:8080/configure

JDKの項目があるので入力して保存する。oracleアカウントが面倒。

■Git連携

以下の画面にアクセスする。

http://localhost:8080/pluginManager/available

Git Pluginを選択し「ダウンロード後に再起動しインストール」を選択する。

gitの設定

http://localhost:8080/job/SampleProject/configure

android

以下にアクセスしてAndroid Emulator Pluginをインストールする。

http://localhost:8080/pluginManager/available

logを使って大きな数の桁数を求める

ちょっと懐かしいのでプログラムと関係ないが解いてみた。

■条件

以下の条件が与えられている。

  • log102 = 0.3010
  • log103 = 0.4771
  • log107 = 0.8451

■3の100乗の桁数

  1. log103100 = 100log103 = 47.71
  2. 47 < log103100=47.71 < 48
  3. 1047 < 3100 < 1048

47桁

■3の100乗の最高位の数字

  1. log103100 = 47.71
  2. 3100 = 1047.71 = 1047 * 100.71

従って、100.71がわかれば良い。

  1. 100.3010 = 2
  2. 100.4771 = 3
  3. 100.8451 = 7

3と7の間という事は分かったがそれだけではどうしようもない。

  1. 1 = 100
  2. 4 = 100.3010 * 100.3010 = 100.6020
  3. 6 = 100.3010 * 100.4771 = 100.7781
  4. 9 = 100.4771 * 100.4771 = 100.9542

8と5については少しひねる。

  1. log108 = log1023 = 3log102
  2. 8 = 100.3010 * 3 = 100.9030
  3. log105 = log1010/2 = log1010 – log102
  4. 5 = 101 – 0.3110 = 100.6990

5 < 100.71 < 6なので5が最高位の数字。

androidでTouchUtilsを使ってテストをする

以下のようにしてTouchUtilsを使用することができる。

public void testPushListByTouchUtils()
{
    LinearLayout item = (LinearLayout) mActivity.getListView().getChildAt(0);
    //TouchUtils.tapView(this, item);// 画面に触れている状態
    TouchUtils.clickView(this, item);// 画面から指が離れた状態
    String face = (String) ((TextView) item.findViewById(R.id.list_face)).getText();
    ClipboardManager cm = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
    assertEquals(face, cm.getText());
}

SecurityExceptionなどが発生すると書いてある文献も見られるが自分の環境では発生しなかった。但し、Viewの選択が間違っていてnullであったりすると発生する(なぜNullPointer〜でないのか…)。ちなみに同様のテストケースをTouchUtilsを使用せずに書くと以下のようになる。

public void testPushList()
{
    mActivity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            LinearLayout item = (LinearLayout) mActivity.getListView().getChildAt(0);
            mActivity.getListView().performItemClick(item, position, 0));
            String face = (String) ((TextView) item.findViewById(R.id.list_face)).getText();
            ClipboardManager cm = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
            MainActivityTest.assertEquals(face, cm.getText());
        }
    });
    mInstrumentation.waitForIdleSync();
}

TouchUtilsは別アプリがタップするようなイメージで、Viewに対して発生させるイメージだ。従って、performItemClickはUIThreadで実行する必要がある。

AsyncQueryHandlerを使ってSQLiteのクエリを非同期処理する

特に重いクエリは使ってないんだが、とりあえずAsyncQueryHandlerを使ってSQLiteのクエリを非同期で処理する。

■実装

以下のようにmanifestファイルのapplication要素内にproviderを追加する。

<provider 
    android:name=".Provider"
    android:authorities="info.justoneplanet.android.sample.provider"<!--アクセスするためのURI-->
    android:exported="false"/><!--非公開にしないと他のアプリからアクセスできてしまう-->

セキュリティには注意する。

Helper.java

class Helper extends SQLiteOpenHelper {
    private static final int VERSION = 1;
    private static final String FILENAME   = "info.justoneplanet.android.sample.db";
    static final String TABLENAME   = "tbl";
    static final String ID          = "_id";
    static final String CREATED     = "created";
    static final String NAME        = "name";
    static Helper INSTANCE = null;
    
    private Helper(Context context, CursorFactory factory) {
        super(context, FILENAME, factory, VERSION);
    }
    public static Helper getInstance(Context context, CursorFactory factory) {
        if (INSTANCE == null) {
            INSTANCE = new Helper(context, factory);
        }
        return INSTANCE;
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(
            "CREATE TABLE `" + TABLENAME + "`(" +
            " `" + ID         + "` INTEGER PRIMARY KEY AUTOINCREMENT," +
            " `" + CREATED    + "` INTEGER," +
            " `" + NAME       + "` TEXT" +
            ");"
        );
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

Provider.java

以下のようにContentProviderクラスを継承したProviderクラスを作る。

public class Provider extends ContentProvider {
    @Override
    public String getType(Uri uri) {
        return null;
    }
    @Override
    public boolean onCreate() {
        return false;
    }
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Helper helper = Helper.getInstance(getContext(), null);
        SQLiteDatabase sdb = helper.getReadableDatabase();
        Cursor cursor = sdb.query(
                Helper.TABLENAME,
                new String[]{Helper.ID, Helper.NAME, Helper.CREATED},
                selection,
                selectionArgs,
                null,
                null,
                sortOrder,
                null
        );
        return cursor;
    }
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Helper helper = Helper.getInstance(getContext(), null);
        SQLiteDatabase sdb = helper.getWritableDatabase();
        sdb.insert(Helper.TABLENAME, null, values);
        getContext().getContentResolver().notifyChange(uri, null);
        return null;
    }
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        Helper helper = Helper.getInstance(getContext(), null);
        SQLiteDatabase sdb = helper.getWritableDatabase();
        int rows = sdb.update(Helper.TABLENAME, values, selection, selectionArgs);
        getContext().getContentResolver().notifyChange(uri, null);
        return rows;
    }
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        Helper helper = Helper.getInstance(getContext(), null);
        SQLiteDatabase sdb = helper.getWritableDatabase();
        int rows = sdb.delete(Helper.TABLENAME, selection, selectionArgs);
        getContext().getContentResolver().notifyChange(uri, null);
        return rows;
    }
}

Table.java

public class Table {
    private static final Uri URI = Uri.parse("content://info.justoneplanet.android.sample.provider/");
    private Context mContext;
    
    public Table(Context context) {
        mContext = context;
    }
    
    /**
     * データを取得する
     * @return ListActivity用のadapter
     */
    public void load(final LoadObserver observer)
    {
        AsyncQueryHandler handler = new AsyncQueryHandler(mContext.getContentResolver()) {
            @Override
            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
                super.onQueryComplete(token, cookie, cursor);
                observer.onLoadCursor(cursor);
            }
        };
        handler.startQuery(0, null, URI, null, null, null, Helper.CREATED + " DESC");
    }
    /**
     * 保存する
     * @return
     */
    public void add(String name) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(Helper.NAME, name);
        contentValues.put(Helper.CREATED, new Date().getTime());
        AsyncQueryHandler handler = new AsyncQueryHandler(mContext.getContentResolver()) {
            @Override
            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
                super.onQueryComplete(token, cookie, cursor);
            }
        };
        handler.startInsert(0, null, URI, contentValues);
    }
    /**
     * 一定時間経過したものを削除する
     * @param created
     * @return
     */
    public void delete(long created) {
        AsyncQueryHandler handler = new AsyncQueryHandler(mContext.getContentResolver()) {
        };
        handler.startDelete(0, null, URI, Helper.CREATED + " < ?", new String[]{String.valueOf(created)});
    }
    /**
     * helper経由でdbをcloseする
     */
    public void close() {
        Helper.getInstance(mContext, null).close();
    }
    public interface LoadObserver {
        public void onLoadCursor(Cursor cursor);
    }
}

CursorからAdapterを生成する

■実装

以下のようにすることでListActivityで使うadapterをcursorから直接生成できる。

SQLiteDatabase sdb = getReadableDatabase();
Cursor cursor = sdb.query(
        "table",
        new String[]{"_id", "name", "address", "created"},
        null,
        new String[]{},
        null,
        null,
        "created DESC",
        null
);

// Cursorを元にしてListActivity用のadapterを生成する
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
        mContext,
        R.layout.list,
        cursor,// datasource
        new String[]{"name", "address"},// データベースカラム名...(a)
        new int[]{R.id.list_face, R.id.list_tag}// (a)に対応するTextViewのid
);

上述の方法ではSQLiteで取得したデータになんらかの文字列を加えたりすることはできない。

■表示データの加工

以下のようにSimpleCursorAdapterクラスを継承することでデータを加工して表示できる。

static class ArrangeListAdapter extends SimpleCursorAdapter {
    public ArrangeListAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
        super(context, layout, c, from, to);
        setViewBinder(new ArrangeListViewBinder());
    }
    
    static class ArrangeListViewBinder implements SimpleCursorAdapter.ViewBinder {
        @Override
        public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
            if (columnIndex == cursor.getColumnIndex("name")) {// nameの時に文字列を付加する
                String p = cursor.getString(columnIndex);// viewにはまだattachされていないのでcursorから取得
                ((TextView) view).setText(p + " さん");
                return true;
            }
            return false;
        }
    }
}

androidでJUnitを使ってテストする

秋葉原Androidアプリ開発勉強会に行ってきたヽ(•̀ω•́ )ゝ✧自分の題材はユニットテスト。

iOSアプリケーションではテストをするためにターゲットを追加する。androidではテストするためにプロジェクトを作る。

■テストプロジェクト

Package Explorerで右クリック「New > Others > Android Test Project」を選択し、Test Targetに既存のアプリを指定して「Finish」をクリックする。

■テストクラス

パッケージで右クリック、「New > Class」を選択して以下のように入力する。

Name MainActivityTest
Superclass android.test.ActivityInstrumentationTestCase2<MainActivity>

MainActivityをimportし、コンストラクタの引数を除去して以下のようにする。

public MainActivityTest() {
    super("info.justoneplanet.android.mainactivity", MainActivity.class);
    setActivityInitialTouchMode(false);
}

■テストコード

以下のようにとりあえず書いてみた。

package info.justoneplanet.android.sample.test;

import info.justoneplanet.android.sample.MainActivity;
import android.app.Instrumentation;
import android.content.Context;
import android.test.ActivityInstrumentationTestCase2;
import android.text.ClipboardManager;
import android.util.Log;
import android.widget.LinearLayout;
import android.widget.TextView;
import info.justoneplanet.android.sample.R;

public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity>
{
    private MainActivity mActivity;
    private Instrumentation mInstrumentation;

    public MainActivityTest() {
        super("info.justoneplanet.android.mainactivity", MainActivity.class);
        setActivityInitialTouchMode(false);
    }
    
    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        mActivity = getActivity();
        mInstrumentation = getInstrumentation();
    }
    
    @Override
    protected void tearDown() throws Exception
    {
        super.tearDown();
        mActivity = null;
        mInstrumentation = null;
    }
    
    /**
     * testPushList
     * まあサンプル
     */
    public void testPushList()
    {
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // テスト対象アプリのUIを制御するコードはこっちに書くよ
                MainActivityTest.assertEquals(true, true);
            }
        });
        mInstrumentation.waitForIdleSync();
        assertEquals(true, true);
    }
}

ちなみに「みんなの顔文字辞典」のテストケース(一部)は以下のようになっている。

/**
 * testPushList
 * タップした部分の顔文字とクリップボードの顔文字が同じかテストする
 */
public void testPushList()
{
    mActivity.runOnUiThread(new Runnable() {
        private int position = 0;
        private LinearLayout item;
        
        /**
         * getClipBoardText
         * クリップボードのテキストを取得して返す
         * @return
         */
        private String getClipBoardText() {
            ClipboardManager cm = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
            Log.e("face", (String) cm.getText());
            return (String) cm.getText();
        }
        
        /**
         * getFaceInListItem
         * ListViewのitemの中に含まれる文字列を返す
         * @param item
         * @return
         */
        private String getFaceInListItem(LinearLayout item) {
            String face = (String) ((TextView) item.findViewById(R.id.list_face)).getText();
            Log.e("face", face);
            return face;
        }
        
        /**
         * getListItemByTappedPosition
         * indexを指定してListViewの中のitemを返す
         * @param position
         * @return LinearLayout item
         */
        private LinearLayout getListItemByTappedPosition(int position) {
            LinearLayout item = (LinearLayout) mActivity.getListView().getChildAt(position);
            Log.e("result", String.valueOf(mActivity.getListView().performItemClick(item, position, 0)));// tap!
            return item;
        }
        
        @Override
        public void run() {
            position = 0;
            item = getListItemByTappedPosition(position);
            KaomojiEveryoneTest.assertEquals(getFaceInListItem(item), getClipBoardText());
            
            position = 5;
            item = getListItemByTappedPosition(position);
            KaomojiEveryoneTest.assertEquals(getFaceInListItem(item), getClipBoardText());
            
            position = 10;
            item = getListItemByTappedPosition(position);
            KaomojiEveryoneTest.assertEquals(getFaceInListItem(item), getClipBoardText());
        }
    });
    mInstrumentation.waitForIdleSync();
}