woshidan's blog

そんなことよりコードにダイブ。

データベーススペシャリストの試験を受けて面白かったのでどんな試験だったか呟いてみる 

こないだデータベーススペシャリストに合格して、解いてる間は自分が冗談を本気に受け取る性質なところもありIPAの試験は受けることすら揶揄される感じがあってつらかったのですが、年度によっては偏ったものもあると思ったものの全体として解いたり調べたりしてて楽しい問題だなぁと思ったので、その内容についてのポエムです。

書いてる人について

一ヶ月で合格しました系の記事を求めている人に申し訳ないんですが、元々DB周りの話が好きで結構アプリケーションで触るSQLとかindexの付け方とかテーブル設計のための業務知識の眺め方の基礎みたいなのは新人並みには勉強してました。

それでもやっぱり全然足りなくて悔しい思いをすることがあったのですが、どこから手をつけていいかわからず、もう少し系統立てて勉強してみたいなという気持ちがあって受験しました。

事前に読んでいた本は

〔入門〕はじめてのデータベース

〔入門〕はじめてのデータベース

書き込み式SQLのドリル 改訂新版

書き込み式SQLのドリル 改訂新版

SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)

SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

SQLアンチパターン

SQLアンチパターン

あたりで、中二病により

エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド

エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド

を持っていましたが、鍵本を使うような事態まで担当したことがあまりなく、幾らかのテーブル設計とindex設計して失敗したーしなかったーって一年くらいやったあと、偉い人の鶴の一声でAndroidに転向した感じで、今回の受験は趣味です。

試験用の勉強期間は2ヶ月くらい。

試験の概要

問題の構成はIPA情報処理技術者の高度試験におなじみの、午前1, 2, 午後1, 2の四部構成です。

基本的には午前を除き、問題で具体的に取り扱うのは、RDBMSだけです。特に午後はそうです。いま、NoSQLのデータベースを全く使っていないサービスは少ないのではないかという時代にRDBMSだけという気持ちもありますが、自分にとっては、十分価値はありました。

午前1は受けていないのでおいておいて、午前2は他の分野も合わせた用語問題と簡単な正規化やトランザクション排他制御などについて問われる形となっていてNoSQLについての用語はここに少しだけ出ます。

個人的に一番豪華なのは午後1で、午後1は試験範囲のいろんな分野がぎゅっと濃縮されており、

  • テーブルの正規化・非正規化に関する検討。具体的に言うと、まず、中途半端に正規化されたテーブルが提示されてそれをどこまで正規化してるか答えさせてから正規化。正規化されてない場合の問題点をいちいち説明させる
  • データベースの移行について、移行用のクエリの形や移行方法を2~3種並べてどの程度時間がかかるか計算させる
  • データベースのトランザクション排他制御について、それっぽいクエリを5つくらい並べてロックがどの組み合わせでかかるか具体的に指摘させる
  • 各種indexの場合のデータ探索時の特性に関して、たとえばクラスタ索引と非クラスタ索引の場合、ページの読み込まれかたが違うから分析用のバッチ処理に使うデータの規模考えて効率的なのどっちですか

などなど、多分、自分と似たような「1~2年開発してみて、ある程度モデル同士の関係を考慮しつつ正規化されたテーブルを設計してn+1などの問題がないクエリは書け、問題のあるテーブルの修正も提案できてrailsの上でSQLをいじって怒られることは減ってきたが、次はなにをやってみたらいいのか具体的によくわからないwebエンジニア」にとって、格好の目標となるのではないでしょうか。いや、いまはあっぷあっぷでこれ解いてるけど、いつかスラスラ説明できるようになりたいと思ったのでなったというか。。

正直時間的にも午後1は制限時間の45分で解くのがしんどいのですが、その分、一問解いてわからなかったことを調べるたびに、わからなかったことに関する用語はこれか、こういう観点があるのかって探検するための地図をもらっているような気持ちでした。鍵本も初めて机上ではあるかもしれませんが、具体的な問題のために取り出せたので満足です。

さて、午後2なんですけど、データベーススペシャリストの試験が受けやすいと言われる同時に国語の試験と揶揄される原因でして、だいたい論理設計1本勝負か、物理設計そのほかを絡めた論理設計から一題選ぶ方式となっています。そして、この論理設計一本勝負の方が問題形式としては大胆で、

  • まず、業務について書かれた長文10ページを読みます
  • 次にそれをふまえて答案用紙にありったけの時間をかけてカラム名とテーブル名を書きなぐります

