2015年12月6日日曜日

Windows 10 KB3116908 0x80070570

Windows 10 KB3116908 0x80070570

これは、パソコンのWindows 10 のシステム更新に関する情報です。

x64 ベース システム用 Windows 10 バージョン 1511 の累積的な更新プログラム (KB3116908)
に関する更新をしようとすると、
エラーコード:(0x80070570)
が発生します。これに関する回答は次のサイトにあります。
Error code: (0x80070570) - When trying to download Win10 pro cumulative (KB3116908)

パソコンの"C:\Windows\SoftwareDistribution\Download"にあるフォルダーやファイルを全て削除すれば、上記エラーは無くなり、更新ができます。

2015年11月19日木曜日

.InstantiationException

InstantiationException

E/AndroidRuntime:  Caused by: java.lang.InstantiationException: java.lang.Class<xxxx.xxxx.xxxx> cannot be instantiated

上記のエラーが出ました。原因は、下記のように、abstractが付いてしまっていたためでした。これを消すと直りました。何かの拍子に自動的に付加されてしまったものです。

public abstract class MainActivity extends Activity

参考
java.lang.InstantiationException: can't instantiate class; no empty constructor

2015年11月4日水曜日

Resources: Converting to string: TypedValue

Resources: Converting to string: TypedValue

W/Resources: Converting to string: TypedValue{t=0x12/d=0x0 a=1 r=0x1020351}
上記のような警告がLogに表示されることがあります。
この警告が不要であれば、Android端末の「設定」>「開発者向けオプション」>「表示属性検査を有効にする」のチェックを外してください。

2015年11月3日火曜日

MiniThumbFile: Unable to create .thumbnails directory

MiniThumbFile: Unable to create .thumbnails directory

Android 6.0で上記エラーが発生しました。
AndroidManifest.xmlファイルに下記のとおり追記すると解決しました。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

参考

2015年10月25日日曜日

Requesting Permissions at Run Time

Requesting Permissions at Run Time

AndroidのエミュレーターをAndroid 6.0 (API level 23、Marshmallow)にすると、mkdir()が動作しなくなった。
また、次のようなエラーも出るようになった。
Caused by: java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/video/media from pid=2627, uid=10058 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()

これは次の事情による。
Android 6.0 Changes > Runtime Permissions
Requesting Permissions at Run Time

この問題をとりあえず解決するには、そのエミュレータ(端末)の「設定」>アプリ>(開発中のアプリを選択)>Permissionsを選択し、必要とするpermissionにチェックを入れればよろしい。

でも、その程度で解決する話ではないんだよね。

6.0では実行時に、permissionの許諾を端末利用者から得ることになる。インストール時ではない。

6.0からは、従前のpermissionに対し、上位概念のpermissionが設けられた。
従前のpermisssionは、例えば、ファイル操作関係であれば次のようなものだ。
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
AndroidManifest.xmlファイルにある、お馴染みさんである。

これに対して、6.0では、上位概念のpermissionとして、例えば、「STORAGE」が設けられた。
STORAGEは、下位に、READ_EXTERNAL_STORAGEとWRITE_EXTERNAL_STORAGEの2個のpermissionを含んでいる。
この上位-下位の関係性はSystem Permissionsの表に掲示されている。上位概念は「Permission Group」という用語で説明されている。

上述の操作「設定」>アプリ>(開発中のアプリを選択)>Permissionsにおいて表示される文字列(コマンド)は、この上位概念のpermissionである。このpermissionに許可を与えれば、READ_EXTERNAL_STORAGEとWRITE_EXTERNAL_STORAGEの2個の許可を与えたことになる(アプリが、それらを使うのであれば)。

Javaのプログラミング上では、上述の「上位概念」(Permission Group)は登場しないし、書く必要はない。引き続き、従前のpermission用語を使えば良い。

1個の下位概念のpermissionの使用に対して端末利用者の許諾を得たのであれば、その上位概念の許諾を得たことになるので、配下の他の下位概念で許諾を得る必要はない。
上位概念単位で端末利用者に許可を得ることになっている。

どのpermissionで対応すべきかは、上記のように、エラーメッセージの中に記されている。

端末利用者に許諾を得るタイミングは、permissionに関するメソッドを使用する前であれば、いつでも良い。一般的には、onCreate()において実施することになるのであろう。

留意すべきは、これは「許諾を得る」という次元の話である。
プログラムの実行時の制約は、下位概念のpermission毎に個別に異なる。例えば、readだけを使って上位概念の許諾を得ていた場合、writeの実行はできない。この場合、writeの実行時には地味なエラーになる。一旦許諾を得てしまえば、上記のような親切なエラーメッセージは何も表示されないので、何故エラーなのか悩むかもしれない。
readができるように許諾を得た後で、こそっと、その許諾に関するコードをwriteに変更したとしても、許諾関連には何も影響はない。そして、writeを実行できるようになる。

Javaのコードは、教科書Requesting Permissions at Run Timeに書かれてあるとおりである。
なお、build.gradleファイルには次のとおり追加する。
dependencies {
    compile 'com.android.support:support-v4:23.1.0'
}

    /*
    onCreate()から呼ばれる。
     */
    private boolean MyCheckPermission(){
        if(BuildConfig.DEBUG) Log.d(TAG, "MyCheckPermission()");
        int i;
        i = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
        if(i==PackageManager.PERMISSION_GRANTED){//既に許諾を得ている場合。
            //Android 6.0未満の場合、自動的にここに来る。バージョンによる振り分けをする必要はない。
            if(BuildConfig.DEBUG) Log.d(TAG, "MyCheckPermission() checkSelfPermission() is GRANTED");
            return true;//起動に必要な残りの処理を実行する。
        }
        //一度許諾を与えると、2度と、下記処理を行わない。
        else{//まだ許諾を得ていない場合
            boolean b;
            b = ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE);
            if(b){//いきなり設定画面を表示するのではなく、permissionの必要性を(開発者の言葉で)説明する画面を表示する(任意)。
                //普通は、ここに来る。
                if(BuildConfig.DEBUG) Log.d(TAG, "MyCheckPermission() shouldShowRequestPermissionRationale is true");
                Intent it = new Intent(MainActivity.this, PermissionActivity.class);
                startActivityForResult(it, Nippon.ID_REQUEST_READ_EXTERNAL_STORAGE);
            }
            else{
                if(BuildConfig.DEBUG) Log.d(TAG, "MyCheckPermission() shouldShowRequestPermissionRationale is false");
                /*
                端末利用者が「二度とこの質問をしない」というチェックボックスにチェックしている場合、ここに来る。
                下記を実行することにより、過去の回答を自動で読み取る。画面に質問用画面を表示しない。
また、アンインストールをした後、再度インストールをした場合も、ここに来る。この場合、画面に質問用画面を表示する。
                */
                ActivityCompat.requestPermissions(
                    this,
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                    Nippon.ID_REQUEST_READ_EXTERNAL_STORAGE
                );
            }
            return false;//処理を、一旦、中断する。
        }
    }

    @Override
    protected void onActivityResult(int iRequest, int iResult, Intent it){
        if(BuildConfig.DEBUG) Log.d(TAG, "onActivityResult()");
        switch(iRequest){
        case Nippon.ID_REQUEST_READ_EXTERNAL_STORAGE:
            if(iResult==RESULT_OK){
                /*
                下記を実行するとAndroidシステム御用達の画面が表示され、READ_EXTERNAL_STORAGEを許可しますか? という趣旨の文言で聞かれる。
                端末利用者の選択結果はonRequestPermissionsResult()で受け取る。
                端末利用者が「二度とこの質問をしない」というチェックボックスにチェックすると、「拒否」しか選択できない表示になる(「承諾」を選択できない)
                アプリの運用途中で「拒否」&「2度と質問しない」をしてしまい、かつ、やっぱり気が変わって使いたいって思った場合、
                端末利用者は、手動で設定を変更しなければならない。
                */
                ActivityCompat.requestPermissions(
                    this,
                    //一度に、複数のpermissionを設定できるようにするため、配列形式になっている。
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                    Nippon.ID_REQUEST_READ_EXTERNAL_STORAGE
                );
            }
            else finish();//「戻る」キーで戻った場合
            break;
        }
        super.onActivityResult(iRequest, iResult, it);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
        case Nippon.ID_REQUEST_READ_EXTERNAL_STORAGE:
            if (grantResults.length > 0
            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                continue_onCreate();
            }
            else finish();
        }
    }

