- 投稿日:2020-06-29T21:11:10+09:00
Splash画面を実装する方法
はじめに
Splash画面とは、アプリ起動時に最初に表示される画面です。アプリを起動するのに少なからず時間がかかります。その間に差し込む画面です。
Splash画面は、↑のGIFで写っているドロイド君の画面です。
この画面を作成する方法は大きく分けて2つあります。
1つ目は、Activityのテーマを変更する方法
2つ目は、Splash画面ようにActivityを作成する方法です。
今回はそれぞれの長所短所と実装方法についてまとめていきます。1つ目の方法
1つ目は、Activityのテーマを変更する方法です。アプリは起動すると、デフォルトでは
windowBackground
の画面が表示されるらしいです。このプレースホルダーをSplash画面に拡張します。実装
windowBackgroudcolor
に、これから作成するSplash画面を設定します。styles.xml... <style name="AppTheme.Splash" parent="Theme.AppCompat.DayNight.NoActionBar"> <item name="android:windowBackground">@drawable/splash_drawable</item> </style> ...今回用意したSplash画面は、中心にドロイド君が配置された画面になります。それを実装すると、
splash_drawable.xml<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@color/colorWhite" /> <item android:drawable="@drawable/ic_baseline_android_24" android:gravity="center" /> </layer-list>このようになります。真ん中に配置するドロイド君は、
New -> VectorAssetから作成してください。起動時のテーマを
AppTheme
からApptheme.Splash
に変更します。AndroidManifest.xml<activity android:name=".MainActivity" android:theme="@style/AppTheme.Splash">アプリが起動する準備ができたら、MainActivityの
setContentView()
で渡されたレイアウトが適応されます。その前にThemeを元のAppTheme
に変更しましょう。MainActivity.ktclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setTheme(R.style.AppTheme) setContentView(R.layout.activity_main) } }以上の実装でドロイド君が中心に配置してあるSplash画面を作成することができました。
長所
この実装方法では、アプリが起動する準備ができ次第、Splash画面から遷移するのでユーザーの時間を無駄にすることがありません。さらに、アプリがすでにメモリにある場合はあまり表示されることもないです。
短所
短所?ではないかもしれませんが、この実装だとアニメーションのような画面を表示することができません。2つ目の方法
2つ目は、Splash画面ようにActivityを作成する方法です。この方法だと、1つ目の方法でできなかったアニメーションや複雑な画面を実装することができます。
実装
この方法では、様々なやり方があると思いますが今回はCoroutineを使って実装します。
まず、Gradleに依存関係を記述します。build.gradledependencies { //Coroutine implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7' }新たに
SplashActivity
というActivityを作成します。SplashActivity.ktclass SplashActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_splash) CoroutineScope(Dispatchers.Main).launch { delay(2000) val intent = Intent(this@SplashActivity, MainActivity::class.java) startActivity(intent) finish() } } }このActivityでは2秒のdelayの後、MainActivityに遷移するという実装をしてあります。Splash画面用にActivityを作成することにより、レイアウトにアニメーションを追加したり、様々なことができます。今回はプログレスバーを表示させるSplash画面を作成しました。
activity_splash.xml<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" > <androidx.appcompat.widget.AppCompatTextView android:id="@+id/splash" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Slash" android:textSize="40dp" app:layout_constraintBottom_toTopOf="@id/progressbar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ProgressBar android:id="@+id/progressbar" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/splash" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>AndroidManifestに、起動時のActivityをSplashActivityに指定してあげれば完了です。
AndroidManifest.xml<activity android:name=".SplashActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".MainActivity" />
- 投稿日:2020-06-29T18:00:29+09:00
Apps Up 2020: アプリコンテストのご案内
革新的なアプリの作成、Huaweiモバイルサービスでアプリを最適化
Apps Up 2020ーHuawei HMSアプリイノベーションコンテストはHuaweiのDigiX Geekフランチャイズの一部です。
DigiX GeekフランチャイズはHMS Coreオープン機能の促進、HMS コアキットの導入と使用による革新的なアプリの作成、170以上の国と地域の6億人のHuaweiデバイスユーザーへのより優れたスマートライフサービスの提供を促す、グローバルプログラムです。
DigiX Geekプラットフォームは、HMSに根ざしたグローバルモバイルエコシステムを革新し構築するために、開発の才能と愛好家の新時代を統合することを目的としています。登録参加対象者
- APAC地域に拠点を置くすべてのモバイルアプリ開発者は、Apps Up 2020 – APACに登録して参加することができます。
- 学生でも、趣味でも、プロでも、新興企業、中小企業、多国籍企業など、どなたでもご参加いただけます。
- 既存のAndroidアプリがある。
- Huawei AppGallery用のまったく新しいアプリを作成する予定。
- Huawei AppGalleryでアプリを公開またはランチしている。
基準
- コンテストに提出するアプリは、Huawei HMSオープン機能を活用し、少なくとも1つのHMSコアキットを導入する必要があります(HMSコアキットの詳細については、こちらを参照してください)
- アプリは、HMSがインストールされている端末で完全に実行する必要があります。
ルール
- 個別にコンテストに参加するか、最大3人のメンバーで構成されるチームを構成できます。
- すべてのメンバーは、コンテストへの参加の一環として、Huawei IDを介してHuawei Developersにサインインする必要があります。 Huawei Developerアカウントをまだ持っていない場合は登録して、コンテストに参加する前に本人確認に合格するよう求められます。
- 各参加者は1つのチームにしか参加できないことに注意してください。 また、各チームは、メンバーの招待/削除、作品の提出など、オンラインでのチームメンバーの管理管理を担当するチームリーダーを特定します。
- 各個人は、チームへの参加または脱退を申請できます。
- 各個人またはチームは、Huawei Developers契約またはHuawei AppGalleryポリシーに完全に準拠する必要があります。
- チームのコンテストリージョンは、チームリーダーがHuawei Developersで開発者として登録するリージョンに従います。
- 所有者の事前の承認の下で、提出されたアプリはサードパーティのゲームエンジン、ミドルウェア、オープンソースソフトウェア、またはコードライブラリを使用する場合があります。
失格となるシナリオ
- 提出されたアプリが1つもHMS Coreキットを導入していない場合。
- 盗作。
- サードパーティのコードプラグインの使用許可を取得できませんでした。
- 提出されたアプリは、Huawei Developers契約またはHuawei AppGalleryポリシーに違反していることが判明しました。
登録方
作品提出
- チームリーダーは、イベントのウェブサイトに記載されている規定の期限までに作品を提出する必要があります。
- 作品提出は2つのステップで構成されています:
- AppGallery Connectを使用してアプリを作成すると、アプリIDが発行されます。 Huawei AppGalleryへのアプリの作成と送信については、こちらをご覧ください。
- チームリーダーは、Apps Upイベントチームページに進み、コンテスト提出要件を完了します。
- アプリ名
- アプリID
- アプリの説明
- 仕事の添付ファイル–アプリの.apkファイル、仕事の紹介ドキュメント、およびアプリの機能と操作を紹介するマーケティングビデオを含める必要があります(ZIPまたはRAR形式である必要があり、200 MBを超えることはできません。例外はすべて、appsup.apac@huawei.comまでご連絡ください)
- 各チームは最大5つのアプリを提出できます。
評価基準
報酬と受賞カテゴリー
HMSについて学ぶ
実際端末の使用に基づく自動テストプロセスの完全なセット。 これにはHuawei Developer consoleか らアクセスできます。
- APAC 地域のオンラインワークショップ
APACリージョナルオンラインワークショップを通じて、HMSコアキットと導入するための個別のトレーニングを受けます。 appsup.apac@huawei.comで関心を示していただければ、ご連絡いたします。
- 投稿日:2020-06-29T14:07:01+09:00
Realmの基本的な使い方まとめ in Kotlin
1.概要
RealmはSQLiteに代わる軽量、高速データベースでアプリ開発にも利用できます。
そのRealmが7.0.0になりKotlinの正式ドキュメントも公開されました!
せっかくなのでAndroidアプリでの簡単な使い方をまとめます。
7.0.0はbeta版で、安定板は6.0.2になります。お好みでどうぞ!■URL
【公式】Kotlinドキュメント
https://realm.io/docs/kotlin/latest/
【公式】Javaドキュメント
https://realm.io/docs/java/latest/
自作サンプル(INSERT,SELECT,DELETEしているだけ)
https://github.com/KIRIN3git/RealmTest2.事前準備
build.gradleは7.0.0と6.0.2で設定が違います。
build.gradle【7.0.0】buildscript { repositories { jcenter() google() maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' } } dependencies { classpath "io.realm:realm-gradle-plugin:7.0.0-beta-SNAPSHOT" } } allprojects { repositories { jcenter() google() maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' } } }build.gradle【6.0.2】buildscript { repositories { jcenter() } dependencies { classpath "io.realm:realm-gradle-plugin:6.0.2" } }[app]build.gradleapply plugin: 'com.android.application' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'realm-android'(注意)kotlinプラグインの後にrealm-androidでないとダメ
3. 初期化と設定
初期化と設定を行います。
初期化の場所はどこでもOKですが、最初に呼び出せば良いのでApplicationクラスがオススメだそうです。設定はDBを分けたり、拡張したり、永続化せずインメモリにしたり、リードオンリーにしたりするのに利用するそうです。
設定しなくても動きます。
・参考
RealmConfigurationMainApplication.ktclass MainApplication : Application() { override fun onCreate() { super.onCreate() // 初期化 Realm.init(this) // 設定 val config = RealmConfiguration.Builder() .name("myrealm.realm") .encryptionKey(getMyKey()) .schemaVersion(42) .modules(MySchemaModule()) .migration(MyMigration()) .readOnly() .inMemory() .build() Realm.setDefaultConfiguration(config) } }4. モデルクラスの作り方
データをinsertしたりselectするためのデータの型を作成
- 手順
- RealmObjectを継承(必須)
- openに設定(必須)
- 必要に応じてアノテーションを設定
- @PrimaryKey プライマリーキー
- @Index インデックス
- @Ignore 保存したくない項目
- @Required 必須項目
プライマリーキーを設定するとcopyToRealmOrUpdateまたはinsertOrUpdateを使用できます。
Person.ktopen class Person( @PrimaryKey var id: Long = 0, var name: String = "", var age: Int = 0 ) : RealmObject()5. 登録方法(INSERT)
- 手順
- インスタンスを作成
- トランザクションでラップ
- insert
- インスタンスを閉じる
インスタンスはアクティビティ内などで使いまわしても良いです。
そしてインスタンスは必ず閉じましょう!
トランザクションの方法は色々あります。
非同期で使いたい場合はexecuteTransactionAsyncRealmSql.kt// インスタンス作成 var realm = Realm.getDefaultInstance() val personData = Person(1,”Taro”,15) realm.beginTransaction() realm.insert(personData) realm.commitTransaction() // インスタンスを閉じる realm.close()6. 取得方法(SELECT)
手順
- インスタンスを作成
- データの取得
- 絞り込み
- メモリ外にコピー
- インスタンスを閉じる
主な取得関数
- findAll:クエリ条件を満たすすべてのオブジェクトを検索します
- findAllAsync:バックグラウンドスレッドで非同期に動作します
- findFirst(またはfindFirstAsync):クエリ条件を満たす最初のオブジェクトを見つける
その他関数はこちら(Javaだけど・・・)
https://realm.io/docs/java/3.5.0/api/io/realm/RealmQuery.html利用方法はこちらを参照
https://realm.io/docs/kotlin/latest/#queriesRxJavaも利用できる!(試してはいない・・・)
https://realm.io/docs/kotlin/latest/#rxjava(注意) realmオブジェクトはRealmインスタンスをクローズすると消えてしまう。kotlinオブジェクトとして使いたい場合はcopyFromRealmを使用!
RealmSql.kt// インスタンス作成 var realm = Realm.getDefaultInstance() // 全取得 val users = realm.where<Person>().findAll() // 絞り込み users.equalTo("name", "John”) // メモリ外にコピー val user = realm.copyFromRealm(users[0]) // インスタンスを閉じる realm.close()7. 削除方法(DELETE)
- 手順
- インスタンスを作成
- トランザクションでラップ
- delete
- インスタンスを閉じる
RealmSql.kt// インスタンス作成 var realm = Realm.getDefaultInstance() realm.beginTransaction() // 削除 persons.deleteAllFromRealm() realm.commitTransaction() // インスタンスを閉じる realm.close()
- 投稿日:2020-06-29T13:21:07+09:00
Facebookアプリからのディープリンク【Android編】
はじめに
※本記事では通常のwebなどからのディープリンクについては記載を割愛しますm(_ _)m
前回の記事に続き、今回はAndroidのFacebookアプリからディープリンクについてを記事にしたいと思います。
有料(従量課金)のツールとか使えば楽できるみたいなのですが、今回のミッションはFacebookアプリからだけなのでお金をかけずにとのことでした。
ので自前で実装することになったんですが…かなり苦戦しましたので記事にして残しておきたいと思います。実現方法
どのように実現するか調べたらまず公式のApp Links on Androidが見つかりました。
早速導入して試したんですが、全く機能せず…色いろ調べても解決方法が見つけられず…
一旦諦めました(><)が、ベテラン技術者の方に色々調べてもらいサーバ側にプログラムを仕組んだらできるのでは無いか?ということで試していただくことに。
導入したのは
- Chromeのintent
- App Links on Android
の2つを導入しました。
が、おそらくApp Links on Androidだけで動くと思われますのでChromeのintentは概要だけ記載します。Chromeのintent
App Links on Android以外にもintent起動する方法があって最初はこれを実装していました。
以下が構成イメージです。meta情報を加工してリダイレクトするだけの割とシンプルな構成です。
ただ古い機種(ASUS Zenfone4:Android8)ならうまくいくんですが、比較的新しい(Android10とか)の機種ではなぜかうまくいきませんでした。原因はわからないまま…
なので次項のApp Links on Androidの導入に至りました。App Links on Android
こちらが本題。以下のようなイメージ構成です。
iOSの時と大差無いですが、違っているのはサーバ側にプログラムを仕組んだことです。
通常のmetaデータだけだとFacebookアプリがうまくintent起動してくれないのでサーバ側でmetaデータを加工してあげることになりました。
Androidの場合は①③④をFacebookアプリがやってくれます。サーバ側
Facebookアプリからのディープリンク【iOS編】の時と同じようにサーバ側にページを用意し
[https://hogehoge.com]
にアクセスがきたら用意したページに飛ぶようにサーバ側で設定します。
今回はmetaデータの加工が必要なのでphpファイルを準備しました。そして、そのphpでmetaデータを加工する処理を仕組みます。
※パラメータの解説はApp Linksの技術文書を和訳してみましたが参考になりました。<?php // 定数定義 define('ANDROID', 0); define('IOS', 1); define('OTHER_OS', 999); define('FACEBOOK', 0); define('OTHER_BROWSER', 999); define('APP_NAME', 'あなたのアプリの名前'); define('ANDROID_STORE', 'https://play.google.com/store/apps/details?id=あなたのアプリID'); define('ANDROID_SCHEME', 'hogehoge-app'); define('ANDROID_CUSTOM_URL_HOST', 'hogehoge-link'); define('ANDROID_PACKAGE', 'あなたのアプリのパッケージ名'); // サーバ変数からホスト、url、ユーザエージェント取得 $httpHost = $_SERVER[HTTP_HOST]; $requestUri = $_SERVER[REQUEST_URI]; $uerAgent = $_SERVER[HTTP_USER_AGENT]; header("Content-type: text/html; charset=utf-8"); $os = findOS($uerAgent); $browser = findFacebook($uerAgent); if($os === ANDROID || $browser === FACEBOOK) { $host = ANDROID_CUSTOM_URL_HOST; $scheme = ANDROID_SCHEME; // metaデータに出力するlink $androidDeeplink = "$scheme://$host$requestUri"; } function findOS($uerAgent) { if(preg_match('/Android/ui', $uerAgent)) { return ANDROID; } else if(preg_match('/iPhone|iPod|iPad/ui', $uerAgent)) { return IOS; } return OTHER_OS; } function findFacebook($uerAgent) { if(preg_match('/FB_IAB/ui', $uerAgent)) { return FACEBOOK; } else if(preg_match('/facebook/ui', $uerAgent)) { return FACEBOOK; } return OTHER_BROWSER; } ?> <html> <head> <meta property="al:web:should_redirect" content="false" /> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta property="al:android:package" content="<?php echo ANDROID_PACKAGE ?>" /> <!-- ここで加工したlinkを出力。このmetaタグでFacebookアプリが動いてくれる --> <meta property="al:android:url" content="<?php echo $androidDeeplink; ?>" /> <meta property="al:android:app_name" content="<?php echo APP_NAME ?>" /> <!-- キャッシュは無効にしておく(でも、多分OGP情報のキャッシュには無力と思われる) --> <meta http-equiv="cache-control" content="max-age=0" /> <meta http-equiv="cache-control" content="no-cache" /> <meta http-equiv="expires" content="-1" /> <meta http-equiv="expires" content="Tue, 31 May 2011 10:15:00 GMT+3" /> <meta http-equiv="pragma" content="no-cache" /> </head> <body style="font-size: medium;"> <!-- アプリがインストールされていない場合ストアに遷移させる --> <?php if($os === ANDROID || $browser === FACEBOOK) { ?> <script> window.onload = function() { setTimeout(function() { window.location = "<?php echo ANDROID_STORE; ?>"; }, 3000); }; </script> <?php } ?> </body> </html>※自分のAndroidはPlay Storeで探しましょう。
※Androidの説明なのでiOSの記載は省略しています。iOSも同じurlを使う場合はFacebookアプリからのディープリンク【iOS編】の処理をphpファイルに移植してください。アプリ側
まずはintent起動できるようにAndroidManifest.xmlに追記します。
<activity android:name=".DeepLinkActivity" android:launchMode="singleTop" android:noHistory="true" android:screenOrientation="portrait" android:theme="@style/AppTheme.NoActionBar"> <!-- app linkとfacebookアプリからのアプリを起動は共存できないのでapp linkは使わない。 --> <!-- deep link for facebook app --> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <!-- dataタグのschemeとhostはphpで定義したANDROID_SCHEMEとANDROID_CUSTOM_URL_HOSTに合わせます --> <data android:scheme="hogehoge-app" android:host="hogehoge-link" /> <!-- リンクさせるパスを追記します --> <data android:pathPattern="/top" /> <data android:pathPattern="/setting" /> </intent-filter> </activity>次にディープリンクを制御するアクティビティを準備します
import android.content.ComponentName import android.content.Intent import android.net.Uri import android.os.Bundle class DeepLinkActivity { companion object { private const val PATH_TOP = "/top" private const val PATH_SETTINGS = "/setting" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val intentFromDeepLink = intent /** 取れるのはこんな情報 Intent { act=android.intent.action.VIEW dat=hogehoge-app://hogehoge-link/top&target_url=https://hogehoge.com/top } **/ val newIntent = Intent(Intent.ACTION_MAIN) newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) var originalUrl: String? = null // facebookアプリからurl情報を受け取り intentFromDeepLink?.let { facebookIntent -> val uri = facebookIntent.data /** uriはこんな情報 hogehoge-app://hogehoge-link/top&target_url=https%3A%2F%2Fhogehoge.com%2Ftop **/ val facebookUrl = uri?.getQueryParameter("target_url") facebookUrl?.let { originalUrl = it /** originalUrlはこんな情報 https://hogehoge.com/top **/ } } // urlを元に起動する画面のintentを設定する originalUrl?.let { val uri = Uri.parse(it) setIntent(uri, newIntent) } startActivity(newIntent) } private fun setIntent(uri: Uri?, intent: Intent) { if (uri == null) { setIntentToTop(intent) return } when (uri.path) { PATH_TOP -> { setIntentToTop(intent) } PATH_SETTINGS -> { setIntentToSetting(intent) } else -> { setIntentToTop(intent) } } } private fun setIntentToTop(intent: Intent) { val cls = TopActivity::class.java intent.component = ComponentName(packageName, cls.name) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) } private fun setIntentToSetting(intent: Intent) { val cls = SettingActivity::class.java intent.component = ComponentName(packageName, cls.name) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) } }これでFacebookアプリからのディープリンクができるようになったと思います。
iOSと同じようにOGP情報のキャッシュがあるので、サーバ側を更新したらOGPキャッシュのクリアは必須だと思っていいです。終わりに
私一人では解決できなかったですが、ベテラン技術者の方に協力いただいてなんとか機能させることができました。大感謝です。
Facebook公式に書いてある情報だけでは実現できないって…ちょっと不親切かな〜と思いました。
他のアプリの中身はわからないので手探り状態でしたがとってもいい経験になったと思います。日本語の記事は見当たらなかったので誰かのお役に立てたらなと思います。
- 投稿日:2020-06-29T13:08:13+09:00
KotlinでTCP/IP送信
はじめに
AndroidでTCP/IP通信をしようとするとうまくいかないので、その対応方法の記録です。
2018年ごろに作られているTCP/IPのサンプルがまったく動作しないのでその原因を調査しました。サンプル1
MainActivityのボタンクリックイベント内に
sample1.ktvar socket : Socket? = null try { val socket = Socket("localhost", 12345) val writer : PrintWriter = PrintWriter(socket.getOutputStream(), true) writer.println("serial test") } if (socket != null) { socket.close() }と代表的なサンプルを書いて動作させてみましょう。
コメントの方にすばらしく簡潔なサンプルがありますのでそちらも参考にして下さい。
これは動作しません。動作しないと言うより接続に行きません。
エラー内容を見るとパーミッションを指定して下さいと表示されるのでAndroidManifest.xmlを修正します。AndroidManifest.xml<application 中略 android:usesCleartextTraffic="true" 中略 </application> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />INTERNETの指定がインターネットへ接続可能にする設定
ACCESS_NETWORK_STATEはネットワークの様々なやりとりを有効にする設定
android:usesCleartextTraffic="true"は暗号化無しの通信を許可する設定です。INTERNET以外の設定は不要と思いますが今後のバージョンアップでまた悩むのが嫌なのであらかじめ付けてます。
そして実行すると今度はアプリが落ちます。
エラーメッセージに若干のヒントはあるのですが要は「ボタンクリックイベントから呼ぶな」と言っているようです。サンプル2
じゃあ簡単なスレッドを作ってその中で動作させてみます。
コメントの方にすばらしく簡潔なサンプルがありますのでそちらも参考にして下さい。sample.ktval runnable = object : Runnable { override fun run() { var socket : Socket? = null try { socket = Socket("localhost", 12345) val writer : PrintWriter = PrintWriter(socket.getOutputStream(), true) writer.println("serial test") } finally { if (socket != null) { socket.close() } } } } val thread = Thread(runnable) thread.start()今度は動作します。
まとめ
元々接続処理を行うと指定しても一定時間処理が返ってこないのでメインで処理する物ではありませんが、だからといってアプリごと落とさなくても・・・
- 投稿日:2020-06-29T10:45:01+09:00
Androidエミュレータ(Windows)で使用するシステムイメージを差し替える・/systemの内容を変更する
はじめに
Android Studioでアプリ開発するにあたり、SDK Managerでエミュレータとシステムイメージを取得して使用していましたが
・自前でAOSPからビルドしたシステムイメージを使う
・エミュレータで/system以下に変更を加える(アプリをpushするなど)
という必要があり、それで行き詰まった部分があるので、解決した方法と合わせて記録しておきます。わたしの環境
Android Studio/エミュレータ動作環境
- Windows 10 Home 64bit
- CPU: Core i7-8700 @ 3.20GHz
- RAM: 16GB
- Android Studio: 3.6.3
- Android Emulator: 30.0.12
AOSPビルド環境(仮想PC)
- Oracle VM VirtualBox 6.0
- Ubuntu 16.04 LTS
- AOSPソースコード: Android Pie android-9.0.0_r42
自前でビルドしたシステムイメージを使いたい
困ったこと
エミュレータを使用する際はAVD Managerで何らかのデバイスを作成→システムイメージを選択します。
このシステムイメージはAndroid各バージョン(私の環境ではAPIレベル30~古いのは7), x86-32/64, Google Play有無など選ぶことができますが、自前でカスタマイズしたシステムイメージを使用したいということもあると思います。私の場合はAndroid9に対し、ビルドオプション変更やプリインストールのアプリを追加したものを使いたかったので、AOSPでx86-64bit向けのシステムイメージ(GSI)を作成しました。
詳細な流れは省略しますが、Ubuntu上でビルド環境の構築→ソース取得→ビルド環境選択→ビルドです。
詳しくはこちらやこちらやこちらですね。ビルドの流れ(ざっくり例)$ sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev libgl1-mesa-dev libxml2-utils xsltproc unzip $ mkdir <作業ディレクトリとか> $ repo init -u https://android.googlesource.com/platform/manifest -b android-9.0.0_r42 $ repo sync $ source build/envsetup.sh $ lunch aosp_x86_64-userdebug $ make -j8出力先ディレクトリ(/home/<user>/<workspace>/out/target/product/generic_x86_64)にこんなイメージ群ができあがりました:
$ ls | grep img cache.img encryptionkey.img ramdisk.img system-qemu.img system.img userdata.img vbmeta.img vendor-qemu.img vendor.imgこれで作成したシステムイメージをエミュレータで使用したかったのですが…AVD Managerでは自前で用意したシステムイメージを選択するようなメニューが無かったので困りました。
解決方法
エミュレータ起動時のオプションで
C:\xxx\Android\android-sdk\tools>emulator -system <自前でビルドしたイメージ置き場>/system-qmeu.img -avd xxxでシステムイメージを参照することで、そのイメージを使用してエミュレータが起動します。
ポイントはsystem.imgではなくsystem-qemu.imgを使用することでしょうか。(system.imgだと起動しませんでした)
vendorも同様に -vendor <自前でビルドしたイメージ置き場>/vendor-qmeu.img のオプションを使用して自前のイメージが使えます。起動オプションで指定するのが面倒な場合は、SDK Managerで取得したシステムイメージ置き場のファイルを直接置き換えれば済みます。自分はそうしてしまいました。
C:\xxx\Android\android-sdk\system-images\android-28\default\x86_64\
system.img ←system-qmeu.imgをリネームして配置
vendor.img ←vendor-qmeu.imgをリネームした配置※注意
自前のシステムイメージを置き換えて使用する際は、置き換え前と後でAndroidのバージョンやアーキテクチャを合わせる必要があります。
SDK Managerでx86-32bit用で取得したシステムイメージに対し、x86-64bit用でビルドしたイメージを置き換えても正常に動作しない(はず)です。/systemパーティションの内容を変更したい
困ったこと
自前でビルドしたシステムイメージでエミュレータを起動することはできましたが、それに追加で/systemパーティションにアプリやライブラリをpushしようとしてもRead-Onlyでできません。
よくある解決方法として、/systemをリマウントするというものがありますが、どうにもうまくいきません。mountコマンドC:\>adb shell $ mount -o,rw remount /system $ mount: 'remount'->'/system': No such file or directoryadbコマンドC:>adb root C:>adb remount remount of the / superblock failed: Permission denied解決方法
Android公式に解決できる起動オプションが載っていました。
-writable-system
エミュレーション セッション中に書き込み可能なシステム イメージを作成する際にこのオプションを使用します。方法は次のとおりです。
1. -writable-system オプションを使用して仮想デバイスを起動します。
2. コマンド ターミナルから adb remount コマンドを入力し、system/ を読み取り / 書き込み用として再マウントするようにエミュレータに指示します(デフォルトでは読み取り専用としてマウントされます)。writable-systemで起動C:\xxx\Android\android-sdk\tools>emulator -writable-system -avd xxx emulator: WARNING: System image is writable ... emulator: INFO: boot completedからのadbコマンドC:>adb root C:>adb remount remount succeededすんなりいけました!
ここで/systemに加えた変更はエミュレータを落としても保持されます。
次回、また-writable-system
オプションで起動すれば前回の変更内容が反映された状態です。
ただ-writable-system
なしで起動した際は、/systemの変更なしの初期状態となります。(そしてRead-Onlyです)
また通常Read-Onlyなのは/systemだけでなく/vendorなども同様ですが、-writable-system
でエミュレータを起動すると、/vendorなども変更が可能となります。なお各AVDの設定やユーザーデータの類は以下の場所に置かれていますが、デフォルトの状態に対して加えた変更は"xxx.img.qcow2"ファイルに書かれているようです。
AVD ManagerなどでWipe Dataするとすべての変更はなくなります。C:\Users<user>.android\avd\xxx.avd\
userdata-qemu.img.qcow2
system.img.qcow2
vendor.img.qcow2
…userdata-qemu.img.qcow2は
-writable-system
なしで起動しても毎回エミュレータ終了時に更新されて保存されますが
system.img.qcow2, vendor.img.qcow2 などは-writable-system の場合のみ更新されることがわかります。最後に
当然といえば当然ですが、実機とエミュレータとではいろいろと扱い方に違いがあるのだなと感じました。
また何か困りごと&解決方法が出てきたら追記しようと思います。以上です。
- 投稿日:2020-06-29T06:35:34+09:00
Flutterで画像、カスタムフォントを使用する方法
今回はFlutterアプリで画像とカスタムフォントを使用する方法を記述します。
プロジェクトに画像とフォントを追加する
- プロジェクト直下に
assets
フォルダを作成し、その直下にimages
とfonts
フォルダを作成する。- 使用したい画像とフォントを各フォルダに格納する。
pubspec.yaml
にパスを記述するFlutterで画像やフォントを使用するには、
pubspec.yaml
にパスを記述する必要があります。pubspec.yamlflutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: assets: - assets/images/ - assets/fonts/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: fonts: - family: OpenSansCondensed fonts: - asset: assets/fonts/OpenSansCondensed-Light.ttf weight: 300 - asset: assets/fonts/OpenSansCondensed-Bold.ttf weight: 700フォントファイルは太さによって複数に分かれていることが多いので、それぞれにweightを定義します。
pubspec.yaml
に記述する際、インデントが揃っていないと、エラーになったり、上手く適用されないなどがあるので、注意が必要です。画像を表示する
実際にアプリ上にプロジェクトに追加した画像を表示してみます。
aseetsの画像を表示する
main.dartimport 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Demo', home: Scaffold( appBar: AppBar( title: Text('Flutter Demo'), ), body: Center( child: Image.asset('assets/images/sample.png'), ), ), ); } }ネットワーク上の画像を表示させる
Image.network('イメージのURL')画像のURLを記述することで、URL先の画像を表示させることもできます。
カスタムフォントを使用する
カスタムフォントを適用する方法は主に2種類あります。
アプリ全体のデフォルトに設定する
MaterialApp()
のtheme
に指定してあげることで、デフォルトのフォントに設定されます。main.dartclass MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Demo', theme: ThemeData(fontFamily: 'OpenSansCondensed'), home: SamplePage(), ); } }個別に指定する
特定のTextにのみ適用させたい場合は、以下のように
TextStyle
のfontFamily
に指定します。
fontWeight
にpubspec.yaml
に記述したweightを指定することで、それぞれのフォントファイルを使用することができます。main.dartText( 'custom font', style: TextStyle( fontFamily: 'OpenSansCondensed', fontWeight: FontWeight.w300, fontSize: 40, ), ),おまけ
フォントファイルをわざわざ用意しなくとも、google_fontsを使用することができるFlutterのパッケージがありますので、そちらを利用してみるのもいいかもしれません。
https://pub.dev/packages/google_fonts
- 投稿日:2020-06-29T00:02:57+09:00
Dagger Hilt触ってみた
はじめに
巷で噂のDagger Hiltをようやく触ってみました。Codelabを一通り終わったので、実際に過去の記事でDaggerを使ってDIしたのですがそれをHiltに移行した手順をまとめます。
Gradle
build.gradle// build.gradle (Project) ext.hilt_version = '2.28-alpha' dependencies { classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" }build.gradle// build.gradle (App) // Dagger_hilt implementation "com.google.dagger:hilt-android:$hilt_version" // 必須 kapt "com.google.dagger:hilt-android-compiler:$hilt_version" // 必須 def dagger_hilt_view_model_version = "1.0.0-alpha01" implementation "androidx.hilt:hilt-lifecycle-viewmodel:${dagger_hilt_view_model_version}" kapt "androidx.hilt:hilt-compiler:${dagger_hilt_view_model_version}"今回は、ViewModelを使うので、
hilt-compiler
とhilt-lifecycle-viewmodel
を追加した。実装
移行の順番は、
1. Application & @Singleton
2. Fragment & Activityということで、Applicationを見てみましょう。
Application
Hiltに移行する前のコードがこちらです。
Application
では、独自に設計したComponent
をインスタンス化していました。しかし、Hiltでは標準のComponentが用意されていてComponentを定義する必要がなくなったらしい。TodoApplication.ktclass TodoApplication : Application() { companion object { lateinit var component: AppComponent private set } override fun onCreate() { super.onCreate() component = DaggerAppComponent.factory().create(applicationContext) } }Hiltでは
Application
を@HiltAndroidApp
アノテーションをつけて定義します。TodoApplication.kt@HiltAndroidApp class TodoApplication : Application() { // companion object { // lateinit var component: AppComponent private set // } // // override fun onCreate() { // super.onCreate() // component = DaggerAppComponent.factory().create(applicationContext) // } }このコードからわかるように
Component
自体もいらないので、AppComponent
を削除します。AppComponent.kt//@Singleton //@Component( // modules = [ // DatabaseModule::class // ] //) //interface AppComponent { // // @Component.Factory // interface Factory { // fun create(@BindsInstance context: Context): AppComponent // } // fun mainViewModel(): MainViewModel //}Activity
元々あった
Component
を削除したので、ActivityやFragmentにInjectできるように、@AndroidEntryPoint
アノテーションを記述しなければならない。MainActivity.kt@AndroidEntryPoint class MainActivity : AppCompatActivity() {今回は、Fragmentが登場しませんが、もしFragmentにInjectしたいときはそれを所持するActivityにも
@AndroidEntryPoint
を付ける必要があります。Module
Moduleの定義方法は、
@InstallIn(ApplicationComponent::class)
アノテーションをつける必要があります。@InstallIn
アノテーションでHiltが生成するComponentにModuleを紐付けます。DatabaseModule.kt@InstallIn(ApplicationComponent::class) @Module class DatabaseModule() { @Singleton @Provides fun provideTodoDatabase(@ApplicationContext context: Context) = Room.databaseBuilder(context, TodoDatabase::class.java, "database_name") .build() @Singleton @Provides fun provideTodoDao(todoDatabase: TodoDatabase) = todoDatabase.todoDao() }移行前は、
Context
をComponentのインスタンス生成する時に渡していたがそれをする必要がなくなりました。 すでにBinding済みのContextを使用することができます。@ApplicationContext
か@ActivityContext
をつけるだけで使用できます。ViewModel
ViewModelをHiltを使ってDIする場合、constructorに
@ViewModelInject
アノテーションをつけます。MainViewModel.ktclass MainViewModel @ViewModelInject constructor(private val repository: TodoRepository) : ViewModel() {最後Activityに、
MainActivity.ktprivate val mainViewModel: MainViewModel by viewModels()を記述すれば移行完了です。