phicdy devlog

Androidアプリ開発やその他技術系の記事をたまに書きます

AndroidとかiOSとかモバイル多め。その他技術的なことも書いていきます。

KotilnでTheoryとDataPointsを使ったJUnitのパラメータテストを書く

Kotlinに何も考えずにTheoryとDataPointsを使ったテストを書こうとするとデータがJUnitから参照できずエラーになる。 Kotlinにstaticはないのでcompanion objectを作って@JvmFieldをつけることでJUnitから参照できるようになる。

@RunWith(Theories::class)
class SampleTest {

    companion object {
        @DataPoints
        @JvmField
        val data = listOf(
                1 to 1,
                2 to 4,
                3 to 9
        )
    }

    @Theory
    fun testSquare(testData: Pair<Int, Int>) {
        val result = Calc().square(testData.first)
        assertEquals("expected:" + testData.second + " , result: " + result, 
                      testData.second, result)
    }
}

【Android】Activityを2重で開かないようにする

なにも考えずに実装を行うとActivityが2重で開いてしまうことがある。 例えばボタンを押して次のActivityが開くとき、そのActivityやIntentに設定をしていなければ、素早く連打することで2重に開くことができる。

修正方法

launchModeは指定しないとstandardに設定される。singleTopはほとんどstandardと同じだが、タスクのスタックの一番上に既存のActivityのインスタンスが既に存在する場合挙動が異なる。

"standard" モードと "singleTop" モードの違いは、1 点だけです。 "standard" アクティビティが新たなインテントを受け取るたびに、クラスの新しいインスタンスが作成され、そのインテントに応答します。各インスタンスは 1 つのインテントを処理します。同様に、"singleTop" アクティビティの新しいインスタンスは、新しいインテントを処理するために作成されることもあります。 しかし、ターゲット タスクのスタックの一番上に、既存のアクティビティのインスタンスが既に存在する場合、そのインスタンスが新しいインテントを受け取ります(onNewIntent() の呼び出し)。新しいインスタンスは作成されません。その他の状況では、たとえば、"singleTop" アクティビティの既存のインスタンスがターゲット タスク内に存在するもののスタックの一番上に配置されていない場合、あるいはスタックの一番上に存在するもののターゲット タスク内ではない場合、新しいインスタンスが作成され、スタック上にプッシュされます。

つまり連打で1回Activityを開き2回目が開かれたとしても、そのActivityを使いまわし、onNewIntent()が呼ばれるという流れになる。これでActivityが2重で開くのを防ぐことができる。

ただstartActivityForResult()の場合はこれが効かず毎回新しいActivityが生成される。未調査だが恐らく結果を次のActivityが受け取るという特性上、Activityごとに結果が違うだろうということで新しいインスタンスが作られるのではないかと思う。これはstartActivityForResult()に渡すIntentにFLAG_ACTIVITY_CLEAR_TOPを指定してあげれば解決できる。

タスクがA→B→C→DとあったときにDからBをFLAG_ACTIVITY_CLEAR_TOP付きで呼ぶと、新しいインスタンスを作らずにC, Dを終了してA→Bとする。今回のケースの場合、A→B→C→DでEをstartActivityForResult()で呼び、A→B→C→D→Eとなった直後にEを呼ぶので、新しいインスタンスを作らずにA→B→C→D→Eのままになる。

最後にアンインストールダイアログなどAndroidのシステムのActivityを呼ぶときも同様にFLAG_ACTIVITY_CLEAR_TOPを付ければ同様に解決できる。これらはsingleTopをアプリ側から指定することができない。

【Android】画像が押されたときの背景を簡単につける

ImageButtonを使えば簡単にできたので自分用にメモ。

<ImageButton
  android:id="@+id/ib_button"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:src="@drawable/your_imgae"
  android:background="?android:selectableItemBackground"
  android:contentDescription="@string/your_image_desc" />

参考

stackoverflow.com

stackoverflow.com

Jenkinsのビルドの説明を自動的に設定する

Jenkinsのビルドでパラメータをつけてビルドするときに何のパラメータでビルドしたかいちいち確認するのが面倒なので自動化した。

まずパラメータに必要なビルドオプション(build_option)とビルドの説明(description)を入力できるようにする。

f:id:phicdy:20170927225527p:plain


そしてパイプラインの最初でビルドの説明変更のAPIを叩く。