2015年10月10日土曜日

google.android.gms.ads.AdActivity

google.android.gms.ads.AdActivity

google.android.gms.ads.AdActivityを使用しない場合(Admobの広告を使用しない場合)であっても、
\app\build\intermediates\manifests\full\debug\AndroidManifest.xmlファイルの下方には、次のように自動的に記載される。何故このような仕様にしているのかは問わない。

        <!-- Include the AdActivity and InAppPurchaseActivity configChanges and themes. -->
        <activity
            android:name="com.google.android.gms.ads.AdActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
            android:theme="@android:style/Theme.Translucent" />
        <activity
            android:name="com.google.android.gms.ads.purchase.InAppPurchaseActivity"
            android:theme="@style/Theme.IAPTheme" />

普段は人畜無害なので冬眠中の虫のようなものだ。
しかし、この記述内のandroid:configChangesの記載にエラーがあるという趣旨で、Build>Rebuild Projectの時、及びGenerate Signed APK...の時にエラーが発生した、

止むを得ないので、gms系のを全部削除した。
すると、新たなエラーが発生した。
gms系のエラーは真の原因では無く、闇の大ボスがいるようである。むむむ!

そこで、Proguardの設定ファイル内に次の行を書き込んでみた。
-dontwarn com.google.android.gms.**
すると、闇の大ボスは出なくなった。
gmsを使ってないのに!!!

Androidのプログラミングの難易度は結構高めです。

2015年9月6日日曜日

動画を連続再生させるアプリ

動画を連続再生させるアプリ

動画を連続再生させるアプリ「動画ん君」を開発・公開しました。
アプリ起動時に、自動的に、再生リストに基づき、動画の再生を開始できます。
お気軽にお使いください。

https://play.google.com/store/apps/details?id=jp.androyer.playmp4

2015年8月30日日曜日

launchMode="singleTop"に設定しているにも関わらず

launchMode="singleTop"に設定しているにも関わらず

端末利用者によるHomeKeyの押し下げにより、Activityは見えなくなるが、存在はする。
アプリのアイコンをクリックすると、当該Activityは、onCreate()ではなく、onStart()から始まり、表示される。
HomeKeyを押し下げてActivityが消えた時点で、
端末利用者がファイラーを使って、関連ファイルをクリックし、ImplicitIntentにより、当該Activityの起動が試みられると、
launchMode="singleTop"に設定しているにも関わらず。
既存のActivityがonNewIntent()でメッセージを受けるのではなく、
新たなActivityがonCreate()で起動してくる。
こうなると、2個のActivityが存在することになる。オブジェクトとしては別物である。
アプリのアイコンをクリックすると、古い方のActivityが動作・表示される。
こんな仕様だとは思わなかった。アプリを作りこんでから分かった。むむむ!

2015年8月28日金曜日

MediaControllerのバグかもしれない

MediaControllerのバグかもしれない

  1. VideoViewを使って、横位置FullScreenで動画を再生させる。
  2. MediaControllerを使って、pauseを手動で行う。動画は停止状態になる。
  3. 端末を縦位置にする。configuration changeを発生させる。
  4. Logが真っ赤になる。

真っ赤に流れるボクのLogCat
同じことを2回行うと、端末が反応しなくなり、再起動が必要になる。

E/WindowManager﹕ android.view.WindowLeaked: Activity jp.androyer.playmp4.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{2210feb8 V.E..... R....... 0,0-1280,117} that was originally added here
            at android.view.ViewRootImpl.<init>(ViewRootImpl.java:370)
            at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:248)
            at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
            at android.widget.MediaController.show(MediaController.java:349)
            at android.widget.MediaController.show(MediaController.java:306)
            at android.widget.VideoView.toggleMediaControlsVisiblity(VideoView.java:697)
            at android.widget.VideoView.onTouchEvent(VideoView.java:638)
            at android.view.View.dispatchTouchEvent(View.java:7767)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2230)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1931)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2230)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1931)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2230)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1931)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2230)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1931)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2230)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1931)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2072)
            at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1518)
            at android.app.Activity.dispatchTouchEvent(Activity.java:2531)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2020)
            at android.view.View.dispatchPointerEvent(View.java:8004)
            at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4136)
            at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:3990)
            at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3548)
            at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3598)
            at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3567)
            at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3674)
            at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3575)
            at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3731)
            at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3548)
            at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3598)
            at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3567)
            at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3575)
            at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3548)
            at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5817)
            at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5797)
            at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5768)
            at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:5897)
            at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
            at android.os.MessageQueue.nativePollOnce(Native Method)
            at android.os.MessageQueue.next(MessageQueue.java:138)
            at android.os.Looper.loop(Looper.java:131)
            at android.app.ActivityThread.main(ActivityThread.java:5257)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:788)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:604)
            at dalvik.system.NativeStart.main(Native Method)

解答は次のサイトにあった。
Android MediaPlayer with MediaController: LogCat error “Activity has leaked window that was originally added here”

onSaveInstanceState()やonCreate(Bundle savedInstanceState)による作り込みを完成させてから、こんな現象が発生することを知った。むむむ!

2015年8月18日火曜日

常駐考察メモ

常駐考察メモ

