NetInstallのソースにできるOS Xインストーラのバージョン

OS X Serverの動作しているMacと同じバージョンのインストールイメージしか、認識しないようです。

イメージの作成には、「システムイメージユーティリティ」を利用します。
Yosemite上のシステムイメージユーティリティでEl Capitanのインストールイメージをベースにしようとしたら、ダメでした。
「ソースが見つかりません」となっていて、選択できない状態。

El Capitan上では、YosemiteのもOKなのだろうか?

2.3.2016追記

El Capitan上で確認したところ、Yosemiteのインストーラは認識できませんでした。作成済みのNetRestoreイメージ(Yosemite版)であれば、El Capitan上のOS X Serverで配信は可能です。

Core Dataで作成されたSQLiteファイルの場所を確認する

Core Dataで管理されている、SQLiteファイルの場所の特定方法です。
開発時、アプリをシミュレーターへ転送して実行する度にパスが変わるのでちょっと確認しづらいですよね。

方法

AppDelegate.swift内でパスをログ出力

プロジェクト作成時にCore Dataを利用するように設定すると、AppDelegate.swift内にCore Dataの基本的なコードが生成されます。
そのコード中、persistentStoreCoodinator属性に代入されるクロージャ内で変数urlにSQLiteのパスが設定されています。
まず、それをprintln()関数でログ出力します。

persistentStoreCoodinator属性を評価

しかし、persistentStoreCoodinator属性はlazy、すなわち遅延評価されます。
開発が進んだ後であれば、この属性が評価されるはずなので問題ないです。が、プロジェクトを作成してすぐの段階では、まだこの属性を評価するコードを書いていないので、クロージャが実行されません。
そのため、この属性を評価しておきます。

コード例

AppDelegate.swiftより抜粋
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
    var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("CoreDataSample.sqlite")
        
    // ===== SQLiteファイルのパスを表示する =====
    println(url)
    // ======================================
        
    〜 中略 〜    

    return coordinator
}()
ViewController.swiftより抜粋
override func viewDidLoad() {
    super.viewDidLoad()

    // ===== AppDelegateのpersistentStoreCoodinator属性を評価する =====
    let coodinator = (UIApplication.sharedApplication().delegate as AppDelegate).persistentStoreCoordinator
    // =============================================================
}

出力例

file:///Users/User/Library/Developer/CoreSimulator/Devices/5D679BDA-E43B-406A-BB91-7B9A41002F38/data/Containers/Data/Application/1E2246CC-C713-4E69-ACBD-F043DC7C3A68/Documents/CoreDataSample.sqlite

アクティビティが起動中のときだけ NFC に反応させる(Foreground Dispatch)

最近、 NFC タグで遊んでます。
でもなかなか、アプリのネタが思いつかないものです。
さて、今回は Android アプリで NFC アプリを開発する際のポイントのひとつについて書きます。

今だけ反応して欲しいんだけど…

たとえば、 NFC タグにデータ書き込みを行うアクティビティを作成していたとします。
NFC タグに反応するように、通常は以下のようにインテントフィルターをマニフェストファイルに書くと思います。

<activity
    android:name="NFC にデータを書き込むアクティビティ"
    android:label="@string/app_name">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <action android:name="android.nfc.action.TAG_DISCOVERED" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

しかし、上記の形式では

  • アプリを起動していなくても、 NFC タグをかざすと常に書き込もうとする

ことになります。
でも、 NFC を使う場合って、書き込むよりは読み込む場面の方が多いですよね。
なので、

  • NFC のデータを読み込むアプリ(通常はこっちで反応して欲しい)
  • NFC へデータを書き込むアプリ(書き込み用アクティビティが表示されてるときだけ反応して欲しい)

ということが求められます。

Foreground Dispatch

この要求を満たすために用意されている仕組みが、 Foreground Dispatch です。

マニフェストファイルにインテントフィルターを指定してしまうと、上記のように常に NFC に対してアクティビティが反応してしまいます。
そのため、コード内で動的にインテントフィルターを指定する必要があります。この場合、マニフェストファイルには NFC に関するインテントフィルターは記述しません。

アクティビティ内のコード例(抜粋)は以下となります。

private NfcAdapter mNfcAdapter;
private PendingIntent mPendingIntent;
private IntentFilter[] mIntentFilters;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    〜省略(レイアウトの指定など)〜

    /*
     * Foreground Dispatch の設定
     * このアプリが優先して Intent を受け取れるようにする
     * 実際の設定・解除は onResume と onPause で実行
     */
    mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());

    Intent intent = new Intent(getApplicationContext(), getClass());
    intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
    mPendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);

    IntentFilter intentFilter = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
    mIntentFilters = new IntentFilter[]{intentFilter};
}

