2011年6月27日月曜日

【Android】ListViewのデフォルトの区切り(divider)を普通のレイアウト上で使っちゃいたい

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
ListViewのデフォルトの区切り。
自前でShaderだのDrawableなxmlを作ってもいいんですが、デフォルトがあるんだからそれ使ったらいいじゃないのっていう。

準備


適当に以下の様なレイアウトファイルを作成する。
仮にdivider.xmlとする
<View xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent" android:layout_height="1dp"
 android:background="?android:attr/listDivider" />

使う


レイアウト上でincludeして使います。


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical" android:layout_width="fill_parent"
 android:layout_height="fill_parent">
 <include layout="@layout/divider" />
 <Button android:text="Button" android:id="@+id/button1"
  android:layout_width="wrap_content" android:layout_height="wrap_content">
 <include layout="@layout/divider" />
 <Button android:text="Button" android:id="@+id/button2"
  android:layout_width="wrap_content" android:layout_height="wrap_content">
 <include layout="@layout/divider" />
</LinearLayout>


ほうほう。

これはAndroid1.6の場合です。Android2.1以降ではListViewのdividerもシンプルになってちょっとあれだなぁと思ったり。


おわり


dividerに困ったらもうこれ使っとけ、みたいな。

2011年6月22日水曜日

【Android】DDMSのタグフィルターを正規表現で絞っちゃいなよ(配布もあるよ)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
タグの絞り込みで正規表現使えたらいいかもなーくらいに思っていたのでやりました。
ちょっと長いけど導入自体は超簡単です。うんちくが長いだけです。

※利用は自己責任でヨロシクお願いします☆

タグで正規表現


こんな感じ。suffixが"Service"であるタグを絞り込みする例

配布物


ddms_add.jarってのを使います。ていうか作りました。
https://sites.google.com/site/goatprog/android/androidproject/ddms_add.jar?attredirects=0&d=1

試す人はとりあえず落として下さい。


どーやったか


DDMSはJavaの住人です。もうなんでもありです。日本語対応の時も書いた気がするな・・・。
という事で、以下の手順でいい感じにクラスを上書きしています。

  1. エントリポイントになるクラスを作ってDDMSのMainの代わりに実行
  2. DDMSのMainを呼ぶ前に書き換えたいクラスをClass.forName()で読み込む。
  3. DDMSのMainを呼び出す。

こんだけです。クラスパスがポイントですね。書き換えたいクラスを含むパスを前に持って来るだけでおk。
Androidのmasterブランチから持ってきたDDMSのソースをいじってビルドして出来たクラスを使っています。


改造内容


いじったのはたったの一行です。
$ANDROID_HOME/sdk/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogFilter.java
boolean accept(LogMessage logMessage) {
        // do the regular filtering now
        if ((mMode & MODE_PID) == MODE_PID && mPid != logMessage.data.pid) {
            return false;
        }

        if ((mMode & MODE_TAG) == MODE_TAG && (
                logMessage.data.tag == null ||
                //logMessage.data.tag.equals(mTag) == false)) {
             logMessage.data.tag.matches(mTag) == false)) { //ここ
            return false;
        }
      -----略-----
    }

このクラスはlogcatをフィルタリングする際の各種条件を保持しているクラスです。
logcatから新たなログが送られてくると、各タブは各自が持つLogFilterのaccept(LogMessage)メソッドを呼び出して、
自分の領域にログを表示するかどうか判定をします。
タグの部分は元々equals(Object)で判定されていました。それをmatches(String)に変えただけです。
本当はPattern#compile(regex)して出来たPatternを保持した方が毎回String#matches(String)を実行するよりいいのですが、修正範囲が広がるのでやめました。動作上全く影響ないです。


課題


実は今まで知らなかったのですが、フィルターは":"と"|"が使えません。
どうもEclipseのプラグイン上のDDMSや、DDMS単体のタブ情報を保存する為に、":","|"をセパレータとして使っているみたいです。
なので今回の修正では完全な正規表現は使えません。