私の出荷済アプリにおいては、参照カウンターを設けて、これがゼロになると、データベースを閉じる&アプリ終了するという方法を採用していた。
しかしながら、下記エラーが発生するという報告が利用者から来た。

Caused by: java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.

上記エラーへの対応方法をネットで調べると、「データベースを閉じるな」という方法が提案されている。
onDestroy()においてデータベースclose()しているにも関わらず、上記エラーが発生しているという状況から察するに、このclose()の後で、onStartCommand()がメッセージを受け取っているということが推測させる。
もはや、どうすることもできません。

●データベースを管理する。
●外部から不意のメッセージを受け取る
この2個の条件を満足させるためには、常駐化しかないかも。

参考:NotificationListenerServiceを実装させたアプリでは、NotificationListenerServiceは常駐している。また、類似アプリの振る舞いを観察しても、常駐させている。

NotificationListenerServiceに関する実験メモ

NotificationListenerServiceに関する実験メモ

【1】bindについて
NotificationListenerServiceは、Serviceであるため、まさにそのとおりに使えるのではないかという仮説を立てて、
1.Activityとbindで結ぶ
2.データベースの管理
3.NotificationListenerServiceとしての本来機能
の3個を、この順番で、実行をさせてみた。
上位2個までは順に順調に実行が完了していった。
本体の「設定」>「通知へのアクセス」>本アプリの選択したあと、通知(Notification)を受け取るようにしてみたところ、次のようなエラーが出た。

W/Parcel﹕ **** enforceInterface() expected 'android.os.IMessenger' but read 'android.service.notification.INotificationListener' W/Binder﹕ Caught a RuntimeException from the binder stub implementation. java.lang.SecurityException: Binder invocation to an incorrect interface at android.os.Parcel.nativeEnforceInterface(Native Method) at android.os.Parcel.enforceInterface(Parcel.java:451) at android.os.IMessenger$Stub.onTransact(IMessenger.java:48) at android.os.Binder.execTransact(Binder.java:404) at dalvik.system.NativeStart.run(Native Method)

残念なことに、私には詳細を調べる能力や気力はありません。
NotificationListenerServiceは、本体設定>「通知へのアクセス」の設定が行われた直後に、本来機能として「何か」とbindします。
しかしながら、上記実験プログラムでは、NotificationListenerServiceは、一番最初にActivityとbindしてしまっているため、上記エラーが発生したものと推測します。

推測ですが、NotificationListenerServiceは、「Avtivityとのbind」との共存はできないのかもです。

【2】他のアプリからのIntent
他のアプリからIntentで、NotificationListenerServiceにアクセスしようとすると、クラッシュする。
6736-6736/jp.androyer.vivitovib E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: jp.androyer.vivitovib, PID: 6736
    java.lang.SecurityException: Not allowed to start service Intent { flg=0x20000000 cmp=jp.Androyer.NotificationLauncher/.NService (has extras) } without permission android.permission.BIND_NOTIFICATION_LISTENER_SERVICE
            at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1571)
            at android.app.ContextImpl.startService(ContextImpl.java:1548)
            at android.content.ContextWrapper.startService(ContextWrapper.java:495)
            at jp.androyer.vivitovib.Nippon$exportList.onPostExecute(Nippon.java:651)
            at jp.androyer.vivitovib.Nippon$exportList.onPostExecute(Nippon.java:638)
            at android.os.AsyncTask.finish(AsyncTask.java:632)
            at android.os.AsyncTask.access$600(AsyncTask.java:177)
            at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:645)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:149)
            at android.app.ActivityThread.main(ActivityThread.java:5257)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:788)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:604)
            at dalvik.system.NativeStart.main(Native Method)

クラッシュを引き起こさせるとは、とても思い罪を犯したということですね。

2015年7月27日月曜日

鳴り分け君1号

新作アプリを開発・公開しました。

通知の内容を基に、音楽や振動パターンを鳴り分けます。
無料ですのでお気軽にお使いください。

製品名:鳴り分け君1号
読み方:なりわけくんいちごう

https://play.google.com/store/apps/details?id=jp.Androyer.NotificationLauncher


その2
端末の振動パターンを編集できるアプリです。
編集後の振動パターンは拙作「鳴り分け君1号」で利活用することになります。
無料ですのでお気軽にお使いください。

製品名:びびっとバイブ
英語名:ViviToVib


2015年6月8日月曜日

Could not reserve enough space for object heap

以前下記のような趣旨のエラーが出たように記憶しており、その回避のために、Android Studio>File>Settings>Build Tools>Compiler>VM Options欄にheapサイズを大きくすべく -Xmx????m などと書いた。
しかしながら、また、下記のエラーが出た。今回は、VM Options欄を空欄にしてみた。そうすれば、エラーは無くなった。むむむ!

そして、その後また同様のエラーが発生した。
-Xmx512m と書くとエラーが無くなった。数値が大きければ良いというものでは無いらしい。

Error:Unable to start the daemon process.
This problem might be caused by incorrect configuration of the daemon.
For example, an unrecognized jvm option is used.
Please refer to the user guide chapter on the daemon at http://gradle.org/docs/2.2.1/userguide/gradle_daemon.html
Please read the following process output to find out more:
-----------------------
Error occurred during initialization of VM
Could not reserve enough space for object heap
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.


その後、Android Studioが1.3になると、VM Options欄が消失していた。いままでの苦労はなんだったんだろうか。

2015/08/08 また出てしまった。
Error:Unable to start the daemon process: could not reserve enough space for object heap.
Please assign more memory to Gradle in the project's gradle.properties file.
For example, the following line, in the gradle.properties file, sets the maximum Java heap size to 1,024 MB:
Windowsを再起動したり、Android Studioを再起動したりすると、このエラーは消える。むむむ!

2015/08/13 また出てしまった。Android Studio 1.3.1
Error:Unable to start the daemon process: could not reserve enough space for object heap.
Please assign more memory to Gradle in the project's gradle.properties file.
For example, the following line, in the gradle.properties file, sets the maximum Java heap size to 1,024 MB:
org.gradle.jvmargs=-Xmx1024m

2015/08/27 また同じのが出てしまった。Android Studio 1.3.2
Error:Unable to start the daemon process: could not reserve enough space for object heap.
Please assign more memory to Gradle in the project's gradle.properties file.
For example, the following line, in the gradle.properties file, sets the maximum Java heap size to 1,024 MB:
org.gradle.jvmargs=-Xmx1024m
Windowsを再起動すれば直るため、このエラーメッセージの内容は参考程度ですね。

2016/01/05 Android Studio 1.5.1
この問題は、Android Studio の問題では無く、Windowsが発生させているようです。
上記のように、オプション欄にいろいろ数値を代入していじくっても解決しません。
よく発生するのは、Windowsのシステムアップデートの時です。事前に、Windowsのシステムアップデートをしっかり定着させておきましょう。
それでも、本日は、頻繁に発生してしまいました。Windowsのいわゆる「起動マネージャ」を使い、Windowsの起動時に起動されてしまうアプリを見直してみましょう。私の場合、この確認行為をしただけで、実際には何も変更を加えていないにも関わらず、Android studioの問題が消失しました。むむむ。