というワイルドな問題構成であることが多いです。図の記入欄の余白、足りないんですけど??

これよりもっと少ないページ数に収まる要件の一部をなんだかんだ一週間、少なくとも数日は議論して決める自分の知ってる実務からすると、これ120分で読ませて書かせちゃうかーって感じなんですが。。

喉元過ぎた今眺めると「ドメインロジックとデータベースの知識の両方があって初めて正しい設計ができる。知識を生かすために、後々困らないように必要な知識をくまなく汲み取ってこそのプロである。それも、急に人が相談しにきて今日中にって言われた、みたいな場合でもある程度の方向性を示せる上級エンジニアたれ」みたいな、強い圧力を感じます。痺れる(腕が)。

なお、この設定、実際にそのままrailsでアプリケーションがかけちゃいそうな勢いで細かいので、設計の演習や練習用のアプリの設定に困っている人も、無料で公開されていますし一回みてみるといいのではないでしょうか。リレーショナルモデルに沿ったある一派の設計に浴びるように触れる、という体験としても非常に楽しいです。時々でもなく文章量にうんざりしますが、いろんな職場や業界で使われるシステムについて書かれた文章を読むのは社会見学みたいで不謹慎ながら面白かったです。

まとめ

午後1はサーバサイドに戻ることになったら一通り解き直したいレベルでよかったです。午後2で問われている領域には知識以上にビジネスロジックに噛んでいくコミュ力とか信頼とかを築く力といった部分が先立つんだろうな、ということでちょいと虚しさもありましたが。。

やっぱりめっちゃ怖い人からこんなもんって言われそうであれなんですが、闇雲にやるよりこの試験問題で知らないことを教えてもらってよかったよ!っていうのを言いたかったので、現場からは以上です。

iOSでアプリがバックグラウンドへ遷移してもタスクが終了するまではアプリのプロセスをkillさせないようにする

AndroidではAndroid Oからバックグラウンド処理の実行制限が厳しくなることが話題ですが、iOSでは以前から基本的にはバックグラウンド処理はアプリがバックグラウンドに回った時点で停止させられます。

実際にはiOS側の判断で止めるので、タスクがすぐにkillされる状況は少なくともiOS10の段階ではなかなか開発段階の状況では再現できなかったりします*1。しかし、少なくとも実装時はバックグラウンドに回ったタスクはすぐkillされても困らない前提で書く必要があります。

そして、音楽アプリでの再生処理や地図アプリ用の位置情報の取得、ファイルのダウンロードなどバックグラウンドになってもしばらくの間動き続けることが保証されていてほしいタスクはよくあります。

この場合の対処としてできることは3種類紹介されており、それぞれ概要としては、

  1. Foregroundで短いタスクを開始する場合は、アプリがバックグラウンドへ遷移するときにそのタスクが終了するまでの時間を要求することができる
  2. Foregroundでダウンロード処理を開始する場合は、ダウンロード中にアプリが停止ないし終了してもよいようにそのダウンロード処理の管理をシステム側へ手渡すことができる
  3. 特定の種類のタスク(音楽再生など)を支援するためにバックグラウンドで実行する必要がある場合は、その支援タスクに1つ以上のバックグラウンドでの実行モードを宣言することができる

となっています。

今回は、1. のケースについての実装についてメモします。

実装概要

  1. beginBackgroundTaskWithExpirationHandler: を使ってiOSのSystemへバックグラウンドのタスクを実行するための追加の実行時間をリクエストする。引数のブロックには追加の実行時間が終わっても処理が終了していなかった場合の後処理の内容を記述します。最低限書く必要がある内容は、後の項目の 3. に記載するiOSのSystemに対するタスクの終了通知です。
    1. の際に、何のタスク用、あるいはコードのどこでリクエストしたのかの識別するための値として UIBackgroundIdentifierの値を受け取ります。
  2. GCDのキューなどにバックグラウンドで実行したいタスクを渡して実行します。タスクの最後で、 2. の値を引数に endBackgroundTask: を実行して、iOSのSystemに当該タスクは終了したため、そのために実行時間を取らなくていいことを伝えます。

サンプルコード

// https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html より
UIBackgroundIdentifier *bgTask;

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        // リクエストした実行時間が切れてしまい、途中終了する場合に呼ばれる後処理を書く
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        // バックグラウンドに入っても継続して実行したい処理を書く
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    });
}