LogFilterにはLogFilterの中身を書きだすtoString()とStringからLogFilterを復元するloadFromString(String)があります。
ここを改造してあげる必要があるんですねーーーー。

@Override
    public String toString() {
        StringBuilder sb = new StringBuilder(mName);

        sb.append(':');
        sb.append(mMode);
        if ((mMode & MODE_PID) == MODE_PID) {
            sb.append(':');
            sb.append(mPid);
        }

        if ((mMode & MODE_LEVEL) == MODE_LEVEL) {
            sb.append(':');
            sb.append(mLogLevel);
        }

        if ((mMode & MODE_TAG) == MODE_TAG) {
            sb.append(':');
            sb.append(mTag);
        }

        return sb.toString();
    }

    public boolean loadFromString(String string) {
        String[] segments = string.split(":"); //$NON-NLS-1$
        int index = 0;

        // get the name
        mName = segments[index++];

        // get the mode
        mMode = Integer.parseInt(segments[index++]);

        if ((mMode & MODE_PID) == MODE_PID) {
            mPid = Integer.parseInt(segments[index++]);
        }

        if ((mMode & MODE_LEVEL) == MODE_LEVEL) {
            mLogLevel = Integer.parseInt(segments[index++]);
        }

        if ((mMode & MODE_TAG) == MODE_TAG) {
            mTag = segments[index++];
        }

        return true;
    }
こんな感じで":"使ってますね。各値をBase64か何かでエンコード/デコードすりゃいいんじゃね、と思います。
今回はやりません。


導入手順 Windows編


簡単です。
ddms_add.jarを以下の場所に配置してください。つーかddms.jarがある所と同じ場所。
$ANDROID_SDK_HOME/tools/lib/
そしてddms.batをちょっといじります。
とりあえずddms.batをコピーしてddms_add.batとかにでもして下さい。
やる事は
  • クラスパスを追加
  • メイン呼び出しを書き換え
です。
ddms.batの一番下でjavaを実行しています。これをちょっと書き換えます。

call %java_exe% %java_debug% -Dcom.android.ddms.bindir=%prog_dir% -classpath "%jarpath%;%swt_path%\swt.jar" com.android.ddms.Main %*
          ↓
call %java_exe% %java_debug% -Dcom.android.ddms.bindir=%prog_dir% -classpath "./lib/ddms_add.jar;%jarpath%;%swt_path%\swt.jar" jp.dip.sys1.android.ddms.Main %*

こんだけ。あとは"ddms_add"をコマンドプロンプトで叩くとか。


導入手順 Mac編


Windows編とほぼ同じです。略します。
ddms.batの代わりにddms.shか何かがいるはずです。それを書き換えます。

#exec "$javaCmd" -Xmx256M $os_opts $java_debug -Dcom.android.ddms.bindir="$progdir" -classpath "$jarpath:$swtpath/swt.jar" com.android.ddms.Main "$@"
          ↓
exec "$javaCmd" -Xmx256M $os_opts $java_debug -Dcom.android.ddms.bindir="$progdir" -classpath "lib/ddms_add.jar:$jarpath:$swtpath/swt.jar" jp.dip.sys1.android.ddms.Main "$@"

最後に


【緩募】
もうちょい色々検討した上でpatchを投げたいとか思っているわけですが、
その辺全くしらないので手とりナニとり教えてくれる有識者の方教えて下さい・・・。


追記2011/6/22 14:18
今すぐフォローすべきAndroid界のスーパーエンジニア@keiji_ariyamaさんからadt-devのメーリングリストで聞け!というアドバイスを頂きました!やってみたいと思います。
ありがとうございました真・今すぐフォローすべきAndroid界のスーパーエンジニア@keiji_ariyamaさん!

2011年6月21日火曜日

Java脳でもわかるObjective-C入門

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

photo: Some rights reserved by yukiv


Javaと比較しながら「へぇーObjective-Cってそういう感じか」と理解した気になろう。

流れ




軽くジャブ


まずは以下のソースを眺める