2015年5月17日日曜日

新作アプリ公開:御起動様

新作アプリ公開:御起動様

Androidのアプリを作成・公開しました。
「他のアプリを、指定した時間に起動させる」アプリです。
ゲームアプリを朝に起動させて、ゲーム音楽で目覚めるっていう使い方ができます。
無料ですので、気軽にお使いください。
アプリ名:御起動様
読み方:ごきどうさま

https://play.google.com/store/apps/details?id=jp.Androyer.goKeyDo

2015年4月30日木曜日

CheckBoxの再利用

CheckBoxの再利用

CheckBoxを作成&使用して、その後、そのオブジェクトを再利用したい場合があるかもしれない。
2回目の使用時にも、setCheckedメソッドを使ってチェックを付けるのである。

このsetCheckedメソッドであるが、setOnCheckedChangeListenerメソッドを実行してしまった後で、setCheckedメソッドを実行すると、onCheckedChangedメソッドが呼ばれてしまう。
2回目の使用時に、そうなってしまった場合、不都合が生じる場合が有る。

Windowsにおいても、似た仕掛けがあるが、Windowsの場合には、onCheckedChangedメソッドに「端末利用者がチェックを施したのか」「プログラムでチェックを施したのか」がわかる引数が付いている。
Androidの場合には、そういう引数は無い。

このため、setCheckedメソッドを実行する直前に、setOnCheckedChangeListener(null)を実行させれば良い。setCheckedメソッドを実行させた後で、setOnCheckedChangeListenerを再設定し直せば良い。

2015年4月19日日曜日

無料版の構築エラー

無料版の構築エラー

1個のプロジェクトで、無料版と有料版を構築している。
無料版のデバッグをしようとして、実行をさせようとするが、「無料版の実行形式ファイルがありません」という趣旨のエラーが出る。
そうすると、普通は、無料版関係について、あれこれイジリ回しますよね。

この場合、たぶん、有料版の実行形式ファイルが存在しないのである。このため、有料版の実行形式ファイルを、Build>Clean Projectコマンドを使って作れば良い。

そうすれば、無料版のデバッグができる。むむむ!

開発環境:Android Studio

2015年4月13日月曜日

AndroidLintIconMissingDensityFolder

AndroidLintIconMissingDensityFolder

Android Studio>Analyze>Inspect Code...

下記のエラーが出ることが有る。
AndroidLintIconMissingDensityFolder
Missing density folder
Icons will look best if a custom version is provided for ...

解決方法
主モジュール及び副モジュール(ライブラリ)の両方のresフォルダーに下記7個のフォルダーをダミーで設ければ良い。
drawable
mipmap-hdpi
mipmap-ldpi
mipmap-mdpi
mipmap-xhdpi
mipmap-xxhdpi
mipmap-xxxhdpi

drawable-*系のフォルダーは削除しても良い。
ライブラリ側のresフォルダーにもダミーで設けるってことが肝要ですね。
画像ファイルは入れておかなくても良い。
これに伴い、主モジュールのAndroidManifest.xmlのアイコンに関して次のように書き換えます。
android:icon="@mipmap/ic_launcher"

ちなみに、「mipmap」では無く「drawable」で置き換えた場合、別途、「アイコンのサイズが異なります」という趣旨のエラーが発生します。

2015年4月11日土曜日

if statement can be simplified

if statement can be simplified

私は下記のコードを好んで使っておった。

        String s = "aaa";
        if(s==null
        || s.equals("bbb")) return false;//ここに否定形を列挙していく。
        else return true;//生き残ったのが真実

お気に入りの書き方だった。
すると、Analyze>Inspect Code...において、if statement can be simplifiedという注意を受けた。
これをどうすれば単純化できるのかね。
解答は次のとおりです。

        String s = "aaa";
        return s!=null && s.equals("bbb");

パズルみたいですな。
死なせるべき否定形を列挙していくのではなく、生き残らせるべきものを条件制約していく、ということですな。

ダメなことを列挙すれば無限にあるが、真実は1個しかない。真実を見つめて進みましょう。それが前向きであり、最適です。って、人生哲学がJavaのコーディングから導かれるんだね。

(参考)

Android Plugin for Gradle

Android Plugin for Gradle

「Android Plugin for Gradle」と「Build System」とは、別名ではあるものの同じものを指しているのですね。何も知らない者から見ると、別名で表現されると、ややこしいですね。

Android Plugin for Gradle Release Notes...このページに有る、「Android Plugin for Gradle, Revision *.*.* (* *)」を見て、最新のRevisionを確認します。

Android StudioのメニューでFile>Project Structure...を選択します。
Project Structure画面の「Project」をクリックします。
右側に「Android Plugin Version」欄があるので、そこに、上記Revisionを書き込みます。
このやり方では無く、ルートに存在するbuild.gradleファイル内に次のように書いても良いです。
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.1.3'
    }
}
むむむ!ここでは「tools」って用語を使ってますな。普通は術語を統一するんじゃないのか。

上記はAndroid Studioにおいて、Gradleを使いこなす道具の設定ですね。
次に、Gradleのバージョンの設定も行います。

Gradleのサイトに最新のがあるようです。詳細を観ようとすると、セキュリティの
関係で閲覧できないという趣旨の画面が出ます。残念ですね。
Android Studioにおいては、次のメッセージが出てアプリの作成が拒否られます。
Error:No subject alternative DNS name matching services.gradle.org found.

止むを得ないので、最新のバージョンであるgradle-2.3-all.zipをダウンロードして任意のフォルダーに展開します。
File->Settings...で「Settings...」画面を表示させます。
「Gradle」の「Use default Gradle wrapper(recommended)」では無く、「Use local gradle distribution」にチェックをしまして、先ほど展開したフォルダーを設定します。
また、File>Project Structure...>Project Structure画面>「Project」>Gradle version欄を「2.3」にする。
recommendedされている方式は、ネットに情報を取りに行くようですね。

2015年4月13日現在、2.3にすると、gradleファイル内の項目に下線が引かれてしまう等の異常が発生します。2.2.1にすると問題はありません。

ちなみに私は下記の趣旨のエラー群で苦しんでいました。
Error Gradle DSL method not found compileOptions()
The project may be using a version of Gradle that does not contain the method.
Open Gradle wrapper file The build file may be missing a Gradle plugin.
Apply Gradle plugin
dependencies cannot be applied to groovy lang Closure

Googleにおける解説記事よりも、stackoverflowの記事の分量の方が多いような気がするのは、果たして私だけだろうか。
Gradleの道は険しいですね。みなさん、がんばってください。