endBackgroundTask: はUIスレッドからもバックグラウンドスレッドからも呼び出されていますが、ドキュメントによれば問題ありません。

最初、beginBackgroundTaskWithName:expirationHandler:ないしbeginBackgroundTaskWithExpirationHandler:は、公式やQiitaなどにあるサンプルコードの表面だけ見ると applicationDidEnterBackground:メソッドなど、Backgroundに遷移する前のタイミングで一回呼び出せばいいようなバックグラウンドに入る前に一度呼び出すもののように見えました。

しかし、beginBackgroundTaskWithExpirationHandler:によれば、Backgroundに遷移したら中断されたらユーザー体験に影響があるようなタスクのたびにこのメソッドを呼ぶべきだしアプリの実行中のどこで呼ぶこともできる、と書いてあるので、必要なタイミングで begin~end~ が対応するように呼んだほうがわかりやすいかもしれません。

なお、実行中のBackground処理の残り時間が必要な場合は backgroundTimeRemaining プロパティにて確認可能です。

現場からは以上です。

参考

*1:特に、実行時に他のアプリが動いていないシミュレータなどでは(苦笑)。

java.util.TimerをAndroidで使ってみた場合

以前気になったので素振りした時のメモ。AndroidのバージョンはAPI 21です。

まとめ

  • Timer.schedule(TimerTask task, long delay, long interval)delay秒後から、前回のタスクと今回のタスクの実行開始時間の間隔がなるべくintervalになるように定期的にtaskを実行する 
  • Timerのコンストラクタで新しいスレッドが動き出し、定期的に実行されるタイマーのタスクは全てそのスレッドで実行される
  • Activity.onStopが呼び出されてアプリがバックグラウンドへ隠れてもTimerの処理は止まらない

参考:

簡単にコード書いてログで確認してみた

package com.example.woshidan.timertest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Timer timer = new Timer();
        final Random random = new Random();
        final List<Integer> list = new ArrayList<>();
        final long startTime = System.currentTimeMillis();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                Log.d(Thread.currentThread().getName(), (System.currentTimeMillis() - startTime) + " ms. " + "n = " + list.size() + " start ");
                int wait = random.nextInt(1000);
                try {
                    Thread.sleep(wait + 1000); // アップロード処理の終了の代わりに適当にウェイト置いてる
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(Thread.currentThread().getName(), (System.currentTimeMillis() - startTime) + " ms. " + "n = " + list.size() + " end ");
                list.add(list.size());
            }
        }, 500, 3000);
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d("MainActivity", "onStop");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d("MainActivity", "onDestroy");
    }
}
// 定期的にリクエストされるタイマーのタスクは全て同じスレッドで実行される
D/Timer-0: 500 ms. n = 0 start 
D/Timer-0: 2218 ms. n = 0 end 
D/Timer-0: 3500 ms. n = 1 start // 前回と今回の間のstartの間隔はなるべく3秒間隔
D/Timer-0: 4896 ms. n = 1 end 
D/Timer-0: 6500 ms. n = 2 start 
D/Timer-0: 7733 ms. n = 2 end 
D/Timer-0: 9500 ms. n = 3 start 
D/Timer-0: 11096 ms. n = 3 end 
D/MainActivity: onStop // バックグラウンドになっても止まらない
D/Timer-0: 12500 ms. n = 4 start 
D/Timer-0: 14368 ms. n = 4 end 
D/Timer-0: 15530 ms. n = 5 start // 遅れることもある(前回分の実行に時間がかかったなど)
D/Timer-0: 17289 ms. n = 5 end 
D/Timer-0: 18568 ms. n = 6 start // そうするとその次で「前回と今回の間のstartの間隔はなるべく3秒間隔」を守るために、次以降も時間がずれたままになる
D/Timer-0: 19608 ms. n = 6 end 
D/Timer-0: 21608 ms. n = 7 start 
D/Timer-0: 23448 ms. n = 7 end 
D/Timer-0: 24645 ms. n = 8 start 
D/Timer-0: 26188 ms. n = 8 end 
D/Timer-0: 27678 ms. n = 9 start 
D/Timer-0: 29216 ms. n = 9 end 
D/Timer-0: 30718 ms. n = 10 start 
D/Timer-0: 32033 ms. n = 10 end 
D/Timer-0: 33744 ms. n = 11 start 
D/Timer-0: 34993 ms. n = 11 end 