//SampleClass.h
#import <Foundation/Foundation.h>
#import "SampleClassDelegate.h"

@interface SampleClass : NSObject <sampleclassdelegate>{
 int index;
 NSString *str;
}
@property(assign) int index;
@property(nonatomic, retain) NSString *str;

+(void) dump : (SampleClass*) instance;
-(void) setData : (NSString*) str  str: (NSString*) number;
-(void) setData : (NSString*) str  num: (NSInteger) number;
-(NSString*) toString;

@end

//SampleClass.m
#import "SampleClass.h"
@implementation SampleClass
@synthesize index;
@synthesize str;
+(void) dump:(SampleClass *)instance{
 NSLog(@"%@", [instance toString]);
}
-(void) setData : (NSString*) text  str: (NSString*) number{
 self.str = text;
 self.index = [number intValue];
}
-(void) setData : (NSString*) text  num: (NSInteger) number{
 self.str = text;
 self.index = number;
}
-(NSString*) toString{
 return [NSString stringWithFormat:@"%@:%d", self.str, self.index];
}
//delegate
-(NSString*) toStringDao{
 return [NSString stringWithFormat:@"%@:%d%@", self.str, self.index, @"だお"];
}
@end
さすがのSyntaxHighlighterさんもObjective-Cはサポートしてねーって事でC++でハイライト。
ハイライトしなさ杉。
うんざりする見た目だけど、これから説明する事を読めばスラスラ理解できるようになりますよ!



クラスファイルの構成


Javaなら"クラス名.java"なわけですが、Objective-CはC言語の上に乗ってる言語なので、
その辺はCの仕組みに準拠します。
C/C++同様にヘッダファイルと実装ファイルに分かれてます。分けなくてもいいけど分けた方がいいので分けてます。
XCodeでクラスを新規作成した場合は以下の2ファイルが作成されます。
"クラス名.h" //ヘッダ
"クラス名.m" //実装ファイル

まーCです。



コメント

全く何もかも同じ

■Java

//コメント
/* コメント */

■Objective-C

//コメント
/* コメント */


import文


Objective-CはC言語と違って#includeではなく#importを使うようになっています。
違いは二重includeを自動的に防いでくれるだけです。#ifndefとかそういう制御がいらんという事です。
Javaしか知らない人はこの辺は気にしなくていいです。#importって書いておけばいいんです。

■Java
import java.lang.string;

クラスパスが通っていれば読み込めます。


■Objective-C
#import <UIKit/UIKit.h>
#import "Moge.h"

上の書き方は標準ライブラリから探す感じ(ほんとは違います)
下の書き方はプロジェクトの中から探す感じっぽい雰囲気。多分



変数の定義


概ねJavaと同じです。



■Java
int i = 10;

■Objective-C
int i = 10;


参照


クラスオブジェクトは必ず参照型です。Javaと同じですね。
書き方は"クラス名*"です。

■Java
Object object;

■Objective-C
NSObject *object;


id型


javaでいう処のObject型。
どんなオブジェクトでも入れられる型です。生ポインタという認識で大体いいかもしれません。
クリックイベントなどの引数でsenderをidで渡すといった形で使われます。

■Java
String object1 = new String("あはは");
Object object2 = object1;

■Objective-C
NSObject *object1 = [[NSObject alloc] init];
id object2 = object1;

ちなみにidにプリミティブ型を突っ込むと、警告が出ます。
Javaだとコンパイルエラーになりますね。



クラス定義


Javaと違って、Objective-Cではまずクラスを宣言します。その後実装コードを書きます。
一般的には宣言はヘッダに、実装は実装ファイルに書いていきます。
また、宣言と実装で宣言子が異なります。

宣言


■Java
宣言のみとかってねーよ。(ないよね?)


■Objective-C
@interface Moge : NSObject{
//メンバ変数宣言
}
//メソッド宣言
@end
キモイんですが、 @interface クラス名 : スーパークラス名で始まり
{ }の中にメンバ変数を宣言し、その後メソッドを宣言、最後に @end で閉じます。
メンバやメソッドは宣言のみで初期化や実装をここで書くことは出来ません。
この宣言を書いたヘッダファイル(.h)を実装ファイル(.m)でimportして実装を行います。