sh 'curl -s http://[YOUR_JENKINS_IP]:[YOUR_JENKINS_PORT]/job/[YOUR_JOB_NAME]/${BUILD_NUMBER}/configSubmit -X POST --data-urlencode "json={\'displayName\':\'#${BUILD_NUMBER}\', \'description\':\'${build_option}\\n${description}\'}"'


あとはビルド時に何のビルドかの説明を入力すれば・・・

f:id:phicdy:20170927225832p:plain

自動的に説明欄に設定される。

f:id:phicdy:20170927225847p:plain

参考

blog.keshi.org

pycryptoのインストールがCentOS7.3 minimalで失敗する問題の対処

環境

[root@localhost ~]# cat /etc/redhat-release
CentOS Linux release 7.3.1611 (Core)

結論

gccpython-develをyumで入れる

調査

デフォルトで入れようとすると以下ログでこける。Cコンパイラがないとのことなのでgccを入れる

[root@localhost ~]# pip install pycrypto
...
    configure: error: in `/tmp/pip-build-EJivFC/pycrypto':
    configure: error: no acceptable C compiler found in $PATH
    See `config.log' for more details
...
Command "/usr/bin/python2 -u -c "import setuptools, tokenize;__file__='/tmp/pip-build-EJivFC/pycrypto/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().re
place('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record /tmp/pip-WsN8Es-record/install-record.txt --single-version-externally-managed --compil
e" failed with error code 1 in /tmp/pip-build-EJivFC/pycrypto/
[root@localhost ~]# yum install gcc

もう1回実行するとPython.hがないと言われる。調べた結果これはpython-develがないためとのことなので入れて解決。

...
    gcc -pthread -fno-strict-aliasing -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -std=c99 -O3 -fomit-frame-pointer -Isrc/ -I/usr/include/python2.7 -c src/MD2.c -o build/temp.linux-x86_64-2.7/src/MD2.o
    src/MD2.c:31:20: fatal error: Python.h: No such file or directory
     #include "Python.h"
                        ^
    compilation terminated.
    error: command 'gcc' failed with exit status 1

    ----------------------------------------
Command "/usr/bin/python2 -u -c "import setuptools, tokenize;__file__='/tmp/pip-build-2l6kpU/pycrypto/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record /tmp/pip-7LFD3X-record/install-record.txt --single-version-externally-managed --compile" failed with error code 1 in /tmp/pip-build-2l6kpU/pycrypto/
[root@localhost ~]# yum install python-devel

Android通知のバージョンごとのUIの違い

自分用にメモ。以下のコードを実行したときのAndroid OSバージョンごとのUIを調べた。

Intent intent = new Intent(context, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new NotificationCompat.Builder(context)
    .setAutoCancel(true) // Delete notification when user taps
    .setContentTitle("Test")
    .setTicker("ticker") // Message when notification shows for ~4.4
    .setContentInfo("content info")
    .setContentText("content text")
    .setSmallIcon(R.mipmap.ic_launcher)
    .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher))
    .setContentIntent(pi)
    .build();
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(ID, notification);
  • Android 4.4までは通知が表示されるときにTickerの文字が表示される。5.0以上では設定しても無視される。
  • Android 5.0からはSmall IconがBig iconの右下に表示される。
  • Android 7.0からはContent Infoが表示されない。
Notification Ticker
4.1 f:id:phicdy:20170625152335p:plain f:id:phicdy:20170625152426p:plain
4.2 f:id:phicdy:20170625152440p:plain f:id:phicdy:20170625152453p:plain
4.3 f:id:phicdy:20170625152506p:plain f:id:phicdy:20170625152521p:plain
4.4 f:id:phicdy:20170625152534p:plain f:id:phicdy:20170625152547p:plain
5.0 f:id:phicdy:20170625152601p:plain
5.1 f:id:phicdy:20170625152615p:plain
6.0 f:id:phicdy:20170625152628p:plain
7.0 f:id:phicdy:20170625152647p:plain

CircleCIでAndroid SDKとGradleをキャッシュする

CircleCIで普通にビルドしていると毎回Android SDKとGradleのダウンロードが行われてビルド時間が長くなってくる。CircleCIにはデフォルトのキャッシュ以外にも自分でキャッシュの設定ができるので、Android SDKとGradleをキャッシュすることでビルドを短くできる。~/.gradleはデフォルトでキャッシュされるようになっているようなので設定は不要。