コードでボタンのStateによって色を変えるようにする

前回の続き的なノリで、コードでボタンのStateによって色を変えるように指定するコードを書いてみます。

API21以上の場合

API21以上の場合は、以下のように記述できます。

        Button button = (Button) findViewById(R.id.button_bg_code);

        // 角丸の設定
        int roundRadius = getResources().getDimensionPixelSize(R.dimen.button_round_radius);

        GradientDrawable gradientDrawable = new GradientDrawable();
        gradientDrawable.setCornerRadius(roundRadius);

        int[][] states = new int[][] {
                new int[] {-android.R.attr.state_pressed}, // not pressed
                new int[] { android.R.attr.state_pressed}  // pressed
        };
        int [] colors = new int[] {
                Color.RED,
                Color.GREEN
        };

        // API21以上対応
        gradientDrawable.setColor(new ColorStateList(states, colors));

        if (BuildConfig.VERSION_CODE >= 16) {
            button.setBackground(gradientDrawable);
        } else {
            button.setBackgroundDrawable(gradientDrawable);
        }

API20以下も対応する場合

これで先ほどと同じ振る舞いを確保しようとした場合は下記のようになります。

        Button button = (Button) findViewById(R.id.button_bg_code);

        // 角丸の設定
        int roundRadius = getResources().getDimensionPixelSize(R.dimen.button_round_radius);

        GradientDrawable gradientDrawableNotPressed = new GradientDrawable();
        gradientDrawableNotPressed.setCornerRadius(roundRadius);
        gradientDrawableNotPressed.setColor(Color.RED);

        GradientDrawable gradientDrawablePressed = new GradientDrawable();
        gradientDrawablePressed.setCornerRadius(roundRadius);
        gradientDrawablePressed.setColor(Color.GREEN);

        StateListDrawable stateListDrawable = new StateListDrawable();
        stateListDrawable.addState(new int[] {-android.R.attr.state_pressed}, gradientDrawableNotPressed); // not pressed
        stateListDrawable.addState(new int[] {android.R.attr.state_pressed},  gradientDrawablePressed);    // pressed

        if (BuildConfig.VERSION_CODE >= 16) {
            button.setBackground(stateListDrawable);
        } else {
            button.setBackgroundDrawable(stateListDrawable);
        }

現場からは以上です。

参考

ShapeDrawableのRoundRectShapeとGradientDrawableをそれぞれ使って角丸ボタン作ってみた

ShapeDrawableのRoundRectShape、あるいは、GradientDrawableを使うとコードからフチの余白や色を指定できる形で角丸四角形の背景を持つボタンが作ることができます。

もともとShapeDrawableで書いていたのですが、こちらの記事でShapeDrawableのリソースをGradientDrawableコンパイルされていることを知り、GradientDrawableで書いてみたらスッキリ記述できたので書き直した、という話です。

どちらにせよ、リソースファイルから読み込んだほうが基本的に高速なので、あまり表示頻度が多くない箇所をカスタマイズして表示したいとか、そういった場合に使えば良いと思います。

ShapeDrawableのRoundRectShapeで作る場合

同じ指定をxmlで記述した場合

今回はxmlで記述した場合は下記のようになる指定をコードで実装することにします。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <padding android:left="@dimen/button_padding_horizontal" />
    <padding android:top="@dimen/button_padding_vertical" />
    <padding android:right="@dimen/button_padding_horizontal" />
    <padding android:bottom="@dimen/button_padding_vertical" />
    <!-- 上記の指定はButton要素に下記属性を追加するのと同じ結果となる。
      android:paddingLeft="@dimen/button_padding_horizontal"
      android:paddingTop="@dimen/button_padding_vertical"
      android:paddingRight="@dimen/button_padding_horizontal"
      android:paddingBottom="@dimen/button_padding_vertical"  -->
    <!-- デフォルトのボタンではButton要素は
      paddingを設定しなくてもButton要素に余白を持っているが、それは
      android.R.drawable.btn_default_normal.xmlなどで
      paddingが指定されているため
       -->
    <solid android:color="#f00"/>
    <corners android:radius="@dimen/button_round_radius"/>
</shape>