実装


■Java
public class Moge{

}

■Objective-C
#import "Moge.h"
@implementation Moge
//メソッドの実装などなど
@end
ヘッダをimportし、そこに宣言されたクラスを実装します。
宣言子は@implementationです。
ヘッダで宣言したメソッドの実装をガリガリ書きます。(実際の実装例はもうちょい下でやります)



メソッドの宣言、実装


メソッドには二種類あります。クラスメソッドとインスタンスメソッドです。
Javaでいう所のstaticメソッドとメンバメソッドですね。一緒ですね。
ヘッダのクラス宣言の所で各メソッドを宣言していきます。
ちなみにヘッダで宣言したメソッドは全てpublicになります。
privateなメソッドはmファイル内に実装だけいきなり書くことで実現する事が出来ます。


■宣言の仕方!


まぁキモイんですが慣れです。
-(void) moge : (NSString*) str isSave : (BOOL) isSave;
そうです、":"で色々区切ってます。":"の後に引数を書いていくイメージですね。
最初見たとき「えっ」ってなりました。意味不明ですよねこれ。
つまりこういう事です

-(戻り値の型) メソッド名 : (引数の型) 引数名 ラベル名 : (引数の型) 引数名

さて気になるのが「ラベル名」です。これ実はいりません。なくてもビルド通ります。
どういう時に使うかっていうとオーバーロード的な事をしたいときに使うんですね。
Javaなら勝手にやってくれるってーのにねー。やれやれですね。

-(void) moge : (NSString*) str isSave : (BOOL) isSave;
-(void) moge : (NSString*) str num : (NSInteger) number;
呼び出し側でラベル名を指定しながら引数をぶっこむ事でメソッドの呼び分けができるわけですね。
オーバーロードの際はラベル名付けて無いとコンパイル時に怒られます。

さて勘のいい方は気がついたかもしれません。
メソッド宣言の先頭に"-"とかいって毛が生えていました。
これ気のせいじゃないです。"-"には意味があります。
先頭の記号はメソッドの種類を現しています。



クラスメソッド


Javaで言うところのstaticなメソッド。
クラスをインスタンス化しなくても使えます。

■Java

public static void moge(String str, Boolean isSave){}


■Objective-C

+(void) moge : (NSString*) str isSave : (BOOL) isSave;
先頭を"+"にするとクラスインスタンスである、という事になります。


■インスタンスメソッド


いわゆる普通のメソッド。

■Java

public void moge(String str, Boolean isSave){}


■Objective-C

-(void) moge : (NSString*) str isSave : (BOOL) isSave;



メッセージ式


基本的に見た目がキモいだけです。色々と高尚な何かがあるみたいですけど、気にしなくてもいいです。

■Java

Integer num = new Integer(120);
num.toString();


■Objective-C

NSNumber* num = [NSNumber numberWithInt:120];
[num stringValue];

「メッセージ式」でググると色々出てくるので興味ある方はアレして下さい。
引数付きのコンストラクタも特殊です。ていうかコンストラクタは無いです。
allocの後に初期化メソッドを呼びます。引数付きのコンストラクタは引数付きの初期化メソッドで実現するんですねー。
上の例ではallocすらしてませんね、ファクトリメソッド的な物っぽいですね。多分そうです。



プロパティ


Javaでいうgetter/setterを隠蔽してくれる感じの仕組みです。
いろんな設定ができますがココでは省きます。

■Java

public void setIndex(int index){
  this.index = index;
}
public int getIndex(){
  return this.index;
}

■Objective-C

プロパティも宣言と実装的なものが分かれます。

・宣言
これはクラス宣言の{}の後に書きます。
@property(オプション的な何か) 名前;

こういう感じです。
@interface Moge : NSObject{
 NSInteger index;
}
@property(assign, nonatomic) NSInteger index;
@end