(参考)
Android Tools Project Site
Gradle Plugin User Guide
Gradle Distributions
Error:(1, 0) Plugin with id 'com.android.application' not found
buildTypes cannot be applied to groovy.lang.Closure
Getting error “Gradle version 1.10 is required. Current version is 1.12.” when executing “gradle wrapper”?
Gradle sync error in Android studio when importing project

2015年4月10日金曜日

image varies significantly

image varies significantly
The image *.png varies significantly in its density-independent (dip) size across the various density versions
多分、EclipseからAndroid Studioにプロジェクトを移植した場合、上記の文言がinspection resultとして表示されるかもしれない。
放置しても問題は無いようである。
解決方法としては、Android Studioにおいて、空のプロジェクトを新規に生成して、そこにあるresourceのdrawableフォルダーにあるファイル関係を参考にしながら、自分のアプリのプロジェクトを構築すれば良い。例えば、mipmap-hdpiという名前のフォルダーを作成して、そこに画像ファイルを入れれば良い。
AndroidManifest.xml内の、アイコンを使用しているフォルダー名も書き換えることになる。

(参考)
Image varies significantly in its density-independent (dip) size

2015年4月9日木曜日

恐怖のPower Save Mode

恐怖のPower Save Mode

Android Studioのエディタにおいて、赤色のエラーが表示されなくなった。原因不明で、アンインストールを2回行い、Settings...を何度も見直し、問題解決を試みたが解決しなかった。

原因はFile->Power Save Modeであった。これにチェックしていると、赤色のエラーが表示されなくなる。

Android Studioはコマンド数が多すぎる。Power Save Modeは削除して欲しい。

2015年4月7日火曜日

Calendar.AM_PM

Calendar.AM_PM

Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(System.currentTimeMillis());
if(cal.get(Calendar.AM_PM)==Calendar.AM){
    rgAMPM.check(R.id.Radio_NumericPad_AM);
}
else{
    rgAMPM.check(R.id.Radio_NumericPad_PM);
}

2015年4月5日日曜日

IabHelper#queryInventoryAsync()

IabHelper#queryInventoryAsync(boolean, List<String>, Listener)

第一番目の引数 boolean
trueの場合、消費されたアイテム及び消費されてないアイテムの両方を取得する。
falseの場合、消費されたアイテムだけを取得する。

第二番目の引数 List<String>
取り扱っている商品群を代入する。
この値がnullの場合、取り扱っている商品のうち消費された商品だけが取得される。
この値に取り扱っている商品群を代入しておくと、その商品が消費されていない状況が、Inventory#hasDetails()等を併せ使うことにより、明確に判明する。
管理者は商品の全てを把握しているため、消費されていない商品群を把握する必要は無いが、商品を新規に登録した場合、本当に登録が完了したのかどうか確認したいものである。
売れ残っている商品を顧客に提示する目的でも使う。

第三番目の引数 QueryInventoryFinishedListener
リスナーです。

queryInventoryAsync(Listener)の場合、queryInventoryAsync(true, null, listener)として取り扱われ、消費された商品だけが取得されます。

2015年4月3日金曜日

License Verification Library (LVL) のVT

License Verification Library (LVL) のVT

ServerManagedPolicyでLVLを実装して動かしてみると、VT(Validity Timestamp、有効期間)の値(long型)がLong.MAX_VALUEになっていた。

これは、期間の定めが無く、いつまでも有効であることを意味している。
つまり、検査をするのは最初の1回だけであって、2回目以降は検査をしないということである。2回目以降は検査のためにネットへ接続はしないということであるので、インターネットへの接続を必要としないアプリであっても効果があるということである。

端末利用者にアプリをGoogle Playからダウンロードしてもらった後、ネットに接続している時に、アプリを1回起動して貰えれば検査は完了するのである。

以上がServerManagedPolicyの振る舞いであるが、常時インターネットに接続するアプリであれば、StrictPolicyを使うことを検討しても良い。もちろん、アプリを起動する度にインターネット経由で検査をするので、その分、遅くなるが。

Licensing Referenceに詳しい情報がある。
Table 1のCategoryがLicense check and result及びPolicyとして掲げられたName欄のクラスを必要に応じて適宜変更することになる。
CategoryがLibrary core, no integration neededとして掲げられたクラスは変更をする必要はない。例えば、ILicensingService.javaの冒頭には次のように書かれてある。
This file is auto-generated.  DO NOT MODIFY. Original file: aidl/ILicensingService.aidl
Table 2はGoogle Play serverが返してくる値をまとめた一覧である。基本はResponse Code欄にある値を参照すれば良い。Extras欄の意味は、「Server Response Extras」に説明が書かれている。
ExtrasはServerManagedPolicyでは実装している。
Policyとは、ServerManagedPolicy又はStrictPolicyの基底です。ServerManagedPolicyはひな形としてはGoogleが提供していますが、プログラマが適宜自己責任で改修を行い使うこととなっている、と思います。

以下は私なりの日本語訳です。

License validity period(ライセンス有効期間)

The Google Play licensing server sets a license validity period for all downloaded applications. The period expresses the interval of time over which an application's license status should be considered as unchanging and cacheable by a licensing Policy in the application.

Google Play licensing serverは、すべてのダウンロードされたアプリケーションのライセンスの有効期間を設定します。この期間は、アプリケーションでのライセンスPolicyに基づき、アプリケーションのライセンスの状態が
「不変であり」かつ「キャッシュが可能である」と考えられる時間間隔を表現しています。

The licensing server includes the validity period in its response to all license checks, appending an end-of-validity timestamp to the response as an extra under the key VT.

licensing serverは、ライセンス検査への応答の中に有効期間に関するデータを含めており、extraの「VT」というキーの中に、有効期間の最終日時のデータが有ります。

A Policy can extract the VT key value and use it to conditionally allow access to the application without rechecking the license, until the validity period expires.

Policyは、有効期間が満了するまで、ライセンスを再検査することなく、VTの値を、条件付きで、アプリケーションへのアクセスを許可するために使用することができます。

The license validity signals to a licensing Policy when it must recheck the licensing status with the licensing server.

ライセンス期間というものは、ライセンスPolicyがライセンスサーバーを使ってライセンス状態を再検査しなければならない時を示しています。

It is not intended to imply whether an application is actually licensed for use.

これは、アプリケーションが実際に使用が許可されているかどうかを意味するものではありません。

That is, when an application's license validity period expires, this does not mean that the application is no longer licensed for use 

それは、アプリケーションのライセンス有効期間が満了したとき、アプリケーションの使用が不許可になったことを意味しません。

rather, it indicates only that the Policy must recheck the licensing status with the server.

むしろ、それは、Policyはサーバを使ってライセンスの状態を再検査しなければならない、ということだけを示します。