RoundRectShapeを利用してコードで記述した場合

        Button button = (Button) findViewById(R.id.button_bg_code);

        // 角丸の設定
        int roundRadius = getResources().getDimensionPixelSize(R.dimen.button_round_radius);
        ShapeDrawable shape = new ShapeDrawable();
        shape.setShape(new RoundRectShape(new float[] {roundRadius, roundRadius, roundRadius, roundRadius, roundRadius, roundRadius, roundRadius, roundRadius},
                null,     // 背景のうち、真ん中の方をくりぬいた形にすることができるのですが、その領域の大きさをRectFで指定可能. 利用しない(=一色塗りつぶしにする)場合はnull
                null));   // 背景のうち、くりぬいた部分の長方形の角丸指定. 利用しない(=くりぬいた部分は長方形のまま)場合はnull

        // 余白の設定
        int horizontalPadding = getResources().getDimensionPixelSize(R.dimen.button_padding_horizontal);
        int verticalPadding = getResources().getDimensionPixelSize(R.dimen.button_padding_vertical);
        shape.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
        shape.getPaint().setColor(Color.RED);

        if (BuildConfig.VERSION_CODE >= 16) {
            button.setBackground(shape);
        } else {
            button.setBackgroundDrawable(shape);
        }
<!-- レイアウト -->
    <Button
        android:id="@+id/button_bg_resource"
        android:layout_below="@id/some_element"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/test"
        android:layout_marginTop="8dp" <!-- 見やすくするためつけてる -->
        android:text="TestTestTestTest" />

    <Button
        android:id="@+id/button_bg_code"
        android:layout_below="@id/button_bg_resource"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp" <!-- 見やすくするためつけてる -->
        android:text="TestTestTestTest" />
<!-- dimenリソースの設定 -->
<resources>
    <dimen name="button_round_radius">8dp</dimen>

    <!-- この辺の元ネタはこちら https://github.com/android/platform_frameworks_base/blob/c29fff50322599f53feadf9cf87df9956c9ac44e/core/res/res/values/dimens_material.xml#L109-L116 -->
    <dimen name="button_padding_horizontal">8dp</dimen>
    <dimen name="button_padding_vertical">4dp</dimen>
</resources>

見た目はこんな感じです。上がdrawableリソースで背景を指定したボタンで下がコードで背景を指定したボタンになります。

f:id:woshidan:20170509141454p:plain

GradientDrawableで作る場合

コードでの記述

        Button button = (Button) findViewById(R.id.button_bg_code);

        // 角丸の設定
        int roundRadius = getResources().getDimensionPixelSize(R.dimen.button_round_radius);

        GradientDrawable gradientDrawable = new GradientDrawable();
        gradientDrawable.setCornerRadius(roundRadius);
        gradientDrawable.setColor(Color.RED);

        // 余白の設定はButton要素の属性でやる

        if (BuildConfig.VERSION_CODE >= 16) {
            button.setBackground(gradientDrawable);
        } else {
            button.setBackgroundDrawable(gradientDrawable);
        }
<!-- レイアウト -->
    <Button
        android:id="@+id/button_bg_resource"
        android:layout_below="@id/some_element"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/test"
        android:layout_marginTop="8dp"  <!-- 見やすくするためつけてる -->
        android:text="TestTestTestTest" />

    <Button
        android:id="@+id/button_bg_code"
        android:layout_below="@id/button_bg_resource"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="@dimen/button_padding_horizontal"
        <!-- GradientDrawableの場合はpaddingを設定するメソッドはないので、
        View側にpaddingの指定を置く -->
        android:paddingTop="@dimen/button_padding_vertical"
        android:paddingRight="@dimen/button_padding_horizontal"
        android:paddingBottom="@dimen/button_padding_vertical"
        android:layout_marginTop="8dp" <!-- 見やすくするためつけてる -->
        android:text="TestTestTestTest" />

見た目は変わってません。

参考

AndroidのNDKやABIについてのメモ

はじめに

C系の言語で動いているクロスプラットフォームのブリッジを作成するには、そのコンパイルなどの過程でNDKに関する知識があるとトラブルシューティングやテストケースの設計に役立つことがあります。

なので、ここではその概要をまとめておきます。はじめに用語からまとめようと思ったのですが、

https://developer.android.com/ndk/guides/index.html?hl=ja

Native Development Kit(NDK)は、Android アプリで C および C++ コードを使用できるようにするツールのセットのことです。NDK を使用して独自のソースコードをビルドしたり、事前にビルドされた既存のライブラリを活用したりできます。

https://developer.android.com/ndk/guides/concepts.html?hl=ja