・実装的な感じ
@synthesize index;

上記の例だと自動的にgetter/setterが見えない所で実装されています。
色々と他にも使い方がありますがとりあえずこれだけでおkな気がします。
これによって以下の様な書き方ができます。

NSInteger a = self.index;
self.index = 10;
内部的にはgetter/setterメソッドが実行されています。

詳細はプロパティの宣言と実装とか読んで下さい。



セレクタ


いわゆるコールバックです。関数ポインタみたいなもんです。
Javaで言うとjava.lang.reflect.Methodだなぁというのが個人的なイメージ。

■Java

public class Moge{
  //コールバック
  public void callback(String str){
    System.out.println(str);
  }
  public void caller(Object target, Method callback)throws Exception{
    callback.invoke(target, "あはは");
  }
  public static void main(String[] args){
    try{
      Moge moge = new Moge();
      //リフレクションで取り出す
      moge.caller(moge,Moge.class.getMethod("callback", String.class));
    }catch(Exception e){
      e.printStackTrace();
    }
  }
}

■Objective-C

Objective-Cでは@selectorでメソッドを取りだす事ができます。
取り出したメソッドはSEL型という型になります。
コールバックを実現するメソッドは引数にセレクタを受け取って結果をセレクタに返してやります。
メディア再生のフレームワークとか使う時にセレクタをセットしてメディアの再生ステータスの変更を受け取ったり、
ボタンにセットしてクリックの通知を受ける時などに使います。

@implementation Moge
//コールバック
-(void) callback:(NSString*) str{
  NSLog(@"%@", str);
}
-(void) caller:(id) target callback:(SEL) callback{
  NSInvocation* invoker = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:callback]];
  NSString* string = @"あはは";
  [invoker setTarget:target];
  [invoker setSelector:callback];
  [invoker setArgument:&string atIndex:2];
  [invoker invoke];
}

-(void) exec{
  //@selectorでcallback:(NSString*) strを渡す
  [self caller:self callback:@selector(callback:)];
}
@end



プロトコル


iOSではデリゲートと呼ばれるものです。Javaでいう所のインタフェースにかなり近いです。
ていうか一緒です。

■Java
//宣言
public interface Cat{
  public String getBreed();
}
//実装
public class Mike implements Cat{
  @Override
  public String getBreed(){
    return "tortoiseshell cat";
  }
}


■Objective-C
//プロトコルの宣言
@protocol Cat
-(NSString*) getBreed;
@end

//クラスの宣言
@interface Mike : NSObject < Cat >
//書かなくてもいけるよ
@end

//クラスの実装
@implementation Mike
-(NSString*) getBreed{
  return @"tortoiseshell cat";
}
@end

クラス宣言の最後に< プロトコル名達 > でプロトコルの実装を宣言できます。
プロトコルは複数実装できます。Javaのインタフェースと同じっすね。
プロトコルはiOSではデリゲートとして頻繁に登場します。
例えばWebView(UIWebView)。
AndroidではWebViewClientを継承してshouldOverrideUrlLoadingを実装しますが、
iOSではUIWebViewDelegateというプロトコルを実装してセットしてあげる感じになります。
その他アプリケーションのライフサイクルの通知もデリゲートです。



カテゴリ


カテゴリはちょっと異質で、javascriptのプロトタイプ拡張みたいな雰囲気を持っています。
Javaには無い機能です。超便利です。

■Java
m9(^Д^)

■Objective-C
書き方はクラスと大体同じです。

@interface 拡張するクラス (カテゴリ名)

例えば全てのクラスの基底であるNSObjectに適当にメソッドを追加するなら
@interface NSObject (Unko)
-(void) buriburi;
@end
こんな感じでヘッダに書き、普通に実装します。
ファイル名は判りやすいように"NSObject+Unko.h"という風に作られる事が多いです。それしか見た事ないす。
拡張したNSObjectが使いたい人は"NSObject+Unko.h"をimportするだけです。
それだけでいつでもburiburiメソッドを呼べます。凄いっすね。
Objective-Cじゃなきゃできません。