@Override
protected void onResume() {
    super.onResume();

    // Resumed に移る前に、 Foreground Dispatch を有効にしておく
    mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, mIntentFilters, null);
}

@Override
protected void onPause() {
    super.onPause();

    // Paused に移る前に、 Foreground Dispatch を解除しておく
    mNfcAdapter.disableForegroundDispatch(this);
}

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);

    〜 省略(NFC タグ発見時の読み込み or 書き込み処理) 〜
}

このように Foreground Dispatch を設定しておくことで、アクティビティが表示されている場合だけ NFC タグに対する処理を行うことができます。
それ以外の場合に NFC タグを近づけても、別の NFC 対応アプリが反応するようになります。

Notification が通知されない

知り合いがハマってたので書いておきます。

問題

Notification が表示されない。文法的には正しいし、エラーを吐いているようでもないんだけど…。

解決策

Notification.Builder#setSmallIcon() を呼びましょう。
Android で Notification を利用する際は、 SmallIcon を設定しないと通知がされないです。「通知は文字列だけでいいんだけど」という場面もありそうな気はしますけどね。

API ドキュメントには特に記載がありませんが、 Building a Notification | Android Developers の方に書いてあります。
このドキュメントを読むと、以下の 3 つが必須だということがわかりますね。

  • setSmallIcon()
  • setContentTitle()
  • setContentText()

サンプル

package foo.bar;

import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;


public class MainActivity extends Activity implements View.OnClickListener {

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

        Button buttonNotify = (Button) findViewById(R.id.button_notify);
        buttonNotify.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        Intent intent = new Intent(this, NextActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

        Notification notification = new Notification.Builder(this)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true)
                .setTicker("ティッカーだよ。")
                .setContentText("コンテントテキストだよ。")
                .setContentInfo("コンテントインフォだよ。")
                .setContentTitle("コンテントタイトルだよ。")
                .setWhen(System.currentTimeMillis())
//                .setSmallIcon(R.drawable.my_icon)   // SmallIcon を設定しないと通知されない(例外も発生しない)
                .build();

        notificationManager.notify(0, notification);
    }
}

Agile Japan 2014 に行ってきた(基調講演) #agilejapan

今年は行けないかと思っていたのですが、ギリギリ前日で案件が終了したため参加できました。

概要

  • 日程:2014 年 6 月 27 日(金)
  • 場所:日本 IBM
続きを読む

Dropbox API 利用時の ProGuard 設定

Dropbox API(Core API) を利用したアプリを作成してみた。
APK を生成するときに ProGuard を動かすけど、以下の設定が必要だったのでメモ。

-dontwarn org.apache.**

-keep class com.dropbox.client2.** { *; }
-keep class org.apache.commons.logging.** { *; }
  • Warning を出力しない(Warning が出ると、なぜか Error も出て失敗するため)
    • Dropbox APIApacheAPI が含まれているが、 Android 標準のと被るので Warning が発生するため
    • 本当は、重複しないようにすべきなんだろうけど
  • Dropbox APIApache Commons Logging API を除外
    • アプリ実行時に ClassNotFoundException が出るため
    • API 内部で、クラス名をリテラルで参照してるから?

android:launchMode="singleTask"

タスクをいろいろといじっていて、頭の中が混乱してきたので整理。

launchMode

デフォルトでは、起動したアクティビティはみんな同じタスクに入る。かつ、呼び出されるごとに同じアクティビティがいくつでもインスタンス化されて積まれる。
でも、その挙動を変えたいよねー、というときには AndroidManifest.xml に launchMode を設定する。

launchMode の種類

  • standard(デフォルト値)
  • singleTop
  • singleTask
  • singleInstance

singleTask

The system creates the activity at the root of a new task and routes the intent to it. However, if an instance of the activity already exists, the system routes the intent to existing instance through a call to its onNewIntent() method, rather than creating a new one.

<activity> | Android Developersより引用

私の認識(公式ドキュメントと、自分で作成したサンプルによる考察)
  • この設定がされたアクティビティは新しいタスクのルートとして配置される
    • 同じ taskAffinity が設定されているアクティビティ同士は同じタスクに配置されるので、必ずしもルートとして配置されるとは限らない(明示的な記述は見つからないが、実際の動きがそうなっている)
  • でも同じアクティビティが既にあったら、新しく作らずに onNewIntent() を呼ぶ
    • その際、対象アクティビティが処理を受け付ける必要があるため、上に積まれていたアクティビティは破棄(明示的な記述は見つからないが、実際の動きがそうなっている)

TODO: あとで図を使って整理