アプリケーション バイナリ インターフェース(ABI)はアプリのマシンコードが実行時にどのようにシステムと連携するかを正確に定義したものです。 NDK はこれらの定義に対して .so ファイルをビルドします。

と言われてもよくわからなかったので、NDKがABIを使ってC, C++ソースコードから.soファイルをビルドする周辺で何をしているかを説明します。

.soファイルとは

と書いたものの、書いた本人は.soファイルについての事前知識が調査開始時点でなかったため、簡単に説明します。

soshared object fileの略です。

.soファイルは、UNIX系の環境下での共通ライブラリファイルのことで、Windowsでいうdllにあたります。具体的には、複数の実行ファイル間で共有して利用される処理の定義がまとめられています。

中身は実行形式ファイルですが、あくまで処理を含んでいるだけでエントリポイントがなく単体で起動することはできません。

各々の実行ファイルに含まれず、実行ファイルを起動した時に動的にロードとリンクが行われるため、動的(ダイナミック)リンクライブラリと呼ばれることがあり、Android Developers GuideのNDKについての記述では主に動的リンクライブラリ、と呼称されています。

Androidでは、アプリのコンパイル時にリンクされている方が.a(静的リンクライブラリ), アプリの起動時にロードされてリンクされる方が.soで、NDKを使ってABIに従って作成される方が基本的に.soという感じです。

自分が作成したライブラリが.so か、.aになるかはコンパイルの設定によりますが、Androidアプリのビルドの文脈だと、.soファイルの方しか、公式のドキュメントに記載がなかったので、こちらが多いかもしれません。

NDKを利用してビルドする際の流れ

https://developer.android.com/ndk/guides/concepts.html のフローをもとに適宜用語の説明を入れながら説明します。

  1. Androidプロジェクトを作成します
  2. Androidプロジェクト直下にjniディレクトリを作成し、ネイティブライブラリ(.aファイル, .soファイルなど.アーキテクチャごとにフォルダを用意したり…)とコンパイル対象のソースコード(CやC言語で書かれたソースコード)とそれらをコンパイルするモジュールにどう含むか記述していくAndroid.mkを配置
  3. 2のディレクトリに任意でターゲットABI, ツールチェーン, リリース/デバッグモード, STLを設定するApplication.mkファイルを作成(任意)
  4. デフォルト設定
  5. ABI: armeabi
    • ABIというのは、ネイティブライブラリをコンパイルする際、コンパイルした.so.aファイルがインストール対象の端末で動くことを保証するための規約みたいなものです。
  6. ツールチェーン: GCC 4.8(4.8 32bit / 4.9 64bit端末対応らしい)
  7. モード: リリース
    • リリースモードだと変数宣言が最適化されていたりして、C言語側のステップ実行が困難になったりします
  8. STL: system
  9. NDKツールを適切に利用するために作成されたシェルのラッパーであるndk-buildを使用して、Cのソースコードをネイティブ(.so, .a. 主に.so?)ライブラリへコンパイルしたり、静的ライブラリをリンクしたり
  10. Javaソースコードをビルドして、DVMで実行可能な.dexファイルを生成
  11. アプリの実行に必要な .so, .dex、その他ファイルをすべてapkファイルにパッケージ化

ところで

.soファイルやネイティブライブラリと.dexを一緒に処理していませんが、JNI用のヘッダとそれに対応した規則のメソッド名で作成していけば、JVM(DVM)上でメソッドテーブルが作成されると理解していて、それにしたがってJava<=>C,C++間でメソッドを利用し合うことが可能みたいです。

この辺を調べていると、CMakeといった言葉もでてきますが、ひとまずこの記事を書くにあたっての状況に限っては(Andorid Studioでビルドをしない場合は)関係なかったので省きます。

CMakeについては、また今度機会あればまとめます。

参考

https://developer.android.com/ndk/guides/concepts.html http://blog.katty.in/4347 http://www.peliphilo.net/archives/681 http://qiita.com/m1takahashi/items/3a3c9d2845e9b57aeda3 http://morado106.blog106.fc2.com/blog-entry-80.html

COUNT関数の引数にNULLが入る場合について

SQLで行数を数える時に利用する COUNT 関数で一番使い慣れているのは COUNT(*) なのですが、 COUNT(col) や、COUNT(1かNULLになる式)といった形を見かけたのでメモ。

見かけた時は面白いなと思ったのに短い。。