dependencies:
  override:
   ...
  cache_directories:
    - /usr/local/android-sdk-linux/tools
    - /usr/local/android-sdk-linux/platforms/android-25
    - /usr/local/android-sdk-linux/platforms/android-23
    - /usr/local/android-sdk-linux/platforms/android-16
    - /usr/local/android-sdk-linux/build-tools/25.0.2

キャッシュを使うかダウンロードするかの判定

tools, platforms/android-xxにはsource.propertiesというファイルがある。これにバージョンがPkg.Revision=25.2.5のように書かれているので、この値を見ることでキャッシュを使うかダウンロードするかを決める。build-toolsとGradleはフォルダがあるかどうかで判定する。

dependencies:
  override:
    - if ! $(grep -q "Pkg.Revision=25.2.5" $ANDROID_HOME/tools/source.properties); then echo y | android update sdk --no-ui --all --filter "tools"; fi
    - if [ ! -e $ANDROID_HOME/build-tools/25.0.2 ]; then echo y | android update sdk --no-ui --all --filter "build-tools-25.0.2"; fi
    - if ! $(grep -q "Pkg.Revision=3" $ANDROID_HOME/platforms/android-25/source.properties); then echo y | android update sdk --no-ui --all --filter "android-25"; fi
    - if ! $(grep -q "Pkg.Revision=3" $ANDROID_HOME/platforms/android-23/source.properties); then echo y | android update sdk --no-ui --all --filter "android-23"; fi
    - if ! $(grep -q "Pkg.Revision=5" $ANDROID_HOME/platforms/android-16/source.properties); then echo y | android update sdk --no-ui --all --filter "android-16"; fi
    - if [ ! -e ~/.gradle/wrapper/dists/gradle-3.5-all ]; then ./gradlew init; fi
  cache_directories:
    - /usr/local/android-sdk-linux/tools
    - /usr/local/android-sdk-linux/platforms/android-25
    - /usr/local/android-sdk-linux/platforms/android-23
    - /usr/local/android-sdk-linux/platforms/android-16
    - /usr/local/android-sdk-linux/build-tools/25.0.2

注意点として先にAndroid SDKのアップデートをしないとGradleの初期化中にAndroid SDKの使用許諾が取れていなくて失敗する。

circle.yml

machine:
  java:
    version: openjdk8
  environment:
    ANDROID_HOME: /usr/local/android-sdk-linux
dependencies:
  override:
    - if ! $(grep -q "Pkg.Revision=25.2.5" $ANDROID_HOME/tools/source.properties); then echo y | android update sdk --no-ui --all --filter "tools"; fi
    - if [ ! -e $ANDROID_HOME/build-tools/25.0.2 ]; then echo y | android update sdk --no-ui --all --filter "build-tools-25.0.2"; fi
    - if ! $(grep -q "Pkg.Revision=3" $ANDROID_HOME/platforms/android-25/source.properties); then echo y | android update sdk --no-ui --all --filter "android-25"; fi
    - if ! $(grep -q "Pkg.Revision=3" $ANDROID_HOME/platforms/android-23/source.properties); then echo y | android update sdk --no-ui --all --filter "android-23"; fi
    - if ! $(grep -q "Pkg.Revision=5" $ANDROID_HOME/platforms/android-16/source.properties); then echo y | android update sdk --no-ui --all --filter "android-16"; fi
    - if [ ! -e ~/.gradle/wrapper/dists/gradle-3.5-all ]; then ./gradlew init; fi
  cache_directories:
    - /usr/local/android-sdk-linux/tools
    - /usr/local/android-sdk-linux/platforms/android-25
    - /usr/local/android-sdk-linux/platforms/android-23
    - /usr/local/android-sdk-linux/platforms/android-16
    - /usr/local/android-sdk-linux/build-tools/25.0.2
test:
  override:
    - ./gradlew assembleDebug
    - cp -r ~/$CIRCLE_PROJECT_REPONAME/app/build/outputs/apk/* $CIRCLE_ARTIFACTS
      # unit test
    - ./gradlew testDebugUnitTest 
    - cp -r ~/$CIRCLE_PROJECT_REPONAME/app/build/test-results/testDebugUnitTest/* $CIRCLE_TEST_REPORTS
deployment:
  master:
    branch: master
    commands:
      - ./gradlew assembleRelease

参考

https://circleci.com/docs/1.0/how-cache-works/https://discuss.circleci.com/t/installing-android-build-tools-23-0-2/924/6