もしObjective-Cじゃなかったら、そんなのObjective-Cじゃないんです



文字列リテラル


Objective-Cでの文字列リテラルでは、先頭に@を付ける必要があります。
@"文字列"はNSStringのリテラル表現となります。

■Java
String str = "moge";
Object str2 = "hoge";

■Objective-C

NSString* str = @"moge";
id str2 = @"hoge";



ログ


ログは重要すなぁ。Javaでいう標準出力なんですが、ほんとに標準出力なのか怪しいのでログという事にしました。

■Java
System.out.println("わーい");


■Objective-C
NSLog(@"わーい");



ひと通り使ってみる感じ


では最後にこれらの機能をひと通り使うクラスなどを載せてみます。

SampleClassDelegate.h
//プロトコル(デリゲート)
#import <UIKit/UIKit.h>

@protocol SampleClassDelegate
-(NSString*) toStringDao;
@end

SampleClass.h
#import <Foundation/Foundation.h>
#import "SampleClassDelegate.h"

@interface SampleClass : NSObject <SampleClassDelegate>{
 int index;
 NSString *str;
}
@property(assign) int index;
@property(nonatomic, retain) NSString *str;

+(void) dump : (SampleClass*) instance;
-(void) setData : (NSString*) str  str: (NSString*) number;
-(void) setData : (NSString*) str  num: (NSInteger) number;
-(NSString*) toString;

@end

SampleClass.m
#import "SampleClass.h"
@implementation SampleClass
@synthesize index;
@synthesize str;

+(void) dump:(SampleClass *)instance{
 NSLog(@"%@", [instance toString]);
}

-(void) setData : (NSString*) text  str: (NSString*) number{
 self.str = text;
 self.index = [number intValue];
}

-(void) setData : (NSString*) text  num: (NSInteger) number{
 self.str = text;
 self.index = number;
}

-(NSString*) toString{
 return [NSString stringWithFormat:@"%@:%d", self.str, self.index];
}
//delegate
-(NSString*) toStringDao{
 return [NSString stringWithFormat:@"%@:%d%@", self.str, self.index, @"だお"];
}

@end

SampleClass+Suffix.h
//カテゴリ
#import "SampleClass.h"
@interface SampleClass (Suffix)
-(void) toStringSuffix:(id) target callback:(SEL) callback;
@end

SampleClass+Suffix.m
#import "SampleClass+Suffix.h"
@implementation SampleClass (Suffix)
-(void) toStringSuffix:(id) target callback:(SEL) callback{
 NSInvocation* invoker = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:callback]];
 NSString* string = [NSString stringWithFormat:@"%@%@", self.str, @"っていう"];
 [invoker setTarget:target];
 [invoker setSelector:callback];
 [invoker setArgument:&string atIndex:2];
 [invoker invoke];
}
@end

User.h
//SampleClassを利用するクラス
#import <Foundation/Foundation.h>
#import "SampleClass.h"
#import "SampleClass+Suffix.h"
@interface User : NSObject {
}
-(void) exec;
@end

User.m
#import "User.h"
@implementation User

-(void) exec{
 SampleClass* sample = [[SampleClass alloc] init];
 id<SampleClassDelegate> delegate = sample;
 
 NSNumber* num = [NSNumber numberWithInt:120];
 [num stringValue];
 
 
 [sample setData:@"一回目" num:100];
 NSLog(@"%@", [sample toString]);

 [sample setData:@"二回目" str:@"50"];
 NSLog(@"object:%@", [sample toStringDao]);
 NSLog(@"delegate:%@", [delegate toStringDao]); 
 //セレクタ
 [sample toStringSuffix:self callback:@selector(callback:)];
 
}
-(void) callback :(NSString*) str{
 NSLog(@"callback:%@", str);
 [str release];
}
@end


実行結果:
一回目:100
object:二回目:50だお
delegate:二回目:50だお
callback:二回目っていう


まとめ

ね、簡単でしょ?

2011年6月14日火曜日