It follows that, as long as the license validity period has not expired, it is acceptable for the Policy to cache the initial license status locally and return the cached license status instead of sending a new license check to the server.

つまり、ライセンスの有効期間が満了していない限り、Policyは、初期のライセンスのデータをローカルにキャッシュすること、そして(サーバにライセンスの検査を行わせるのではなく)キャッシュされたライセンスのデータを使うこと、が可能です。

The licensing server manages the validity period as a means of helping the application properly enforce licensing across the refund period offered by Google Play for paid applications. 

有料アプリケーションのために、アプリケーションが適切にライセンスを実施する手段として、ライセンスサーバーは有効期間を管理します。(acrossの意味不明)

It sets the validity period based on whether the application was purchased and, if so, how long ago. Specifically, the server sets a validity period as follows:

アプリケーションが購入されたかどうか、どのくらい前なのか、に基づいて有効期間を設定します。サーバが有効期間を設定するのは具体的には以下のとおりです。

●For a paid application, the server sets the initial license validity period so that the license response remains valid for as long as the application is refundable. A licensing Policy in the application may cache the result of the initial license check and does not need to recheck the license until the validity period has expired.

●有料アプリのために、サーバは初期のライセンスの有効期間を設定します。それにより、アプリが返金可能である限りは、ライセンスのレスポンスは有効の状態を維持し続けます。アプリケーションにおけるライセンスPolicyは、初期のライセンスチェックの結果をキャッシュすることができるし、有効期間が満了するまで、ライセンスを再検査する必要はありません。

●When an application is no longer refundable, the server sets a longer validity period — typically a number of days.

●アプリケーションがもはや払い戻しできない場合は、サーバは長い有効期間 - 通常は数日 を設定します。

●For a free application, the server sets the validity period to a very high value (long.MAX_VALUE). This ensures that, provided the Policy has cached the validity timestamp locally, it will not need to recheck the license status of the application in the future.

●無料のアプリケーションのために、サーバーは非常に高い値(Long.MAX_VALUE)を有効期間として設定します。これは、
Policyがローカルに有効期間に関するデータをキャッシュしてしまうことを保証します。今後ずっとアプリケーションのライセンスステータスを再検査する必要はありません。


2015年4月1日水曜日

License Verification Library (LVL)

License Verification Library (LVL)

App Licensing
Android LVL を使う - Securing Android LVL Applications -
Android Application Licensing
LVL (License Verification Library) を使ってみた
Google Play の Licensing サービスを使う
LVL(License Verification Library)の導入
Anti AntiLVL - AntiLVL への簡単な対抗方法について考えてみる
Android 5.0 で LVL が正常動作しない問題の対処

ServerManagedPolicyは、飛行機に乗っている時のように、オフラインで端末を使っている時でも取り扱えるような、規約を定めます。
StrictPolicyは、サーバーとのやり取りをする時だけしか動作しないような規約を定めます。オフラインでは動作しません。
どのように使うのかを、プログラマーは、適宜判断をして、LVLのソースコードを書き換えて使用します。

Licensing Overviewには、次のとおり書かれています。
Google Play considers a user to be licensed if the user is a recorded purchaser of the application.
また、Requirements and Limitationsには、次のとおり書かれています。
You can implement licensing controls for a free app, but only if you're using the service to provide APK expansion files.
LVLは有料アプリで使うのですね。

2015年3月29日日曜日

proguard dontwarn

proguard dontwarn

下記の趣旨のエラーが出た。
Warning: com.google.common.cache.Striped64: can't find referenced class sun.misc.Unsafe
Warning: there were 21 unresolved references to classes or interfaces.
         You may need to add missing library jars or update their versions.
         If your code works fine without the missing classes, you can suppress
         the warnings with '-dontwarn' options.
         (http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedclass)
:proguardRelease FAILED

対処方法
次の1行だけを記述したテキストファイルを適当なファイル名(ここでは'my_proguard-project.txt')で、(ルートでは無く)モジュール側のフォルダーに作成する。
-dontwarn sun.misc.Unsafe