【Android】 ACTION_EXTERNAL_APPLICATIONS_AVAILABLEはこねーよ!

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
photo: Original Update by Origamiancy

昨夜未明、私はSDカードへのインストールをサポートしたアプリに新規機能を追加していました。
その機能はAlarmManagerを使って色々登録するので、端末の再起動時にACTION_BOOT_COMPLETEDによってそれらをリストアしてやらんといけませんでした。
技術的には簡単だったので颯爽と実装を終えた私は意気揚々とテストに取り掛かりました。
さてリストアはうまくいったかな?

が・・・だめっ・・・!

何度端末を再起動しても、マニフェストファイルを見直してもダメ。
全く同じ設定の他アプリは受け取っているのにこの新機能を追加したアプリだけは受け取れません。

30分ほどハマってようやく気が付きました。

SDカードにインストールしてるアプリはACTION_BOOT_COMPLETEDを受け取れないんじゃね?

全くそのとおりでした。
長い夜の始まりでした。



SDカードにインストールされていた場合ACTION_BOOT_COMPLETEDは受け取れない


そうです、SDカードにアプリがインストールされていた場合ACTION_BOOT_COMPLETED(android.intent.action.BOOT_COMPLETED)は受け取れないんです。
確かに、SDカードのマウントより前に飛んでるイメージがありますね。
実際ドキュメントにも書かれています。

App Install Locationの"Applications That Should NOT Install on External Storage"に以下の記載があります。
Broadcast Receivers listening for "boot completed"
The system delivers the ACTION_BOOT_COMPLETED broadcast before the external storage is mounted to the device. If your application is installed on the external storage, it can never receive this broadcast.
訳:SDカードにインストールしてるアプリにACTION_BOOT_COMPLETEDはこねーよ! neverだよnever!

ではどーしたらええの?



ACTION_EXTERNAL_APPLICATIONS_AVAILABLEというアクションが追加されている


同ドキュメントにはまた、こんな記載もあります。
Your running Service will be killed and will not be restarted when external storage is remounted. You can, however, register for the ACTION_EXTERNAL_APPLICATIONS_AVAILABLE broadcast Intent, which will notify your application when applications installed on external storage have become available to the system again. At which time, you can restart your Service.
訳:Serviceもほんとはダメだけど、と、特別にACTION_EXTERNAL_APPLICATIONS_AVAILABLEを登録してたら再起動してあげるんだからね!

つまりACTION_EXTERNAL_APPLICATIONS_AVAILABLEを受け取るサービスを定義しておいてナニすればいいってことー!?



いやACTION_EXTERNAL_APPLICATIONS_AVAILABLEはSDカードにインストールされているアプリ側は受け取れないぽい


試す前に色々調べていると、以下のissueが。

Issue 8485: Documentation bug: ACTION_EXTERNAL_APPLICATIONS_AVAILABLE

ACTION_EXTERNAL_APPLICATIONS_AVAILABLEってSDカード内のアプリは受け取れなくねー?との事。
確かにリファレンスのACTION_EXTERNAL_APPLICATIONS_AVAILABLEの方では以下の記載が

ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
Note that the packages in this list do not receive this broadcast. The specified set of packages are now available on the system.
訳:extraに乗ってくる利用可能リストに含まれるパッケージはこのbroadcastを受け取れないよー!(つまりSDカードがマウントされて有効化されるアプリ自身はこのbroadcastは受け取れない!)



ドキュメントバグじゃね


Issue 8485にこんなコメントが
This is a real bug. The apps installed on external storage will not receive ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.
訳:ACTION_EXTERNAL_APPLICATIONS_AVAILABLEはこねーよ!



明日があるさ


もしかしたらServiceで動的にACTION_EXTERNAL_APPLICATIONS_AVAILABLEを受け取るBroadcastReceiverをregisterしておくとシステムが空気読んで再起動してくれるのかもしれません。でもなんだか試すのが面倒なので諦めました。


全てを諦めた私はとうとうアプリを"internalOnly"にしました。


終わり