(ルートでは無く)モジュール側のbuild.gradleファイルに次のように書く。
    buildTypes{
        release{
            minifyEnabled true//プロガードを実行する
            proguardFile getDefaultProguardFile('proguard-android.txt')
            proguardFile file('my_proguard-project.txt')
        }
getDefaultProguardFile()は${sdk.dir}/tools/proguard/proguard-android.txtを自動的に読み込む。
my_proguard-project.txtは、このプロジェクトのモジュール固有のproguard設定です。
ちなみに、デフォルトでは、minifyEnabledはfalseです。

(参考)Android Tools Project Site

Gradle Plugin User Guide

Gradle Plugin User Guide

取り敢えず日本語版を読んで概要を知る
第34回 バージョン管理 ─プロジェクト管理ファイルについて[中編]
第 4 回・新 IDE : Android Studio を使ってみよう!-Part2-

Gradle Tutorial : Part 6 : Android Studio + Gradle 画面の画像がある。
Gradle Plugin User Guide 本丸。ここに詳細がある。

DSL == Domain Specific Language。

Android Studioでいわゆる「カス」が溜まる現象について

Android Studioでいわゆる「カス」が溜まる現象について

Android Studioで矛盾するエラーメッセージが出る場合があります。

私の事例の場合のエラーは次のような趣旨でした。
Information:Gradle tasks [clean, : compileDebugSources]
Error:orientation|screenLayout|uiMode|screenSize|smallestScreenSize"

Information:Gradle tasks [clean, generateDebugSources, generateDebugAndroidTestSources]
Error:orientation|screenLayout|uiMode|screenSize|smallestScreenSize"

上記エラーが出たので、AndroidManifest.xmlファイルにおいて該当文言を削除しました。しかし、引き続き上記エラーが発生し続けたのです。
もはやプロジェクトファイルのどこにも上記エラー文言が存在しないのにも関わらずです。
多くの時間をかけて、やっと解決しました。

解決方法は次のとおりです。
File->Settings...->Compiler (Gradle-based Android Projects)
「Use in-process build」欄のチェックを外す。
「Configure on demand」欄のチェックを外す。
その上で、Clean等を行います。すると、いわゆる「カス」が除かれます。
そうすれば、再度上記チェックをonにしても、エラーは発生しません。

チェックを入れると、コンパイルは速くなるようです。
でも、わけのわからんエラーが発生し、その問題解決のために、コンパイルが早くなった時間以上の、多大な時間を浪費してしまいました。むむむ!

ちなみに、上記処理により、.idea/workspace.xmlの内容は自動的に書き換わります。.idea/workspace.xmlを手動で書き換えても意味ないです。

とにかく、わけわからんエラーが出たら、上記チェックを外す。

プロジェクトの構成を変更したら、自動的に、チェックを外してコンパイルするようにして欲しいな。

その後、上記対応を行っても同様のエラーが発生した。Proguardのtxtファイルが存在しなかったので、作成してやると、上記エラーが無くなった。

2015年3月23日月曜日

stacktrace option, debug option

stacktrace option, debug optionの設定方法

* Try:
Run with --stacktrace option to get the stack trace.
Run with --info or --debug option to get more log output.

上記メッセージが出たら、下記を参照する。
How to Add Stacktrace or debug Option when Building Android Studio Project

Android Studio->File->Settings->Compiler(Gradle-based Android Project)->Command-line Options:
ここの欄に「--stacktrace --debug」と書く。

Xlint deprecation

Xlint deprecation

次のような趣旨の注意が出ました。
compileDebugJava
注意:一部の入力ファイルは非推奨のAPIを使用またはオーバーライドしています。
注意:詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。

回答は次のページにありました。
How to add -Xlint:unchecked to my Android Gradle based project?
ルートのbuild.gradleファイル内に次のコマンドを追加・記述して、Build->Clean Projectをすればよろしい、とのことです。

allprojects {
    gradle.projectsEvaluated {
        tasks.withType(JavaCompile) {
            options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
        }
    }
}

そうすれば、ログに次のメッセージが出ました。

*\library\src\main\java\com\google\android\vending\licensing\LicenseChecker.java
Error:(257, 55) 警告: [deprecation] DateのtoGMTString()は非推奨になりました
Error:(257, 55) 警告: [deprecation] DateのtoGMTString()は非推奨になりました
警告1個

むむむ!このプログラムは、Google社が提供している、安全性を確保するための、ライセンスを検査するプログラムなんです。そのプログラムにおいて、非推奨メソッドを使って、警告が発せられるってのは面白いですね。

LicenseChecker.javaのverifyLicenseメソッド内で、次のようにログを出力するために使われていました。
Log.d(TAG, "Time: " + date.toGMTString());
なので実害は無いです。

2015年3月21日土曜日

Google Cloud Endpoints

Google Cloud Endpoints

背景
スマホアプリ開発に超絶便利なBaaSとは、MEAPとは
Google App Engine、「Cloud Endpoints」でモバイルバックエンド環境を提供
Google、アプリへのクラウドサービス実装を容易にする「Mobile Backend Starter」
モバイルアプリ開発を加速する「mBaaS」のメリット/デメリット
Google Cloud Platform のモバイル ソリューション
Google Cloud Endpoints の紹介
【RainbowAppsセミナー】Google Cloud Endpoints入門

英語
Cloud Endpoints Support Android Studioの視点から見た大雑把な概要
New ways to connect your app to the Cloud using Android Studio and Google Cloud Platform 開発の視点から見た概要
Google Cloud Endpoints Annotations等も載せた理論詳細
HelloEndpoints 開発画面も載せている。

実装詳細
20.Google Cloud Endpointsを試してみた (1/3)
Android Studioに追加されたGoogle App Engineテンプレートを試そう 導入編
Android Studioに追加されたGoogle App Engineテンプレートを試そう 実装編

1個の文字列だけを取得したいのであれば、HelloEndpointsにあるとおりにすれば良い。
しかし、実際には、複数のデータを取得したいのであるから、Android Studioに追加されたGoogle App Engineテンプレートを試そう 実装編の記事が役に立つ。

HelloEndpointsの「2. Connecting your Android app to the backend」では、EndpointsAsyncTaskを実行させようとしているが、この中の
    private static MyApi myApiService = null;
のMyApiが意味不明であった。

このMyApiキーワードに対して、Android Studioの「Find Usages」コマンドを実行してみると、MyApiはcom.google.api.client.googleapis.services.json.AbstractGoogleJsonClient から派生されたクラスであることがわかる。つまり、Jsonであるということですね。
そして、このクラスの名前にAndroid Studioの「Copy Path」コマンドを実行してみると、このクラスはjarファイルの中に入っていることがわかる。Windowsのエクスプローラを使うと、そのファイルの存在を確認できる。
つまり、ライブラリを生成していたということですね。Android Studioからは直接には見えない場所にあるのですね。

ちなみに、Android Studioでは、Javaコードのエディターにおいて、赤色になってエラーになっているキーワードに対して「Alt」キー+「Enter」キーの押し下げにより、import文が自動で生成される。

"http://10.0.2.2:8080"や"http://localhost:8080/_ah/api/explorer"のローカルホストを使う意義は、動作の確認のためなんですね。

2015年3月18日水曜日

In-app Billing に関するサイト

In-app Billing に関するサイト

In-app Billing Overview
概要や安全性に関する記事。概要以外の記事にも目を通しておいた方が良い。

Google Play のアプリ内課金機能を使用して払い戻しを処理することはできません。
アプリ内アイテムの価格、払い戻し、注文
Handling In-App Billing Refunds in v3
Does Google Play In-App Billing Version 3 support refunds?

Selling In-app Products
トレーニングであり、実践

Android In-app security recommendation - what does this mean?

In-App Billing Version 3 概要
アプリ内課金商品の購入 Purchase In-app Billing Products

Androidで課金アプリ作製 サンプルコード(BILLING V3) 起動編
サンプルプログラムの実行時画面がある。助かります。

Google Play In-app BillingをAPI v3のサンプルを使って楽して導入する

Google Play In-App Billing テスト方法
Testing In-app Billing
in app billing v3 でサンドボックステストするときの話

安全性について
Security and Design
Android LVL を使う - Securing Android LVL Applications -
Androidアプリの解読・改ざんを防ぐ難読化ツールとは
Why is it important to set the developer payload with in-app billing?
Tips for integrating with Google Accounts on Android

安全性の確保のためクラウドを使う
Cloud Tools for Android Studio

ProGuard の設定

SKU == Stock Keeping Unit



2015年3月14日土曜日

EclipseからAndroid studioへのプロジェクトの移植

EclipseからAndroid Studioへのプロジェクトの移植

移植に伴い発生するエラーの表現は適切ではない。ネットで検索してもヒットしない。自分で試行錯誤しよう。

●昔はEclipseにおいて移植に備えたコマンドを実行しておかねばならなかった。今はその必要は無い。

●EclipseにおいてAndroid 5.1が使えない状態で、Android 5.1しか使えないAndroid Studioにインポートした場合、意味不明なエラーになった。
EclipseにおいてSDK ManegerでAndroid 5.1をインストールして、これを使えるように設定した後で移植をすると問題がなくなった。

●Android Studioを使っているとウイルス検出プログラムで「aaptを使用するな」と警告されるが、aaptを使用する必要がある。
誤ってウイルス検出プログラムでaaptを削除してしまった場合には、デバッグができなくなる。SDK Manegerを使って、「Android SDK Build-tools」をインストールすれば治る。

●Google Play Service等のライブラリを使用している場合には、Android Studioでインポートする時に表示されるダイアログ「Import Project from ADT (Eclipse Android)」画面で表示されている「Replace jars ...」と「Replace library ...」のチェックを外さねばならない。
チェックをしたままインポートすると、意味不明な結果になる。

●Android StudioのAnalyze> Inspect Code...を実行させて、そこにある各種指摘のとおりコードを修正する。この修正と、移植に伴うエラーとは何の論理的関係も無い。無いのであるが、この修正を行うと、何故か分からんが、移植に伴うエラーが消えた。

(これが最も効果があった)
特に、アイコン画像ファイルを入れているdrawable系のフォルダーをmipmap系に変えて、使わないdrawable系を削除する。この作業に伴い、AndroidManifest.xmlファイル内での「drawable」を「mipmap」に変更する。
この処置は「Javaのバージョンが異なります」的なエラーをも消去してくれる。
エラー表示内容と、上記対応が何の関連も無いように見えるので「まさかこんなことで?」と半信半疑になる。

●Android Studioで新規プロジェクトを生成する。そしてその新規プロジェクトと、自分が開発中のプロジェクトの構成の違いを観察し、修正する。

2015年3月10日火曜日

AdMobのInterstitial ad

AdMobのInterstitial ad

詳しくは次のサイトに掲載されています。
Interstitial Ad
Integrating New Google Admob with Banner and Interstitial ads
AdMob インタースティシャルの設置

私のJavaコードは次のとおりです。
private class InterstitialView{
    private InterstitialAd adInterstitial;
    public InterstitialView(Context context) {
        Builder adB = new AdRequest.Builder();
        adB.addTestDevice("__xxxx__");
        adInterstitial = new InterstitialAd(context);
        adInterstitial.setAdUnitId("__ID__");
        adInterstitial.loadAd(adB.build());
    }

    public boolean show(){
        boolean b = adInterstitial.isLoaded();
        if(b==true) adInterstitial.show();
        return b;
    }
}

private InterstitialView adInterstitialView = null;
private void ShowInterstitialAD(){
    if(adInterstitialView==null) return;
    adInterstitialView.show();
}

表示は、Activityのように、全画面表示になってしまいます。
全画面であるため、バナー広告のように表示位置を指定する必要はありません。つまり、xmlファイルに何かレイアウトを施す必要はありません。
携帯端末だと、広告内容の絵柄が全画面に表示されますが、タブレット端末の場合は、画面中央に絵柄が配置されて、周囲が濃い灰色で表示されます。
端末利用者は、左上にある、小さい×ボタンを押して、画面を非表示にすることになります。タブレット端末の視点からすると、本当に小さい。

この画面は、端末利用者に次の疑問を持たせます。
むむむ。いきなり画面が変わったぞ? アプリが壊れたのか? アプリの一部なのか? 広告なのか? どうやって閉じるのだ?
このように、違和感を持つと思います。

Google Play Supportは、このような仕様はリジェクトはしないのですね。同じGoogleが作ったからね。

2015年3月1日日曜日

BroadcastReceiverの遅延及びServiceとの関係

BroadcastReceiverの遅延及びServiceとの関係 2題

BroadcastReceiverの遅延
BroadcastReceiverではonReceive()で情報を受け取ることになります。
EclipseでCleanコマンドを実行した後で、デバッグによるアプリの実行を行うと、アプリの稼働後(onCreate()が呼ばれた後)に(アプリを更新したことを原因として)BroadcastReceiverのonReceive()が実行されてしまう場合があります。
止むを得ないため、アプリにおいて、こういう状況ではアプリを終了させるようにコーディングを行いました。
つまり、アプリを再起動させなければならないということです。

Serviceの終了行程時におけるBroadcastReceiverによる呼び出し
Serviceが役目を果たし終えたので、Service内においてstopSelf()を実行させたところ、onDestroy()が呼ばれる前に、BroadcastReceiverのonReceive()が呼ばれ、ServiceのonStartCommand()が呼ばれました。
既にstopSelf()が実行された後なので、BroadcastReceiverに基ずく仕事は何もできませんでした。
このようなタイミングはほとんど発生はしないです。人工的に作ろうとしても難しいでしょう。
しかしながら、発生してしまった以上、見逃すことはできないため、アプリの設計を見直しました。
stopSelf()を実行したのであれば、それ以降は、Serviceはメッセージを受け取らないという仕様にして欲しかったです。

開発環境:Windows & Eclipse
Android 4.4W.2(API20)

2015年2月21日土曜日

ServiceのonRebind()の振る舞い

ServiceのonRebind()の振る舞い

Activityが終了した時、同時にServiceも終了させるというのが一般的であろう。
ただし、アプリの仕様によっては、Activityの終了後もServiceを稼働させたい場合がある。
つまり、「Activityとの通信を行うこと」と「Service独自の業務を行うこと」の2個の業務をServiceが担っている場合である。
これを行うため、一方で、Activity-Service間のbindを確立させ、他方で、(Activityは)Serviceに対してダミーで(何もしない)Intentを発行しonStartCommand()を実行させる。こうすることによりonUnbind()後もServiceは終了はしないようになる。

onRebind()を実装しない場合の流れ
ActivityとServiceとをbindさせた後、Activityを終了させ、このServiceが生きた状態で、再度同じActivityを起動させ、再度同じServiceにbindService()した場合、Serviceにおいては、onBind()もonUnbind()も呼ばれない。
第2回目のActivityの起動でも、onServiceConnected()が呼ばれており、Activity-Service間の通信は行える。
通信だけが行えれば良いのであれば、onRebind()を設けなくても良い。

onRebind()を実装した場合の流れ
ServiceのonUnbind()においてtrueを返すようにした場合、第2回目に起動したActivityがServiceと通信をすると、Serviceにおいて、Activityの開始時にonRebind()が、Activityの終了時にonUnbind()が呼ばれる。onBind()は呼ばれない。

onRebind()が呼ばれるのは、ServiceがActivityと生死を共にしないように仕込んだ場合である。生死を共にすると、onRebind()は呼ばれることはない(当たり前)。

教科書には、Activityの死後直後に、再度Activityを起動-接続させるように書いてあるように思えたが、Activityの死後数時間後に接続させても問題はない(Serviceが生きていれば)。

onRebind()を実装する意義
Activity-Service間で接続-分離が繰り返される場合、その接続-分離のタイミングを捉えたい場合に使うのである。
Activityは定時で退社してしまうのに対して、Serviceは残業して残務整理をしなければならない場合があるだろう。残務整理をしていたら、気が付けば翌朝になっていて、Activityが出勤して来たっていう場合があるだろう。
Activityは定時に出勤-退社を繰り返しても良いが、Serviceは自分の仕事も終えなければ退社してはいけないのである。
onBind()、onRebind()及びonUnbind()を使ってActivityの出勤-退社のタイミングを捉えることにより、Serviceは自身の退社の時を自分で決定できる。