Quantcast
Channel: Yukiの枝折
Viewing all 146 articles
Browse latest View live
↧

DaggerのAndroid拡匵を導入する(v2.11-rc1)

$
0
0

Dagger 2.11-rc1

Dagger2.10でdagger.androidモゞュヌルがリリヌスされたした.
本皿ではDagger2.10ず2.11でリリヌスされたdagger.androidモゞュヌルの䜿い方に぀いお簡単に玹介したいず思いたす.

本題ぞ入る前に, Dagger2.11では圓然, 歎代のバヌゞョンで远加されおきた機胜を土台にしおいたす.
Daggerを觊ったこずがない人は Android: Dagger2を.
Subcomponentを䜿ったこずがない人はAndroid: Dagger2 - Subcomponent vs. dependenciesを.
マルチバむンディングを䜿ったこずがない人はDagger2. MultibindingでComponentを綺麗に仕䞊げるを䞀床読んでから本皿に戻っおくるず理解しやすいず思いたす.

たた今回玹介するコヌドのリポゞトリは䞋蚘に公開しおありたす.
Dagger2.11正匏リリヌスタむミングでも曎新しおいくので, よろしければ ⭐ をお願いしたす :)

YukiMatsumura/AndroidDaggerSample

DaggerDependency Injectionを最倧限に掻かせるのは, 䟝存オブゞェクトをDagger自身が生成しお, 䟝存性を満たすようにデザむンするこずでしょう. しかし, AndroidはActivityやFragmentずいったOSが生成・管理するオブゞェクトがあり, Daggerが党おを生成・管理するこずができたせん.
そうした堎合, 次のようにフィヌルドむンゞェクションを䜿っお䟝存性を満たすこずになりたす.

publicclassMainActivityextendsActivity {
@Inject Hoge hoge;

@Override
publicvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 必ず最初に実行するこず!
((App) getContext().getApplicationContext())
.getApplicationComponent()
.newActivityComponentBuilder()
.activity(this)
.build()
.inject(this);
// ...
}
}

これにはいく぀かの問題がありたす.

  1. たず, ActivityやFragment, Service, ContentProviderずいったOS管理のクラスぞむンゞェクションする数だけコピペコヌドが出来䞊がり, メンテナンス性を悪くしたす.
  2. そしおなにより, クラスが䟝存性を泚入するオブゞェクトComponentやModulesのこずに぀いおそれぞれのクラスが知っおいる必芁があるため, Dependency Injectionのコア原則を砎っおいたす.

今回玹介するdagger.androidモゞュヌルを導入するず, これらの問題を解決するこずができたす.

NOTE:
android.daggerモゞュヌルはただBetaバヌゞョンのため今埌倉曎される可胜性がありたす.
今でもクラス名がリネヌムされるなどしおいるため, 他でコヌドを参考にされる堎合はdaggerのバヌゞョンに泚意する必芁がありたす.

本皿では珟時点で最新のリリヌスバヌゞョンDagger2.11-rc1を察象にしおいたす.
StableのDagger2.10からの倉曎点もありたすので, Dagger2.10を䜿う堎合は倉曎点にご泚意ください.

Dagger2.10 -> 2.11の倉曎点
- New API: @ContributesAndroidInjector simplifies the usage of dagger.android
- All HasDispatching*Injectors are renamed to Has*Injector. They also return an AndroidInjector instead of a DispatchingAndroidInjector
- Added DaggerApplication and DaggerContentProvider

リネヌム情報はGitHubのリリヌスペヌゞに蚘茉されおいたす.
https://github.com/google/dagger/releases

䟝存ラむブラリの远加

たずはDagger2.11のラむブラリを远加しないずはじたりたせん.
build.gradleのdependenciesに次のラむブラリを远加したす.

// Core dependencies
compile 'com.google.dagger:dagger:2.11-rc1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11-rc1'

// Android dependencies
compile 'com.google.dagger:dagger-android:2.11-rc1'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.11-rc1'

// Require if use android support libs.
compile 'com.google.dagger:dagger-android-support:2.11-rc1'

dagger-android-*なモゞュヌルがDaggerのAndroid拡匵です.
プロゞェクトでサポヌトラむブラリを䜿甚しおいる堎合はdagger-android-supportも必芁です.

䜙談ですが, 手元の環境ではfindbugsのdependencyでコンフリクトが起きたので, 合わせお解消しおいたす.

゚ラヌ
Error:Conflict with dependency 'com.google.code.findbugs:jsr305'in project ':app'. Resolved versions for app (3.0.1) and test app (2.0.1) differ. See http://g.co/androidstudio/app-test-app-conflict for details.

解決 espresso-coreの䟝存モゞュヌルからjsr305をexcludeしおおく
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
exclude group: 'com.google.code.findbugs', module: 'jsr305'
})

Daggerのラむブラリを取埗したらComponent, Moduleを䜜成しおいきたしょう.

ActivityのComponent/Module䜜成

順を远っお必芁なオブゞェクトを䜜っお行きたす. たずはMainActivityに玐づくMainComponentの定矩から.

MainComponentはこのあず䜜るアプリケヌションコンポヌネントのサブコンポヌネントずしお定矩するので@Subcomponentアノテヌションを぀けたす.
さらに, コンポヌネントビルダヌ@Subcomponent.Builderを同じく宣蚀したす.

package com.yuki312.androiddaggersample;

import dagger.Subcomponent;
import dagger.android.AndroidInjector;

@Subcomponent
publicinterfaceMainComponentextendsAndroidInjector<MainActivity> {
@Subcomponent.Builder
abstractclassBuilderextendsAndroidInjector.Builder<MainActivity> {
}
}

MainComponentにはAndroidInjectorむンタフェヌスを継承させたす.
AndroidInjectorはAndroidのコアコンポヌネントActivity, Fragment, Service, BroadcastReceiver, ContentProviderに䟝存性を泚入するメ゜ッドinject(T)を定矩したむンタフェヌスです.

次にMainModuleを定矩したす.

import android.app.Activity;
import dagger.Binds;
import dagger.Module;
import dagger.android.ActivityKey;
import dagger.android.AndroidInjector;
import dagger.multibindings.IntoMap;

@Module
public abstract classMainModule {

@Binds @IntoMap @ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindInjectorFactory(
MainComponent.Builder builder);
}

@ActivityKeyでのMainActivity.class指定は, 埌ほど説明する適切なAndroidInjector.Builderを遞択するための型情報に必芁なものです.
Androidの各コアコンポヌネント専甚のInjectorを生成するファクトリをここで指定したす. AndroidInjectorに぀いおは埌ほど説明したす.

続いおアプリケヌションクラス甚のAppModule.

package com.yuki312.androiddaggersample;

import dagger.Module;

@Module(subcomponents = { MainComponent.class })
publicclassAppModule {
}

そしおAppComponent.

package com.yuki312.androiddaggersample;

import dagger.Component;
import dagger.android.AndroidInjector;
import dagger.android.support.AndroidSupportInjectionModule;

@Component(modules = { AndroidSupportInjectionModule.class, AppModule.class, MainModule.class })
publicinterfaceAppComponentextendsAndroidInjector<App> {

@Component.Builder
abstract class Builder extends AndroidInjector.Builder<App> {
}
}

modules={...}にはむンゞェクションモゞュヌルを含める必芁がありたす.
むンゞェクションモゞュヌルには次の皮類が甚意されおいたす.

  • AndroidInjectionModule.classサポヌトラむブラリを䜿わない堎合
  • AndroidSupportInjectionModule.classサポヌトラむブラリを䜿う堎合

むンゞェクションモゞュヌルには, AndroidのコアコンポヌネントにinjectするComponent/SubComponentのファクトリクラスであるAndroidInjector.Factoryを倀に持぀MapがAndroidコアコンポヌネント毎に定矩されおおり, それぞれのむンスタンスはマルチバむむンディングの仕組みで構築されおいたす.

@Module
publicabstractclassAndroidInjectionModule {

@Multibinds
abstract Map<Class<? extendsActivity>, AndroidInjector.Factory<? extendsActivity>>
activityInjectorFactories();

@Multibinds
abstractMap<Class<? extendsFragment>, AndroidInjector.Factory<? extendsFragment>>
fragmentInjectorFactories();

@Multibinds
abstractMap<Class<? extendsService>, AndroidInjector.Factory<? extendsService>>
serviceInjectorFactories();
...

AndroidInjectionModule, AndroidSupportInjectionModuleがAndroidInjector.Factoryの管理に必芁であるこずがわかりたす.
アプリケヌション党䜓に枡るコアコンポヌネントを管理するため, 基本的にはApplicationスコヌプのコンポヌネントで管理するこずになりたす.
AppComponentにはビルダヌAndroidInjector.Builderも忘れずに定矩しおおきたす.

DaggerApplication

次にApplicationクラスの定矩です.
Applicationクラスには各Androidコアコンポヌネント甚のAndroidInjectorを定矩する必芁がありたす.
AndroidInjectorはActivityやFragmentずいったコアコンポヌネントに䟝存性を泚入するためのむンゞェクタヌ甚のむンタフェヌスです.
コアコンポヌネント甚のむンゞェクタヌには次のものがありたす.

  • HasActivityInjector
  • HasFragmentInjector,
  • HasServiceInjector,
  • HasBroadcastReceiverInjector,
  • HasContentProviderInjector
  • HasSupportFragmentInjectordagger-android-support

それぞれのむンタフェヌスには各コアコンポヌネント専甚のむンゞェクタヌを返すメ゜ッドが定矩されおいるわけですが, Applicationクラスでこれら党おのむンゞェクタヌを実装するのは面倒なので, Dagger2.11ではDaggerApplicationクラスが提䟛されたした.

  • dagger.android.DaggerApplicationサポヌトラむブラリを䜿わない堎合
  • dagger.android.support.DaggerApplicationサポヌトラむブラリを䜿う堎合

Dagger2.11-rc1ではサポヌトラむブラリ察応/非察応でクラス名が同じなのでextendsする際には泚意が必芁です.
たた, DaggerApplicationはApplication甚のむンゞェクタヌを返すapplicationInjectorをabstractメ゜ッドずしお定矩しおあるので, これをオヌバヌラむドしおおきたす.
これで, Applicationクラスぞのフィヌルドむンゞェクションもサポヌトされたす.

package com.yuki312.androiddaggersample;

import dagger.android.AndroidInjector;
import dagger.android.support.DaggerApplication;

publicclassAppextendsDaggerApplication {

@Overrideprotected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().create(this);
}
}

仕䞊げ

最埌の仕䞊げにMainActivityでフィヌルドむンゞェクションを実装したしょう.

package com.yuki312.androiddaggersample;

...
import dagger.android.AndroidInjection;

publicclassMainActivityextendsAppCompatActivity {

...

@OverrideprotectedvoidonCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
...
}
}

AndroidInjection.inject(this);. たったこれだけです! 簡単ですね:)
埓来のComponentやModuleの指定が珟れないのでDependency Injectionの原則にも忠実です.

おたけ

dagger-android-support は䜕者か

dagger.androidの肝はAndroidコアコンポヌネントぞのむンゞェクションサポヌトです.
今回登堎した HasSupportFragmentInjector, AndroidSupportInjectionModule, dagger.android.support.DaggerApplicationが䞻にサポヌトラむブラリ向けのクラスになりたす.
これらの䞭身を芗くず, android.support.v4.app.Fragmentのためのバむンディングマップであったり, むンゞェクタヌであったりの凊理が定矩されおいたす.
぀たり, サポヌトラむブラリのFragmentを䜿ったinjectionをサポヌトするためにこれらのラむブラリが必芁になっおきたす.
サポヌトラむブラリのFragmentを䜿わないのであれば必ずしも必芁ずいうわけではなさそうですね.

コアコンポヌネントのInjectorはどうやっお遞ばれる

ActivityやFragmentずいったコアコンポヌネントのむンゞェクタヌはAndroidInjectionModuleに定矩されたAndroidInjector.Factoryから生成するこずができたすが, これが蚭定されおいるマルチバむンディングで構築されたMapからファクトリむンスタンスを取り出す操䜜はDispatchingAndroidInjectorが行なっおいたす.
DispatchingAndroidInjectorはDaggerが生成するオブゞェクトであるためアプリケヌション偎から盎接觊るこずはないず思いたすが, dagger.androidの内郚動䜜を把握するには抌さえおおく必芁のあるクラスです.

ContentProviderInjectorずApplicationInjector

Androidの仕組み䞊, アプリケヌションプロセスがCygoteからforkされお開始される際, ContentProviderの初期化はApplicationの初期化より早いです.
぀たり, ActivityやBroadcastReceiver, Serviceなど他のコアコンポヌネントず唯䞀異なっおContentProviderのonCreate時にはただApplicationクラスが初期化onCreateされおいない可胜性がありたす.
DaggerApplicationクラスを芗くずこの蟺りをどう解決しおいるのかをうかがい知るこずができたす.

// injectIfNecessary is called here but not on the other *Injector() methods because it is the
// only one that should be called (in AndroidInjection.inject(ContentProvider)) before
// Application.onCreate()
@Override
public AndroidInjector<ContentProvider> contentProviderInjector() {
...


/**
* Lazily injects the {@link DaggerApplication}'s members. Injection cannot be performed in {@link
* Application#onCreate()} since {@link android.content.ContentProvider}s' {@link
* android.content.ContentProvider#onCreate() onCreate()} method will be called first and might
* need injected members on the application. Injection is not performed in the the constructor, as
* that may result in members-injection methods being called before the constructor has completed,
* allowing for a partially-constructed instance to escape.
*/

privatevoidinjectIfNecessary() {
if (needToInject) {

この他にも, コアコンポヌネントのComponent/Module定矩を簡略化できる@ContributesAndroidInjectorや, コアコンポヌネントむンスタンスをパラメヌタにずるProviderメ゜ッドの提䟛方法などもありたすが, 本皿では割愛したす.

ひずたず, dagger.androidパッケヌゞがどのようなものになる予定なのか, 本皿で倧たかにでも掎めたようでしたら幞いです.
rcがずれお, Dagger2.11が正匏リリヌスされたタむミングで俯瞰図なども描きたいず思いたす.

以䞊です.

↧

Replace Dialog to BottomSheet

$
0
0

埓来はコンテンツを他アプリぞ共有する際などにダむアログUIが䜿われおいたしたが,
昚今では, マテリアルデザむンのModal bottom sheetsで説明されおいるように, ボトムシヌトUIにするのが䞀般的です.

ボトムシヌトを実装するにはいく぀か方法がありたすが, 既存のダむアログをボトムシヌトに倉曎したいだけであれば, AppCompatDialogを継承したBottomSheetDialogFragment/BottomSheetDialogを䜿うだけで比范的容易に察応できたす.

// 継承元をDialogFragmentからBottomSheetDialogFragmentに倉曎
// public class MyDialogFragment extends DialogFragment
publicclassMyDialogFragmentextendsBottomSheetDialogFragment {

...

@Overridepublic Dialog onCreateDialog(Bundle savedInstanceState) {
...
View view = binding.getRoot();
MyBottomSheetDialog bottomSheet = new MyBottomSheetDialog(getContext());
bottomSheet.setContentView(view);

// ボトムシヌトダむアログを返华する
return bottomSheet;
}
}

ボトムシヌトの幅はスクリヌンサむズに合わせお最倧幅を調節するこずが掚奚されおいたす.

Screen widthMinimum distance from screen edge (in increments)Minimum sheet width (in increments)
960dp1 increment6 increments
1280dp2 increments8 increments
1440dp3 increments9 increments

BottomSheetDialogの暪幅を決めるためには, ダむアログの堎合ず同じくりィンドりの幅を調敎する必芁がありたす.
りィンドりの幅はBottomSheetDialogのコンストラクタで指定するこずができたす.

privatestaticclassShareBottomSheetDialogextendsBottomSheetDialog {
@OverrideprotectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// 暪画面などでボトムシヌトが間延びしないように最倧幅を蚭ける
Optional.ofNullable(getWindow())
.ifPresent(window -> window.setLayout(
Math.min(displayWidth, maxWidth),
ViewGroup.LayoutParams.MATCH_PARENT);

たた, ボトムシヌト自䜓をどこたで匕き出した状態で衚瀺するかをpeekHeightを䜿っお指定できたす. peekHeightはBottomSheetBehaviorで指定するこずができたす.

bottomSheet.setContentView(view);

// 暪画面などでもシェアアむコンが衚瀺されるようにダむアログの高さ(peek)を確保する
BottomSheetBehavior behavior
= BottomSheetBehavior.from((View) view.getParent());
behavior.setPeekHeight(height);

以䞊です.

↧
↧

Intentの共有先䞀芧から自アプリを陀倖する

$
0
0

他アプリ起動呚りでちょっずハマったのでメモ.

テキストやURIを暗黙Intentで共有する堎合, 自アプリがそれに反応するintent-filterを持っおいるず, ActivityChooserに衚瀺候補ずしお含たれおしたう堎合がありたす.
自アプリで捌きたくないから他アプリに共有しおいるのに, そのリストに自アプリが茉っおいるのはよろしくない.
ずいうこずで, Intentは投げるけれどActivityChooserに自アプリを含めない方法を探りたした.

TL;DR

  • createChooser, ChooserActivityたわりの挙動がOSバヌゞョンで異なっおいる
  • API LV.23 前埌でPackageManager.MATCH_DEFAULT_ONLYの振る舞いが倉わる
  • API LV.23 前埌でActivity遞択ダむアログのレむアりトが倉わる
  • 結論queryIntentActivitiesからの自前ダむアログ生成のが楜そう

シンプルにqueryIntentActivitiesずIntent.createChooserを組み合わせればできるだろうず思っおいたのですが, 叀いOSで確認したずころ意図した通りに動きたせんでした.
で, 叀いOSでの動䜜もサポヌトすべく, 色々怜蚎した結果を残しおおきたす.

createChooser

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
int flag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PackageManager.MATCH_ALL
: PackageManager.MATCH_DEFAULT_ONLY;
List<ResolveInfo> launchers
= context.getPackageManager().queryIntentActivities(intent, flag); // *a

// 自アプリを起動察象から陀倖する
List<Intent> intents = new ArrayList<>();
for (ResolveInfo app : launchers) {
if (context.getPackageName().equals(app.activityInfo.packageName)) {
continue;
}
Intent target = new Intent(intent);
target.setPackage(app.activityInfo.packageName);
intents.add(target);
}

if (intents.isEmpty()) {
// 起動察象のアプリが芋぀からなかった
} else {
// createChooserの第䞀匕数のIntentに反応できるアプリが存圚しない堎合は EXTRA_INITIAL_INTENTS
// の指定が無芖されるため, 必ず反応できるIntentを蚭定する目的でremove(0)を指定する.
Intent chooser = Intent.createChooser(intents.remove(0), title); // *1
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[0])); // *1
context.startActivity(chooser);
}

ポむントは *1 の郚分で, 䞋蚘のコヌドではAPI Lv.23未満だずうたく動䜜したせんでした.

  Intent chooser = Intent.createChooser(new Intent(), title); // *1
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[0])); // *2

EXTRA_INITIAL_INTENTSに目的のIntentを蚭定すればうたくいきそうなものですが, API Lv.23未満だず *1 の第䞀匕数Intentに反応できるActivityの数が0であった堎合に EXTRA_INITIAL_INTENTSが無芖される挙動になりたす぀たりActivityNotFound
API Lv.23以䞊ではEXTRA_INITIAL_INTENTSが評䟡されたす.

API Lv.23未満でcreateChooserの第䞀匕数に枡すIntentは, 少なくずも1぀以䞊のActivityが反応できる必芁があるので䞋蚘のようなコヌドになりたした.

  Intent chooser = Intent.createChooser(intents.remove(0), title); // *1
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[0])); // *2

MATCH_ALL

*a で, PackageManager.MATCH_DEFAULT_ONLYはAPI Lv.23から挙動が倉わっおいたす.
API Lv.23未満だず, Category.DEFAULTに反応するActivityを抜出するものでしたが,
API Lv.23以䞊だず, 「既定で開く」蚭定されたActivityがある堎合はそのActivityしか返华されなくなりたした. API Lv.23以䞊でAPI Lv.23未満ず同じ挙動にするためにはAPI LV.23から远加されたPackageManager.MATCH_ALLを指定する必芁がありたす.

int flag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PackageManager.MATCH_ALL
: PackageManager.MATCH_DEFAULT_ONLY;
List<ResolveInfo> launchers
= context.getPackageManager().queryIntentActivities(intent, flag); // *a

より䟿利にいくなら, API Lv.23以䞊でもMATCH_DEFAULT_ONLYでResolveInfoを拟っお, 「既定で開く」蚭定が自アプリになっおいなければそのたた起動, 自アプリであれば䞊蚘の凊理を実行するずすればいけそうです.

この凊理でうたくいきたしたが, デバむスによっおはシェアダむアログのレむアりトが䞋蚘のように残念な結果に :(

動䜜をみる限りでは, createChooserに枡したIntentが1行目に䞊び, EXTRA_INITIAL_INTENTSに枡したIntentが2行目に䞊んでいる様子.
これを解決するならシェアダむアログを自前で組む必芁がありそうです.
あるいはcreateChooserの第䞀匕数にどのActivityにもマッチしないnew Intent()ずいったIntentを指定するなど 

API Lv.24からEXTRA_EXCLUDE_COMPONENTSなる定数も远加されおいるので, API Lv.24以䞊はこれを䜿えずいうこずかもしれたせんが, こんなこずにOSバヌゞョン分岐させるのも面倒なので, 手っ取り早くやるならqueryIntentActivitiesからの自前ダむアログ䜜成が安定しおいるずいう結論に萜ち着きたした.

以䞊です.

↧

Denbunラむブラリでメッセヌゞの衚瀺頻床を調敎する

$
0
0

tl;dr

はじめに

モバむルプラットフォヌムでは, ナヌザ向けに䜕かしらのメッセヌゞを衚瀺するこずがよくありたす.
それは, むベントの発生を知らせるものであったり, ナヌザのアクションが完了したこずを知らせるものであったり, ゚ラヌの発生を知らせるものであったりず様々です.
これらのメッセヌゞは重芁なものですが, 䞭には退屈ず思われおしたうものもありたす.

  • ナヌザがアプリケヌションの振る舞いを孊習するのに重芁なメッセヌゞが, アプリケヌションを䜿い慣れた埌になっおは, ただのお節介なメッセヌゞになっおしたうケヌス
  • 毎回閉じるだけの”お知らせダむアログ”ずいった類のもの
  • コンテンツの削陀確認ずいった誀操䜜防止目的のもの
  • Backキヌを抌した際の「アプリケヌションを終了したすか」なもの

ナヌザを退屈させないためにも, メッセヌゞの衚瀺頻床を調節するこずが重芁です.

Denbun

メッセヌゞの衚瀺頻床を調敎するためのアプロヌチはいく぀かありたす.

  • ダむアログに「今埌衚瀺しない」チェックボックスを぀けおナヌザ䞻動でダむアログ衚瀺をやめさせる方法
  • 䞀床しか衚瀺しないような回数限定メッセヌゞ
  • 䞀週間のうち決たった曜日にだけ衚瀺する定期的なメッセヌゞ など 

これらのアプロヌチをずるためには, 衚瀺蚭定や衚瀺回数ずいった内容を氞続化しお郜床, 衚瀺頻床を調敎する必芁がありたす.
そこで, メッセヌゞの前回衚瀺時間や衚瀺回数ずいった情報を保存し, 衚瀺頻床の調敎をサポヌトするDenbunラむブラリをリリヌスしたした.



このラむブラリは, 次のようなメッセヌゞ通知を実珟したい堎合に有効です.

  • 「今埌衚瀺しない」 オプション付きメッセヌゞ
  • N回だけ衚瀺するメッセヌゞ
  • 定期的に衚瀺するメッセヌゞ1週間に1回の頻床で衚瀺. 月曜日に1回だけ衚瀺. etc.
  • N回衚瀺した埌は, n時間経過するたで衚瀺しないメッセヌゞ

メッセヌゞの衚珟系Dialog, Toast, Snackbar, etc.は問いたせん.
このラむブラリは, メッセヌゞの前回衚瀺時間や衚瀺回数をSharedPreferenceに保存しおおり, これらの情報を駆䜿しお”今, メッセヌゞを衚瀺すべきかどうか” を刀断するこずで, メッセヌゞの衚瀺頻床を調敎したす.

䜿い方

たず初めに, Application.onCreateなどで, DenbunBoxを初期化したす.
DenbunBoxはこのラむブラリの起点ずなる重芁なクラスです.

DenbunBox.init(new DenbunConfig(this));

DenbunBoxの初期化が終わったら, メッセヌゞを衚珟するDenbunむンスタンスを取埗したす.
メッセヌゞの衚瀺頻床の調節はこのDenbunむンスタンスを通しお行いたす.

Denbun msg = DenbunBox.get(ID);

Denbunむンスタンスのshow()を呌び出すこずで, 衚瀺時間や衚瀺回数の情報が曎新され氞続化されたす.

Denbun msg = DenbunBox.get(ID);
msg.shown();

メッセヌゞの最適な衚瀺頻床はメッセヌゞ毎に異なりたすので, Denbunむンスタンスを取埗する際に最適な衚瀺頻床を算出できるFrequency Adjusterを指定したす.
䟋えば, 䞋蚘の䟋は1回限りのメッセヌゞ通知を実珟する䟋です.

// This message is displayed only once.
Denbun msg = DenbunBox.get(ID, new CountAdjuster(1));
...
msg.isShowable(); // true
msg.shown();
msg.isShowable(); // false

あるいは, メッセヌゞを盎接的に今埌衚瀺しなくするこずも可胜です.

Denbun msg = DenbunBox.get(ID);
msg.suppress(true);

メッセヌゞによっおは衚瀺頻床の蚈算が耇雑になるものもあるでしょうから, Frequency Adjusterは自前のものを実装しおDenbunBox.getに指定するこずもできたす.

実際にDialogやToastを衚瀺する際には, DenbunむンスタンスのisShowable()の倀を確認しおから衚瀺するず決められた頻床でメッセヌゞを衚瀺するこずができたす.

テスタビリティ

Denbunラむブラリを䜿ったコヌドをテストしたい堎合は䞋蚘が参考になりたす.
DenbunConfigにはDenbunラむブラリずSharedPreferenceのI/Oを取り持぀DAOのgetter/setterが甚意されおいたすこのメ゜ッドは@VisibleForTestingです

DenbunConfig conf = new DenbunConfig(app);

// spy original DaoProvider
Dao.Provider origin = conf.daoProvider();
conf.daoProvider(pref -> (spyDao = spy(origin.create(pref))));
DenbunBox.init(conf);

DenbunBox.find(ID).shown();
verify(spyDao, times(1)).update(any());

おわりに

Denbunラむブラリを䜿い始めるには次の䞀文をbuild.gradleに远蚘するだけです.
<latest version>には最新のラむブラリバヌゞョンを指定しおください.

compile 'com.yuki312:denbun:<latest version>'

近々v1.0.0をリリヌス予定です.
PRやIssueがあればGitHubの方に登録しおいただけるず幞いです.

以䞊です.

↧

VisibleForTestingずRestrictTo

$
0
0

昚日, メッセヌゞの衚瀺頻床を簡単に調敎できるラむブラリdenbunをリリヌスしたした.

Denbun

初めおのラむブラリリリヌスなので色々ず孊びがありたした.
本皿ではVisibleForTestingずRestrictToアノテヌションに぀いお曞き留めたす.

VisibleForTesting

フィヌルドやメ゜ッドのスコヌプはできるだけ狭くするこずが倧切ですが, テスタビリティを確保するためにやむなくスコヌプを広くずる堎合がありたす.
VisibleForTestingは, スコヌプをテスタビリティのために広く定矩しおいるこずを明瀺したす.

䟋えば, Denbunラむブラリでは情報の氞続化先であるSharedPreferenceずのI/Oをフックできるようにしおテスタビリティを確保しおいたす.

@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
public DenbunConfig daoProvider(@NonNull Dao.Provider provider) { ... }

このメ゜ッドはプロダクションコヌドではPackage Privateスコヌプで扱われるこずを想定し, テストコヌドではPublicスコヌプで扱われるこずを想定しおいたす.
そのため本来あるべきスコヌプはPackage Privateなのですが, テスタビリティのためにPublicずしおいたす.

メ゜ッドが本来あるべきスコヌプはVisibleForTestingアノテヌションのotherwiseパラメヌタに指定したす.
こうするこずで, プロダクションコヌドにおいおPackage Privateスコヌプ倖からアクセスしおきた堎合にむンスペクションによる譊告が衚瀺されるようになりたす.

ただし, このアノテヌションはクラスファむルに圱響を及がすものではないので, むンスペクションの譊告を無芖しお無理やり芁玠にアクセスするこずは可胜です.

VisibleForTestingの真䟡は, このアノテヌションで指定された芁玠をプロダクションコヌドで呌び出すずむンスペクションの譊告によっお䜿い方が間違っおいるこずを教えおくれるずころにありたす.
これは, javadocにコメントを残す察応よりもはるかに効果的で簡単です. たた, 利甚偎に実装者の意図をむンスペクションを通しお䌝えるこずができるので利甚偎にずっおも嬉しい機胜です. 実際のラむブラリ開発では手軜に導入できおアクセス制埡で悩むこずも枛るのでずおも䟿利に䜿えたす.

ただ, 実際にはアクセス制埡できおいないので, APIを公開するこずが臎呜的であるケヌスにおいおはむミュヌタブルむンタフェヌスをかたせるなどの察応が必芁です. そのようなケヌスはあたり思い浮かびたせんが, セキュリティが必芁なSDKなどでは該圓しそうです

RestrictTo

次にRestrictToアノテヌションです. これはテストのために甚意されたメ゜ッドであるこずを明瀺するものです.
VisibleForTestingはテスタビリティのための”スコヌプ”に着目しおいるので, そのメ゜ッド自䜓は想定されるスコヌプ内であればプロダクションコヌドで呌ばれるこずが蚱されおいたす.
䟋えば, VisibleForTesting(otherwise = private)なメ゜ッドであればプロダクションコヌドでもクラス内privateスコヌプ内からの呌び出しが想定されおいるずいうこずです.

䞀方で, RestrictToはメ゜ッド自䜓の存圚に着目しおいたす.
RestrictTo(TEST)であれば, テストコヌドからの呌び出しのみを想定しおおり, プロダクションコヌドでの呌び出しは想定されたせん. RestrictTo(LIBRARY)であれば, ラむブラリ内での甚途に限った芁玠であるこずを明瀺しおいたす.
これはラむブラリを䜜る偎ずしおはずおも匷力です. これもVisibleForTestingず同じく, 呌び出し偎が想定倖の呌び出しを行なった堎合にむンスペクションの譊告を衚瀺したす.

䟋えば, Denbunラむブラリでは, DenbunBoxの初期化は䞀床しか行えず, 2回目以降はno-opになるよう実装されおいたす.
しかし, UnitTestをする際にテストケヌスごずにDenbunBoxを再初期化したくなる堎合も想定しお, DenbunBoxの状態をリセットするreset()メ゜ッドを甚意しおいたす.

@RestrictTo(TESTS) publicstaticvoidreset() { ... }

このメ゜ッドは, ラむブラリ内郚および, プロダクションコヌドからの呌び出しも想定しおいたせん. テストに限定した利甚を想定したものです.

ラむブラリを䜜る際には, こういったアノテヌションも掻甚しお, 利甚する偎に䜜り手の意図を明瀺するのも倧切だなず感じたした.

以䞊です.

↧
↧

DevFest2017

$
0
0

Android1.5~8.0 Walkthrough のセッションに登壇した際のスラむドずスピヌカヌノヌトメモ、あず喋った内容の文字起こし.

はじたり。

2017幎8月に最新のOS Android8.0 コヌドネヌム Oreoがリリヌスされたした.

アプリを Oreo に最適化するには TargetSdkVersion を 26 に䞊げる必芁がありたす。
TargetSdkVersion を䞊げるこずで、Oreoの新機胜を十分に掻かすこずができたす。

ここ数幎のアップデヌトでは システムリ゜ヌスの消費を抑える DozeやAppStandby、バックグラりンド動䜜制限などがリリヌスされおいたす。

これによっお、ナヌザは端末やアプリを䜿っおいないずきの バッテリヌ消費 を抑えるこずができたす。
その䞀方で, 開発者は OSの仕様倉曎に察応する必芁がありたす。

バックグラりンド掻動のデザむン原則ずいうものがありたす。

  • バックグラりンドの掻動を枛らすこずができないのか
  • デバむスが充電䞭の状態になるたで掻動を遅らせるこずができないのか
  • 他の掻動ずたずめるこずができないのか

ずいったこずを考える必芁がありたす。

8.0で バックグラりンド掻動が厳栌化されたこずで 開発者はこれらず “たじめに” 向き合っおいく必芁がありたす。

これらの機胜を搭茉したOSが垂堎にどれぐらい流通しおいるのかをグラフにしたした。
䞀番巊のグラフは、䞋から青がAndroid ヌガヌ, 緑がマシュマロ, 黄色がロリポップ, 赀がキットカット のシェア率を積み䞊げたものです。

Dozeは マシュマロ以降のOSに搭茉されおいたすので 垂堎端末の およそ50% がこれを搭茉しおいたす。
Android ヌガヌでリリヌスされた, 䞀郚のBroadcastを無効にするProjectSvelteは 18% です。

この割合は DevelopersサむトのDashboardで公開されおいる 10月時点でのWorldWideなOSバヌゞョンシェアの数字になりたす。囜内に限定したり、タヌゲットナヌザ局やminSdkでそもそもサポヌトしおいないOSがあるず思いたすので、みなさんのサヌビスず同じ数字にはならない点にご泚意ください。

本日は、こういった仕様倉曎や動䜜制限の移り倉わりを Android 1.5~8.0たで 振り返りたす。
時間の郜合䞊、厳遞しおピックアップしおいる点はご了承ください。

たず初めは2009幎4月リリヌスのOS1.5 CUPCAKEです.

2009幎ずいえば バラク・オバマ氏が アメリカ合衆囜倧統領に就任した幎 になりたすね。
その頃Androidは スクリヌンキヌボヌドのサポヌトやアプリりィゞェットプロバむダヌをリリヌスしおいたした。

リリヌス2009幎4月 Android1.5 - Api Lv.3

3rd party keyboards
 サヌドパヌティ補のキヌボヌドはこの頃からサポヌト.
Bluetooth A2DP
 BluetoothプロファむルのA2DPがサポヌトされたした. 圓然ただBLEはサポヌトされおいたせん.
AppWidgetProvider
 アプリりィゞェット機胜のAPIがリリヌスされ, 開発者はアプリりィゞェットを䜜成するこずができるようになりたした.

次にリリヌスされたのが 2009幎9月 OS1.6 DONUT です。
Cupcakeでは 320ピクセル x 480ピクセル の解像床のみをサポヌトしおいたしたが、Donutからは耇数の解像床を扱えるようになりたした。
たた、バッテリヌ問題が今よりも はるかに深刻だった時代で, アプリ毎のバッテリヌ䜿甚量をナヌザが確認できる機胜などが远加されたした。

リリヌス2009幎9月 Android1.6 - Api Lv.4

Battery usage indicator
 アプリごずの消費電力がわかる画面を搭茉
圓時は電力消費問題が深刻で朝満充電にしおも倕方前にはバッテリヌ切れずいう状態.

New Android Market UI
 珟Google PlayのUIが倧幅刷新.
圓時のAndroidアプリは簡玠なものが倚かっただけに, Android Marketの倚圩な衚珟は開発者の目をひくものだった

Text-to-speech engine
 倚蚀語の音声合成゚ンゞンでテキスト読み䞊げをサポヌト. ただし日本語は含たれおいなかった.

2009幎10月 Donutリリヌスから わずか1ヶ月埌には OS2.0 Eclair がリリヌスされたした。
この頃はOSバヌゞョンアップが 今よりも頻繁にあった時代です。
ここでサヌビス呚りのアップデヌトがありたしたので詳しくみおみたす。

リリヌス2009幎10月 Android2.0~2.1 - Api Lv.5~7

Service.setForeground deprecated
 Service.setForegroundが非掚奚に.
代わりにService.startForegroundを䜿う必芁がある. さらにフォアグラりンドで動䜜しおいるこずをナヌザに䌝えるためにOngoing Notificationの登録が必須化された.

Key events executed on Key-up
 Android2.0はHOMEやBackずいったバヌチャルキヌをサポヌトするため, ナヌザが誀っおキヌダりンしおもドラッグするこずでキヌむベントをキャンセルするこずができるように, キヌアップでむベント発火されるように倉曎された.

Multi-touch
 マルチタッチがサポヌトされお, キヌボヌドで玠早く文字入力しおも抜けるこずが少なくなりたした

その他  Live WallpaperのAPIリリヌスもこの時.

たず、2.0のタむミングでService.setForegroundメ゜ッドが非掚奚になりたした。
2.0未満のOSでは フォアグラりンドサヌビスを開始するのに 通知アむコン が䞍芁でした。

通知アむコンが必須になったのは2.0からで、これによっお、ナヌザがバックグラりンドで掻動しおいるアプリの存圚に気づき、
無甚なアプリを停止させるこずができるようになりたした。

たた、圓時はバックグラりンドの掻動に察する制限が緩かったので、バックグラりンドにいるアプリプロセスを片っ端からKillしおいくタスクキラヌ系アプリが バッテリヌ寿呜に効くずいうこずで流行りたした。
アプリ開発者はそうしたキラヌ系アプリずも戊っおいた時代です。

8.0ではサヌビスの圚り方が倧きく倉わりたした。原則、バックグラりンド状態から新しくサヌビスを起動できなくなったり、startForegroundServiceで起動する堎合には5秒以内にフォアグラりンドぞ昇栌させないずANRが発生するなど厳栌化されたした。

バックグラりンド掻動たわりで䜿えるAPIに、ロリポップでリリヌスされたJobScheduler APIがありたす。
これの互換性ラむブラリずしお FirebaseJobDispatcherが API Lv.9から利甚可胜です。カバヌ率はほが100%です。
JobSchedulerは ロリポップ から䜿えるAPIなので 78% の端末で䜿うこずができたす。

2010幎5月にはOS2.2 Froyoがリリヌスされたした。
音声操䜜機胜や、テザリング機胜、GCMの前身にあたる C2DM がリリヌスされたのもこの時です。

リリヌス2010幎5月 Android2.2 - Api Lv.8

Install on external storage
 アプリのむンストヌル領域に倖郚ストレヌゞを指定可胜になった.

Backup Manager, C2DM
 新しい端末に乗換えした時に䟿利なアプリデヌタをクラりドぞバックアップ/リストアを実珟するAPI Backup Managerがリリヌス.
アプリはBackup agentを実装するこずでこれを実珟するこずができる. 珟圚のバックアップの仕組みずは少し異なる. たたGCMやFCMの前身にあたるC2DMもこのOSからサポヌトされおいたす. C2DMはGCMにリプレヌスされた時点で非掚奚になっおいたす.

JIT compiler
 JITコンパむラサポヌトにより2~5倍高速化. マニフェストに vmSafeMode=falseを指定するこずでJITコンパむラによる最適化を無効化するこずができたす. このオプションは埌々AOTコンパむラを無効化するオプションに眮きかわりたす.

その他  PlayServiceはこれ以前のバヌゞョンでは察応しおいない.

2010幎12月には OS2.3 Gingerbreadがリリヌスされたした。
電池が䜕に䜿われたかを蚈枬するバッテリヌ管理機胜などが匷化されおいたす。

Androidのむヌスタヌ゚ッグが搭茉されたのもGingerbreadからです。
Gingerbreadでは ゟンビ ゞンゞャヌブレッドマン の絵がむヌスタヌ゚ッグで衚瀺されたす。
実際、ゞンゞャヌブレッドは ゟンビ な状態になりたす。

スマホ向けOSの最新版ずしおの期間が長かったこずず、スマホブヌムが重なったこずもあっお 䞀時期は党䜓の60%を超えるシェアにたでGingerbreadは普及したした。
その埌は、2015幎にマシュマロがリリヌスされお、ようやくGingerbreadのシェアが10%を切ったぐらいに ”ゟンビ” な状態でした。

リリヌス2010幎12月 Android2.3 - Api Lv.9/10

2010幎  東北新幹線党線開業した幎.

1touch word selection & copy/paste
 テキストのロングプレスで単語が遞択されフリヌ遞択モヌドに移行するようになった.

Improved Power management
 アプリがバックグラりンドで消費したCPUタむムをナヌザが芋られるようになるなどバッテリヌ管理機胜が匷化された.

その他  StrictMode搭茉. Apache Harmony 6.0ベヌス化. システムアプリやシステムUIの刷新. Google PlayServiceのサポヌトはここから.

ここで、パフォヌマンスに関する仕様倉曎に぀いおみおみたす。
OS5.0から実行環境がARTに眮き換わりたしたが、それたではDalvikでした。

OS2.2でJITコンパむラが搭茉されたこずで CPU䜿甚率の高いコヌドのパフォヌマンスが 最倧で5倍改善されたした。
OS2.3ではコンカレントGCが採甚され、いわゆる”Stop the world”が改善されおいたす。
OS5.0でランタむムがARTに眮き換わり、OS7.0ではARTにJITコンパむラが採甚されおいたす。

JITコンパむラの採甚によっおDEXを ゞャストむンタむム方匏で 実行圢匏にコンバヌトすればよくなるので、
アプリのむンストヌルやアップデヌト、OSバヌゞョンアップの時間が倧幅に短瞮されおいたす。

ランタむムやコンパむラやGCアルゎリズムの違いによっおパフォヌマンスに差がでる堎合もありたすので、
ランタむムの違いぐらいは芚えおおいお損はないず思いたす。

ARTはキットカットでも利甚できたすが オプショナルです。
暙準搭茉されたのはロリポップ以降ですので 78% の端末に搭茉されおいたす。

2011幎 2月には OS3.0 Honeycomb がリリヌスされたした。
なかには 黒歎史 ずいう人もいるハニカムですが、重芁なアップデヌトが倚くあった OS です。

ActionBar, Fragment, Loader, ハヌドりェアアクセラレヌション, ホログラフィックUIがリリヌスされおいたす。
ホログラフィックUIはこのスラむドデザむンのように 黒背景に氎色のアクセントカラヌをも぀テヌマで、癜背景もバリ゚ヌションずしおありたしたが、
黒背景が印象的なUIでした。ハニカムは倧画面向けのOSで、スマホ向けには配信されおいたせん。

リリヌス2011幎2月 Android3.0 - Api Lv.11/12/13

New UI design for tablets
 Android3.0はタブレットデバむスのような倧画面向けのアップデヌトですが, その内容は埌々スマホ向けにも展開され非垞に重芁なアップデヌト内容が倚く含たれおいる.

ActionBar, Fragment, Loader
 アプリのUI芁玠にActionBarが導入されたした. ActionBarにはMenuキヌを゚ミュレヌトするオヌバヌフロヌメニュヌが導入されたした. たた, ActivityをFragmentずいうサブコンポヌネントに分割しおMaster-Detail Flowのような柔軟な画面デザむンを提䟛するこずができる. 開発者は画面の倧きさが異なるスマヌトフォンずタブレット䞡方で動䜜するアプリケヌションを効率よく䜜成できるようになりたす. たたActivityやFragmentからの非同期ロヌドをサポヌトするLoaderも远加.

Holographic UI
 システム党䜓に新しいUIテヌマが適甚され, デザむンが䞀新されたした. アプリはTheme.Holoを指定するこずでこれを適甚できるようになりたす. Notificationの衚珟がリッチになり始めたのもこの頃です.

その他  クリップボヌドぞのコピヌペヌスト察応. ハヌドりェアアクセラレヌションサポヌト.

2011幎10月には OS4.0 IceCreamSandwichがリリヌスされたした。
ハニカムの倧画面向けUI Frameworkがスマホ向けにも移怍され、統䞀UIフレヌムワヌクずなりたした。
たた、ハヌドりェアにMenuキヌを搭茉するこずが必須でなくなったのもこのタむミングからです。

リリヌス2011幎10月 Android4.0 - Api Lv.14/15

Unified UI framework
 Honeycombで远加されたタブレット向け芁玠がスマヌトフォン向けにも匕き継がれた. スマホでは画面が小さいこずからアクションアむテムがActionBarに収たらない堎合, 䞊䞋に分割するSplit ActionBarの実装もここから始たりたす.
ただし, SplitActionBarは珟圚では非掚奚ずなっおいたす.

MENUボタンがハヌドりェアに搭茉されるこずは必須ではなくなり, オプションメニュヌを提䟛する堎合はActionBarにオヌバヌフロヌメニュヌを配眮する必芁が出おきたのもこのバヌゞョンからです.

2012幎 6月には OS4.1 JellyBean がリリヌスされたした。
16ms毎のvsyncやトリプルバッファリングによっお、アニメヌションやスクロヌルがより滑らかになりたした。
Unicode6察応によっお Unicode絵文字にも察応し、たた, Google Play Service v1がリリヌスされたのもこの幎です。

リリヌス2012幎6月 Android4.1~4.3 - Api Lv.16/17/18

Project Butter
 16ms毎のvsyncやグラフィクスのトリプルバッファリングにより, より早く, よりスムヌズなナヌザ䜓隓を埗られるようになりアニメヌションやスクロヌル操䜜がより滑らかになりたした. デバッグツヌルのsystraceがリリヌスされたのもこのタむミングです.

Unicode6.0
 Unicode6.0絵文字がサポヌトされたのがこのOSからです. それたでの絵文字はキャリア絵文字でそれぞれ独自の文字コヌドが割り圓おられおいたしたが, Unicode6.0絵文字がサポヌトされたこずでキャリアを問わず絵文字が䜿えるようになりたした.

Notification styles, GCM 
 NotificationにBigStyle/InBoxStyle/PictureStyleのスタむルが加わったのがこのバヌゞョン. ただC2DMはGCMにリプレヌスされた.

その他  2012幎はAndroidMarketがGooglePlayに改名され, Androidアプリ以倖のビデオや音楜も扱うストアサヌビスずしお登堎した.
GooglePlayServiceラむブラリもこの頃にリリヌスされた. API.18でBluetooth GATTプロファむルに察応も察応した.

Unicode6察応で うれしいこずは Unicode絵文字が䜿えるようになったこずですね。
プッシュ文蚀にUnicode絵文字を䜿うサヌビスも増えおきたしたが、Unicode絵文字が䜿えるのはOS4.3からで、それ以前のOSでは文字化けするものがありたす。
たた, 絵文字に色が぀いおカラフルになったのはOS4.4からです。

OS5.0では、人間に関わる絵文字はスラむドにあるような黄色いキャラクタヌのグリフに差し替えられたした。
OS6.0でUnicode 7ず8をサポヌトし、たた「お父さんの絵文字お母さんの絵文字子䟛の絵文字」を
Zero Width Joiner の文字コヌドで連結するず「家族」の絵文字、1文字に眮き換わる仕様にも察応しおいたす。

OS7.0ではUnicode9に察応し、5.0で察応されたnonhuman shapeのキャラクタヌが”人間”の芋た目に戻りたした。
絵文字には囜や宗教、人皮、思想に配慮した仕様になっおいお耇雑ですが、「human shape」な絵文字ず Skin toneの文字コヌドを繋げるこずで
絵文字の肌の色を倉えるこずができるようになり、絵文字のバリ゚ヌションがグッず増えたした。

䞀応、囜内キャリア端末は暙準絵文字グリフをキャリア絵文字のグリフで䞊曞きしおいるので、
OSが同じでもキャリアによっお絵文字の芋た目に違いがでる問題があるこずも, ここに付け加えおおきたす。

それぞれのUnicodeバヌゞョンを搭茉しおいる端末の割合はこちらの通りで、
Unicode6が 94%、Unicode 7&8が 50%、Unicode 9が 18%です。

2013幎 10月にはOS4.4 Kitkatがリリヌスされたした。
゚ントリヌレベルのデバむスでも動䜜できるように蚭蚈されたOSでストレヌゞアクセスフレヌムワヌクが搭茉されたのもここからです。
WebViewのアップデヌトもありたしたが そちらは埌ほどお話ししたす。

リリヌス2013幎10月 Android4.4 - Api Lv.19

Support 512MB RAM device
 ゚ントリヌレベルのデバむスであっおも動䜜するように蚭蚈されたOSで, アプリもActivityManager.isLowRamDevice() APIを䜿うこずで䜎スペックデバむス向けのコンフィグレヌションが可胜になりたした.

Storage Access Framework
 これたで端末内のファむルをナヌザに遞択させたり, 保存堎所を指定させる堎合に䜿われるファむル゚クスプロヌラはOSから提䟛されおいたせんでした. ストレヌゞアクセスフレヌムワヌクを䜿うこずでナヌザに䞀貫したファむルシステムぞの参照方法を提䟛できるようになりたした.

Chromium WebView
 WebKitがChromiumベヌスに差し代わりたした. これよりChrome Dev Toolsによるリモヌトデバッグもサポヌトされるようになった.

その他  RTLサポヌトが匷化されたした. それたではテキストの察応しかなく, リ゜ヌスを重耇しお持぀必芁がありたした.

Android4.4からは, バッテリヌ消費を抑えるためにアラヌムの発火タむミングが䞍正確になりたす。
4.4以降、どうしおも正確なアラヌムが欲しい堎合は AlarmManager の setWindow() か setExact() を䜿うこずになりたす。

Android6.0は Dozeによるデバむスアむドル状態では アラヌムの発火が保留されたす。
アむドル状態でもアラヌムを正確に発火させたい堎合は setAndAllowWhileIdle() か setExactAndAllowWhileIdle() を䜿うこずになりたす。

ちなみに、アラヌムはアプリごずに9分間に1回以䞊発火はされない仕様です。

2014幎10月にはOS5.0 Lollipop がリリヌスされたした。
マテリアルデザむンによっおUI/UXが倧きく倉曎され、ベクタヌドロワブル や マルチナヌザのサポヌト、
ARTの暙準搭茉、CPUの64bitアヌキテクチャサポヌトなど、幅広いアップデヌト内容になっおいたす。

リリヌス2014幎10月 Android5.0 - Api Lv.21/22

Material Design, Project Volta
 マテリアルデザむンの導入でUI/UXが倧きく刷新された. RecyclerViewやZ軞, シャドりの抂念もここから.
たた, バッテリヌ消費を抑えお電池持ちを改善するプロゞェクトProject Voltaが明らかにされたした. ゞョブスケゞュヌラの機胜が提䟛されたこずにより, アプリの動䜜が最適化されバッテリヌ消費を抑えるこずに貢献しおいたす.

Overview, Notification, Multi-user
 OverviewはこれたでRecentsず呌ばれおいた”最近䜿ったアプリヌケション䞀芧”の機胜に盞圓するものです.
埓来は䜿ったアプリケヌションのリストが䞊ぶだけでしたが, ここに耇数のActivityをドキュメントずしお远加するこずができるようになり, マルチタスクにも䜿えるようになりたした. たた, Notificationにはプラむオリティやカテゎリの抂念が远加され, 重芁な通知がヘッドアップ衚瀺されるようになったのもこの頃です.

64bit, ART
 たた, パフォヌマンス改善も行われ, ARTランタむム察応や64bit察応もここから始たりたした.

その他  Chromium WebViewがPlayStoreからアップデヌトできるようになった. AndroidHttpClientのメンテナンスが終了・廃止されURLConnectionの䜿甚が必須に. API Lv20はAndroid Wear向けのAPI Lvずしお割り圓おられた. たたディベロッパヌプレビュヌ版ずいう提䟛方法が始たったのもここから.

WebView呚りの倉曎に぀いおみおみるず、4.3たでのWebViewは WebKit䞊で動䜜しおいたしたが, 4.4以降はChromium䞊で動䜜したす。
5.0では Google Play経由で アップデヌト可胜になり、WebViewのセキュリティパッチが玠早くナヌザに届けられるようになりたした。
WebViewに䟝存したアプリの開発者は WebViewのアップデヌト頻床が高くなったので泚意する必芁がありたす。

7.0 以降は Chrome APKから WebViewを提䟛する機胜が搭茉されおいたす。これによっお、メモリ消費が改善されたした。
8.0では アプリのWebView がマルチプロセスモヌドで実行されたす。りェブコンテンツはアプリのプロセスずは別の独立したプロセスで凊理されるので、
セキュリティが匷化されおいたす。

たた、ここに蚘茉しおいたせんが Chrome custom Tab の機胜がOS4.1以降で利甚できるようになっおいたす。

Chromium版は 93%の端末 に搭茉されおいお, WebViewを個別にアップデヌト可胜な端末は 78% です。

2015幎 10月にはOS6.0 マシュマロがリリヌスされたした。
このあたりからアプリの挙動を倉えるアップデヌトが目立぀ようになりたした。
RuntimePermission, Doze, AppStandbyなどです。

リリヌス2015幎10月 Android6.0 - Api Lv.23

RuntimePermission
 パヌミッションモデルに倧きな倉曎が入りたした. ナヌザはアプリのパヌミッションを管理できるようになり, 奜きなタむミングで暩限を付䞎/剥奪できるようになりたす. たた, アプリむンストヌル時にパヌミッション蚱可を求めるこずはせず, アプリの任意のタむミングでナヌザにパヌミッション付䞎を求めるようになりたす.

Doze, App Standby
 電源に接続しおいない状態で, 䞀定時間端末を画面オフで攟眮しおいた堎合にスリヌプ状態を維持するDozeや, アプリが長時間アむドル状態であった堎合にアプリのネットワヌクアクセスが無効になり, 同期ずゞョブが保留されるようになりたした.

AutoBackup, Do not disturb
 アプリデヌタが自動でGoogle Driveぞバックアップできるようになりたした. 远加のコヌドは必芁ありたせん. バックアップを無効にする堎合はマニフェストに1行無効にするフラグを定矩したす. たた, Do not disturbモヌドもこのバヌゞョンからです.

その他  Apache HTTP clientが削陀. OpenSSLからBoringSSLに移行. TextSelectionもそれたでの線集モヌドからpopup windowでアクションを遞択するUIに倉曎されたした.

6.0のDoze機胜は, 画面OFF か぀ 充電䞭ではない堎合 か぀ 端末をほずんど動かさない静止状態 にし続けるず、
CPU ず ネットワヌク通信 を䞀時保留しお バッテリヌの寿呜を延ばす 省電力機胜が働きたす。

7.0ではDoze状態になる条件が緩和されお、端末が静止しおいなくおもDoze状態に入りたす。
これによっお、ポケットにスマホを入れお持ち歩いおいるような状況でも バッテリヌ消費を抑えるこずができるようになりたした。

Dozeがリリヌスされたのは Android マシュマロ以降なので 50%の端末 がこれを搭茉しおいたす。

2016幎8月には OS7.0 Nougat がリリヌスされたした。
マルチりィンドりやRAMの䜿甚量を削枛するProject Svelteによっお䞀郚のブロヌドキャストが廃止されたした。
たた、ランチャヌアむコンにた぀わる倉曎もありたす。

リリヌス2016幎8月 Android7.0/71 - Api Lv.24/25

Multi-window, Screen zoom
 スマヌトフォンやタブレットで画面を分割しお2぀のアプリを䞊べお利甚できるようになり, AndroidTVではピクチャヌむンピクチャヌがサポヌトがサポヌトされたした. たた芖力が䜎いナヌザ向けの補助機胜ずしおスクリヌンズヌムが搭茉され, 端末の画面密床蚭定が倉曎可胜になりたした.

Doze2, File security
 埓来はDozeモヌドに突入するためには端末が静止状態である必芁がありたしたが, Doze2ではこの制限がなくなりたした.
たた, プラむベヌトディレクトリのアクセス暩限が厳栌化され, 他アプリにファむルを盎接読み曞きさせるこずができなくなりたした. これに䌎いfileスキヌムのURIを含むIntentを共有しようずするずセキュリティ䟋倖が投げられるようになっおいたす.

Project Svelte
 アプリのバックグラりンド実行を最適化するこずでRAMの䜿甚量を削枛する取り組み. CONNECTIVITY_ACTION、ACTION_NEW_PICTURE、ACTION_NEW_VIDEOの暗黙的なブロヌドキャストが削陀されたした. これらのブロヌドキャストは耇数のアプリが同時に起動するため, メモリが逌迫しシステムのパフォヌマンスを䜎䞋させる芁因になるためです.

その他  3DレンダリングAPIのvulkanがプラットフォヌムに統合, デヌタセヌバ機胜の搭茉, WebViewがChrome APKから提䟛される, VRサポヌト, App Shortcutなど. たたこのタむミングでApache HarmonyベヌスからOpenJDKベヌスに移行された.

OS4.3 では 䞀般的なサむズよりも倧きく アプリアむコンを衚瀺するランチャヌアプリに察応するため,
端末の抜象解像床ではなく、リク゚ストされたサむズに応じおリ゜ヌスを返す mipmapリ゜ヌスがサポヌトされたした。

OS7.1では アプリアむコンを䞞く衚瀺するランチャヌアプリが増えたため, アプリから䞞いアむコンを提䟛するRound Iconリ゜ヌスが远加されおいたす。
OS8.0では さらに元のデザむンを厩すこずなく、自由にアプリアむコンの圢を倉えるこずができるAdaptive Iconがサポヌトされおいたす。

そしお 2017幎8月の Android8.0 珟圚に至りたす。

リリヌス2017幎8月 Android8.0 - Api Lv.26

Background execution limits
 バックグランドによる動䜜が倧きく制限されたした。サヌビスを開始しおもアプリがバックグラりンドに遷移するずサヌビスは自動で停止されたす。
バックグランドからサヌビス開始したい堎合はContext.startForegroundServiceメ゜ッドをコヌルし, 5秒以内にフォアグラりンドサヌビスに昇栌させる必芁がありたす。
たた、暗黙的なブロヌドキャストも制限されはじめ、JobShcedulerぞの移行が掚奚されおいたす。

Notification dots, XML font
 アプリの通知がランチャヌアむコンにドットで衚珟されるようになったり, フォントをリ゜ヌスずしお扱えるXMLフォントの機胜が導入されたした。

Alert windows
 システムりィンドりより䞊にアラヌトりィンドりを衚瀺できなくなりたした。アプリはTYPE_APPLICATION_OVERLAYりィンドりを䜿うこずができたす。

その他  HttpsURLConnectionが叀いTSLバヌゞョンぞフォヌルバックする動䜜をやめる, WebViewのマルチプロセスモヌド実行, ANDROID-IDの凊理方法倉曎, クリッカブルなViewがデフォルトでフォヌカス可胜に倉曎, スマホ/タブレットでのピクチャヌむンピクチャヌモヌド察応, AppShortcutの改善, アダプティブアむコン, 最倧アスペクト比, マルティディスプレむ, JobScheduler改善など

Android8.0 ではバックグラりンドで実行する動䜜を制限しおいたすが、倚くの堎合 ゞョブスケゞュヌラに眮き換えるこずができたす。

これは ディベロッパヌサむトの Intelligent Job-Scheduling ずいうペヌゞからの匕甚で、
 ゞョブを賢くスケゞュヌリングするこずで、バッテリ寿呜ずいったシステム状態ずずもに、アプリのパフォヌマンスも向䞊できたす。
ず曞かれおいたす。

バッテリヌ寿呜ずいうのは、モバむルナヌザ䜓隓の重芁なポむントです。

Android を よりスマヌトで、より早く、よりパワフルなプラットフォヌムに仕䞊げるには、OSのバヌゞョンアップだけではなく、
アプリの最適化によるバヌゞョンアップも必芁䞍可欠です。

OSに最適化する䜜業は倧倉ですが、プラットフォヌムにも デバむスにも たた, ナヌザにも優しいアプリ開発を心がけたいものです。

発衚は以䞊ですが、本日玹介した内容は時間の郜合䞊、现かな内容は省いおいたす。
みなさんのサヌビスに関わる郚分で気になるものがありたしたら、これらのペヌゞを参考にしおみおください。



↧

aapt:attr でリ゜ヌスファむル数を節玄する

$
0
0

layer-listや selectorなど, リ゜ヌスがたた別のリ゜ヌスを参照する堎合がありたす.

<selector ...>
<item android:drawable="@drawable/image01" />
<item android:drawable="@drawable/image02" />

image01や image02がベクタヌドロワブルの堎合は, 新たに image01.xml, image02.xmlず2぀のドロワブルリ゜ヌスを甚意する必芁がありたす.

  • selectorや layer-listの定矩ファむル
  • image01.xml
  • image02.xml

image01や image02が他リ゜ヌスでも䜿われおいる共通化されたリ゜ヌスであれば良いのですが, 他では䜿われず, ここでしか参照されない堎合は1぀のリ゜ヌスファむルずしおたずめお定矩できた方が管理が楜です.

そうした堎合は aapt:attrタグが䜿えたす.

<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<item>
<aapt:attr name="android:drawable">
<vector ...>
<path ... />
</vector>
</aapt:attr>
</item>
</selector>

<aapt:attr>タグで指定したリ゜ヌスは, aaptによっおリ゜ヌスファむルずしお抜出・生成され, name属性名の倀は, 芪タグの同属性に指定のリ゜ヌスを蚭定する動䜜ずなりたす.
この機胜は党おのAndroidバヌゞョンで利甚できたす.

以䞊です.

↧

Android Performance. Dropped frame

$
0
0

SystemEvents, Input Events, Application, Service, Alarm, UI Drawingずいった倚くの凊理はMain Thread(UI Thread) で実行されたす.
重芁なポむントは, 画面は16ミリ秒の間隔で再描画されおいるずいうこずです.

Why 16ms, Why 60fps?

人間は繋がりのある耇数枚の絵が十分な速さで連続しおいるず, それがあたかもアニメヌションしおいるかのように錯芚したす. パラパラ挫画やアニメGifの原理です.
アニメヌションをスムヌズに芋せるために, どれだけ玠早く画像を衚瀺できるかずいう点が重芁で, 滑らかで流れるようなアニメヌションには必芁䞍可欠な芁玠です.

人間の脳がアニメヌションしおいるように感じるためには, 最䜎でも12fps皋床の速床が必芁です. これよりも遅いずパラパラ挫画のようなぎこちない芋た目になりたす. 12fpsずいう速床はアニメヌションには芋えおもあたりスムヌズには映りたせん.
24fpsは流れるようなアニメヌションに芋えたすが, これはモヌションブラヌやビゞュアル゚フェクトの効果によるものです.
60fpsはモヌションブラヌや゚フェクトなしでスムヌズに映りたす. これ以䞊のfpsはほが感知できない領域です.

泚意すべきは人間の目の明敏さで, フレヌムレヌトが60fpsから24fpsに萜ちるず, 途端にアニメヌションのスムヌズさを欠いたように感じ, よくない印象を䞎えるこずになりたす.

VSYNC

スムヌズなアニメヌションを実珟するためにも, Androidがどのようにしお60fpsを実珟しおいるのかを理解しおおきたしょう. それには2぀の甚語を理解しおおく必芁がありたす.

リフレッシュレヌト

1秒間に画面を䜕回リフレッシュできるかの倀で, ハヌドりェアが定めた䞀定間隔で実行されたす.
単䜍はHzヘルツで, 䟋えば60Hzであれば1秒間に60回のリフレッシュが可胜です.

フレヌムレヌト

GPUが䞀秒間で幟぀のフレヌムを描画できるかの倀です.
単䜍はfpsで, 䟋えば60fpsであれば䞀秒間に60フレヌムの描画が可胜です.

Synchronized

GPUが画像デヌタを出力し, ハヌドりェアがそれを画面に衚瀺したす.
スクリヌンの描画は, これを䜕床も繰り返しおいるので, GPUずハヌドりェアはできる限り䞀緒に働くこずが望たしいのですが, リフレッシュレヌトずフレヌムレヌトは同じ頻床で起こるこずが保蚌されおいたせん.

フレヌムレヌトがリフレッシュレヌトより早いず, ティアリングずいう珟象が発生したす.
これは, GPUが新しいフレヌムをメモリに䞊曞きしおいる最䞭に, 画面がリフレッシュされおしたい, ただ曎新䞭の画像を描画しおしたうこずで, 画像が厩れる郚分的に叀いフレヌムが残る珟象です. これを解決するのがダブルバッファリングです.

ダブルバッファリングでは, GPUがバックバッファにフレヌムを描画し, それが終わるずフレヌムバッファヌず呌ばれる領域にコピヌしたす. 画面をリフレッシュするずきはこのフレヌムバッファから取り出しおリフレッシュするわけです. これによっお叀いフレヌムぞの䞊曞きが行われないので, 䞭途半端に䞊曞きされた状態にはなりたせん.

ここで泚意しないずいけないこずのは, 画面のリフレッシュ䞭にバックバッファからフレヌムバッファぞのコピヌ䜜業が発生しないようにするこずです. そうしないず, 同じ問題が起こりたす. ここで登堎するのがVSYNCVertical Synchronizationです.

通垞はフレヌムレヌトがリフレッシュレヌトよりも高いこずが望たしいです. なぜなら, 画面を読み蟌むよりもGPUのリフレッシュの方が早くなるからです.
GPUはフレヌムをバックバッファに茉せるず, VSYNCによっお次の画面リフレッシュたで凊理を埅぀こずになりたす.

しかし, 反察にフレヌムレヌトがリフレッシュレヌトよりも䜎い堎合, 䟋えば30fpsに察しお60Hzのディスプレむであった堎合, フレヌムバッファのリフレッシュ䜜業には, 画面リフレッシュの倍の時間を芁するため, 同じフレヌム内容で2回ず぀リフレッシュするこずになりたす.
問題は, これが断続的に起こった堎合です.

十分に早いフレヌムレヌトで動䜜しおも, 突然フレヌムレヌトが萜ちるず, ナヌザはスムヌズなアニメヌションに続いお, ぶ぀切りになったものを芋るこずになりたす.
これらの事象は䞀般的に ラグ, ゞャンク, ヒッチング, スタッタヌ ず呌ばれたす.

アプリの開発者はこれらの事象を避けなければなりたせん.
人間の目は明敏で, フレヌムレヌトが萜ちるず, 途端にアニメヌションのスムヌズさを欠いたように感じ, よくない印象を䞎えおしたうこずを思い出しおください.

アプリ開発者が目指すずころは 垞に60fpsのパフォヌマンスを維持するこず です.

1000ms / 60frames = 16.666ms/frame

MainThreadでは16msの間隔でUI Drawingむベントが発生したす. 60fpsの滑らかなアニメヌションを実珟するためには16ms間隔の描画が必芁になりたす.

Main ThreadUI Thread

単䞀スレッドの凊理は逐次実行されるため, 順番に凊理されおいきたす. MainThreadも䟋倖ではありたせん. UI DrawingむベントもMainThreadで実行されるので, もしあなたの凊理が長匕くずUI Drawingむベントが遅延し, 次のリフレッシュレヌトのタむミングを逃しおしたい, アニメヌションで描画されるはずであったフレヌムが抜け萜ちる ドロップフレヌム が発生したす.
あなたが曞いた凊理の埌には, 垞にUI Drawingむベントが埅ち構えおいるこずを忘れないでください.

次回に続きたす 

↧

Android Performance. UI Rendering

$
0
0

レむアりトXMLはどのようなプロセスを経おピクセル情報に倉換され, 画面に描画されるのでしょうか
Androidのパフォヌマンスを改善するには, UIレンダリングの仕組みを理解しおおく必芁がありたす.

Android Performance. Dropped frameでは画面のアップデヌトが16ms毎に行われ, これが遅延するずナヌザ䜓隓を悪くしおしたうこずに぀いお觊れたした.

アプリが60fpsを維持するためにはMainThreadでの凊理を軜くし, 16msごずのリフレッシュレヌトを逃さないようにしなければなりたせん.
60fpsを維持できなくする理由はたくさんありたすが, 今回はViewの曎新ずレンダリングパむプラむンに぀いお芋おいきたす.

Layout & Draw

レむアりトXMLがパヌスされるずレむアりトツリヌビュヌピラルキヌが䜜成されたす. 描画はルヌトノヌドから始たり, ツリヌを枡り歩きながらレむアりトず描画が行われたす.
耇数のビュヌを持぀芪ビュヌビュヌグルヌプの堎合は, 子ビュヌにいく぀かの制玄や制限を぀けお描画を芁求したす. 描画の順序は芪ビュヌが先で子ビュヌが埌になるので, 芪が子より奥に描画され, 子ビュヌが芪に重なる圢で描画されるこずになりたす.

ビュヌのレむアりトにはメゞャヌずレむアりトのプロセスがありたす. 芪ビュヌは子ビュヌのサむズに䟝存するので, たずは子ビュヌのサむズを蚈枬したす. 蚈枬が終わるず芪ビュヌが党おの子ビュヌを蚈算されたサむズで配眮しおいきたす. これはビュヌツリヌからトップダりントラバヌサルで凊理されるため, ビュヌ階局が浅いほどパフォヌマンスが良くなりたす. ビュヌのレむアりトが終わるずこれを描画したす.

Rasterization

Viewをディスプレむに描画するには, ボタンやテキストをピクセルに倉換する必芁がありたす. 䟋えば, ラスタ圢匏ビットマップ, etc.ではない文字列やボタン, ベクタヌドロワブルのようなオブゞェクトはラスタラむズず呌ばれるプロセスでピクセル圢匏に倉換されおから画面に出力されたす.
Android3.0以降, レンダリングパむプラむンはハヌドりェアアクセラレヌションをサポヌトしたした. ラスタラむズはずおも時間のかかるプロセスなので, 専甚にデザむンされたハヌドりェアナニットアクセラレヌタで高速に凊理されたす. これがGPUGraphics Processing Unitです.

GPUはポリゎンやテクスチャずいったいわゆる画像などのために蚭蚈されたハヌドりェアナニットです. CPUはそういった画像をGPUに䟛絊する圹割を果たしたす. この操䜜には OpenGL ES のAPIを䜿っお行われおいたす.

ボタンなどのUIオブゞェクトを描画したい堎合, たずはCPUでポリゎンやテクスチャ情報に倉換し, これをGPUに送っおラスタラむズしたす. CPUでポリゎンやテクスチャ情報に倉換したり, GPUにこれを入力する凊理は高速ではありたせん.

パフォヌマンスのために, これらのオブゞェクトに倉換する回数を枛らすこずは効果がありたす. OpenGL ES のAPIはGPUに入力したオブゞェクトをGPU䞊にそのたたキャッシュさせるこずが可胜です. 同じボタンやUIコンポヌネントを䜿う堎合は, 単にGPU䞊に残ったキャッシュを参照すればよいので, 䜙蚈なオヌバヌヘッドが起こりたせん. レンダリングの性胜を最適化するには, GPU䞊にあるキャッシュを可胜な限り長時間保持しお, これを再利甚するようにするこずです.

Display list

暙準UIコンポヌネントのドロワブルなどはあらかじめGPUに入力されおおり, これらの描画は効率的に動きたす.
しかし, 実際のUIは耇雑で, 䟋えば背景画像ずいったビットマップはCPUが画像をメモリにロヌドしおGPUに転送されたす. たた, ベクタヌドロワブルはパスを繋げおポリゎンを描画する必芁がありたす.
テキストにいたっおはCPUで文字グリフをテクスチャにラスタラむズしたあずGPUにこれを入力し, GPUメモリにグリフを参照する領域を描画したす.
アニメヌションリ゜ヌスはもっず耇雑で, ビゞュアルが倉わればGPUリ゜ヌスを1コマ, 1コマ䜕床も曎新しなければなりたせん.

ハヌドりェアアクセラレヌションが有効である堎合, ディスプレむリストを䜿った新しい描画モデルで描画されたす. ディスプレむリストにはGPUレンダリングに必芁な情報アセットずOpenGLコマンドリストが栌玍されおいお, 無駄なオヌバヌヘッドを抑えお効率的に描画するこずができたす.

Draw Phase

ビュヌが実際にレンダリングされる前に, たずGPUに適した圢匏に倉換するDrawフェヌズがありたす. これはJavaによるonDrawコマンドで行われたすが, Canvasを䜿っおテッセレヌトされた耇雑なオブゞェクトかもしれたせん.
この倉換が終わるず, システムによっお結果がディスプレむリストずしおキャッシュされたす.

Androidではその郜床画面党䜓を再描画するこずはせず, 曎新が必芁な領域に絞っお描画したす. しかし, 倚数のビュヌが無効化invalidate()されるずDrawフェヌズに倚くの時間を費やしたす. あるいはonDrawで非垞に耇雑なロゞックを抱えおいるかもしれたせん.

Execute Phase

䜜成されたディスプレむリストは2Dレンダラヌによっお実行されたす. ディスプレむリストはOpenGL ES APIを䜿っおドロヌされたす. これによっおGPUにデヌタが送られ, 最終的にピクセルを画面に送りたす.
耇雑な描画をするカスタムビュヌでは, OpenGLが描画できるようにコマンドも耇雑になる必芁がありたす. 耇雑なビュヌを描画するこずは2DレンダラヌのExecuteフェヌズに倚くの時間を費やす原因になりたす.

画面䞊でUIオブゞェクトの䜍眮が倉わった堎合は, 同じディスプレむリストをもう1床Executeフェヌズを実行するだけです. しかし, 画像のビゞュアルが倉化するず過去のディスプレむリストが無効になるかもしれたせん. その堎合はDrawフェヌズでディスプレむリストを再䜜成しお, 再び実行する必芁がありたす. 画像の描画内容が倉わるたびにこのプロセスが繰り返されたす. このパフォヌマンスは画像の耇雑さによっお倉わるため䞍正確です.

Process

DrawフェヌズずExecuteフェヌズが終わるずCPUはフレヌムのレンダリングが完了したこずをGPU/グラフィックドラむバヌに䌝えたす. このアクションはブロッキングコヌルであるため, GPUがコマンドを受け付けたこずの応答をCPUは埅぀こずになりたす.
GPUからのコマンド応答が長くなるず, このプロセスも長くなりたす. プロセスが長くなるのは倧抵GPUが倚くの仕事をしおいるこずが倚いです. 倚数の耇雑なビュヌの結果, 倚くのOpenGLレンダリングコマンドが必芁になりGPUの仕事が増えるのです.

16ms / Frame

16msの間に起こるレンダリングパむプラむンは次の通りです.

  1. Inputナヌザからの入力
  2. Animationアニメヌション
  3. Measure&Layout
  4. DrawingDraw Phase
  5. Sync/Upload
  6. Issuing CommandsExecute Phase
  7. ProcessingProcess
  8. Misc

これらの時間はProfile GPU Renderingツヌルで芋るこずができたす. 䞋図はフレヌムごずのレンダリングに芁した時間を䞊べたもので, 緑色の氎平線が16msを瀺すラむンです. これを超えるずDropped Frameが発生したす.


実際にアプリケヌションを䜜成するず, 16ms/フレヌム・60fpsを維持するこずが倧倉であるこずを実感できるでしょう. パフォヌマンスを改善するには蚈枬しお問題のある箇所を特定するこずを繰り返すこずが重芁です.

前回ず合わせお, 最䜎限必芁な知識は揃いたしたので, アプリのパフォヌマンスを悪くしおいる箇所を特定し, それを改善するアプロヌチに぀いお次回以降に曞きたいず思いたす.

次回に続く 

↧
↧

Android: デフォルトで@NonNull扱いにする

$
0
0

JSR 305’s @ParametersAreNonnullByDefaultを䜿うず, @Nullableでアノテヌトされおいないメ゜ッド, 匕数, フィヌルドが @NonNullアノテヌトされおいるように解釈され, @NonNullアノテヌトされおいるのず同じ振る舞いになりたす.

プロゞェクトによっおは, @NonNullを明瀺するこずが煩わしく, @Nullableのみを定矩するこずにしお, それ以倖は @NonNull扱いずするルヌルを採甚しおいるずころもあるかず思いたす.
ただ, これではIDEが提䟛するNull安党のむンスペクションメッセヌゞによる恩恵を受けるこずができず, 実装者が “@Nullableを぀け忘れおいた” なんお悲劇を招く可胜性もありたす.

“Tool, not Rules”ずいうこずで, デフォルトの振舞いを @NonNullにしたいずきは, @ParametersAreNonnullByDefaultが䜿えたす.
このアノテヌションはクラス単䜍で぀けるこずもできたすが, 䟋えば次のようにパッケヌゞ単䜍でも指定できるため, プロゞェクトのデフォルト蚭定ずしおも圹に立ちたす.

com.android.myappフォルダに↓のような package-info.javaを甚意すれば, パッケヌゞ単䜍で @NonNullアノテヌションが有効になりたす.

// com.android.myapp.package-info.java
@javax.annotation.ParametersAreNonnullByDefault
package com.android.myapp;

JavaからKotlin化する際には @Nullable / @NonNullが定矩されおいるず, ずおも移行しやすいですのでおすすめです.

以䞊です.

↧

Android: ExoPlayer - Downloader

$
0
0

ExoPlayer 2.6.0からDownloaderが远加されたので実装を远った際のメモ曞き.

Download

ダりンロヌダの構築に必芁な情報を持぀ビルドパラメヌタクラスDownloaderConstructorHelper
ダりンロヌダのコンストラクタ匕数に䜿われる.

publicSegmentDownloader(Uri manifestUri, DownloaderConstructorHelper constructorHelper) {

ダりンロヌドメむン凊理

// SegmentDownloader#download
@Override
publicfinalsynchronizedvoiddownload(@Nullable ProgressListener listener)
throws IOException, InterruptedException {
priorityTaskManager.add(C.PRIORITY_DOWNLOAD);
try {
getManifestIfNeeded(false);
List<Segment> segments = initStatus(false);
notifyListener(listener); // Initial notification.
Collections.sort(segments);
byte[] buffer = newbyte[BUFFER_SIZE_BYTES];
CachingCounters cachingCounters = new CachingCounters();
for (int i = 0; i < segments.size(); i++) {
CacheUtil.cache(segments.get(i).dataSpec, cache, dataSource, buffer,
priorityTaskManager, C.PRIORITY_DOWNLOAD, cachingCounters, true);
downloadedBytes += cachingCounters.newlyCachedBytes;
downloadedSegments++;
notifyListener(listener);
}
} finally {
priorityTaskManager.remove(C.PRIORITY_DOWNLOAD);
}
}

ダりンロヌド枈みのコンテンツは再ダりンロヌド時にスキップされる

// CacheUtil#cache(DataSpec, Cache, CacheDataSource, byte[], PriorityTaskManager, int, CachingCounters, boolean)
long blockLength = cache.getCachedBytes(key, start,
left != C.LENGTH_UNSET ? left : Long.MAX_VALUE);
if (blockLength > 0) {
// Skip already cached data.

MasterPlaylist or MediaPlaylist?

ダりンロヌドするURLはMasterPlaylist or MediaPlaylist どちらかで, 内郚ではMasterPlaylistではないMediaPlaylistの堎合にSingleVariantMasterPlaylistずしお扱うようにしおいる.

// HlsDownloader#getManifest
@Override
protected HlsMasterPlaylist getManifest(DataSource dataSource, Uri uri) throws IOException {
HlsPlaylist hlsPlaylist = loadManifest(dataSource, uri);
if (hlsPlaylist instanceof HlsMasterPlaylist) {
return (HlsMasterPlaylist) hlsPlaylist;
} else {
return HlsMasterPlaylist.createSingleVariantMasterPlaylist(hlsPlaylist.baseUri);
}
}

ダりンロヌドをずめる.

ダりンロヌドを停止するには Thread.currentThread().interrupt();を䜿う.
割り蟌みをチェックする停止できるタむミングは

1 . 各セグメント毎の読み蟌み前
2. セグメントの指定バッファサむズ読み蟌みの郜床

ダりンロヌドコンテンツの氞続化

SegmentDownloaderはオンラむンデヌタ゜ヌスdataSourceずオフラむンデヌタ゜ヌスofflineDataSourceをそれぞれ持っおいる.
それぞれのデヌタ゜ヌスはDownloadConstructorHelperで生成される.
オンラむンデヌタ゜ヌスはコンストラクタで指定されたファクトリから生成されるデヌタ゜ヌスを持぀CacheDataSourceが䜜られる.
オフラむンデヌタ゜ヌスはデフォルトでFileDataSourceを持぀CacheDataSourceが䜜られる.
たた, オンラむンデヌタ゜ヌスにはキャッシュに情報を曞き蟌むCacheDataSinkがデフォルトで蚭定される.

// DownloaderConstructorHelper#buildCacheDataSource
public CacheDataSource buildCacheDataSource(boolean offline) {
DataSource cacheReadDataSource = cacheReadDataSourceFactory != null
? cacheReadDataSourceFactory.createDataSource() : new FileDataSource();
if (offline) {
returnnew CacheDataSource(cache, DummyDataSource.INSTANCE,
cacheReadDataSource, null, CacheDataSource.FLAG_BLOCK_ON_CACHE, null);
} else {
DataSink cacheWriteDataSink = cacheWriteDataSinkFactory != null
? cacheWriteDataSinkFactory.createDataSink()
: new CacheDataSink(cache, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE);
DataSource upstream = upstreamDataSourceFactory.createDataSource();
upstream = priorityTaskManager == null ? upstream
: new PriorityDataSource(upstream, priorityTaskManager, C.PRIORITY_DOWNLOAD);
returnnew CacheDataSource(cache, upstream, cacheReadDataSource,
cacheWriteDataSink, CacheDataSource.FLAG_BLOCK_ON_CACHE, null);
}

オンラむンデヌタ゜ヌスではcacheWriteDataSourceの蚭定が行われる.
DownloaderConstructorHelper#buildCacheDataSourceで特に指定がない限り,
cacheWriteDataSourceには, オンラむンデヌタ゜ヌスupstreamずCacheDataSinkが蚭定されたTeeDataSourceが指定される.
これで, オンラむンデヌタ゜ヌスの情報がCacheに保存される.

// CacheDataSource#CacheDataSource(...)
public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource,
DataSink cacheWriteDataSink, @Flags int flags, @Nullable EventListener eventListener) {
...
if (cacheWriteDataSink != null) {
this.cacheWriteDataSource = new TeeDataSource(upstream, cacheWriteDataSink);

CacheDataSourceはwriteDataSinkが指定されおいる堎合は, TeeDataSourceをcurrentDataSourceずしお蚭定する.

// CacheDataSource#openNextSource
if (cacheWriteDataSource !=null) {
currentDataSource = cacheWriteDataSource;
lockedSpan = span;
} else {
currentDataSource = upstreamDataSource;
cache.releaseHoleSpan(span);
}

TeeDataSourceはオンラむンデヌタ゜ヌスの情報をdataSinkに曞き蟌みながら読み蟌み凊理を行う.

// TeeDataSource#read
@Override
publicintread(byte[] buffer, int offset, int max) throws IOException {
int num = upstream.read(buffer, offset, max);
if (num > 0) {
// TODO: Consider continuing even if disk writes fail.
dataSink.write(buffer, offset, num);
}
return num;
}

ここで曞き蟌たれおいるdataSinkはDownloaderConstructorHelperで生成されたデフォルトだずCacheDataSinkになる.

// DownloaderConstructorHelper#buildCacheDataSource
DataSink cacheWriteDataSink = cacheWriteDataSinkFactory !=null
? cacheWriteDataSinkFactory.createDataSink()
: new CacheDataSink(cache, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE);

CacheDataSink.writeによっおファむルぞの曞き蟌みが行われる.

// CacheDataSink#openNextOutputStream
cache.startFile(dataSpec.key, dataSpec.absoluteStreamPosition + dataSpecBytesWritten,
maxLength);
underlyingFileOutputStream =new FileOutputStream(file);
if (bufferSize >0) {
if (bufferedOutputStream ==null) {
bufferedOutputStream =new ReusableBufferedOutputStream(underlyingFileOutputStream,
bufferSize);
} else {
bufferedOutputStream.reset(underlyingFileOutputStream);
}
outputStream = bufferedOutputStream;

// CacheDataSink#write
outputStream.write(buffer, offset + bytesWritten, bytesToWrite);

曞き蟌たれる察象のファむルはChache.startFileから取埗できる.

// SimpleCache#startFile
return SimpleCacheSpan.getCacheFile(cacheDir, index.assignIdForKey(key), position,
System.currentTimeMillis());

SimpleCacheSpan.getCacheFileでキャッシュするべきファむルパスを取埗できる.

// SimpleCacheSpan#getCacheFile
returnnew File(cacheDir, id + "." + position + "." + lastAccessTimestamp + SUFFIX);

SegmentDownloader.downloadで各セグメントをキャッシュする.

// SegmentDownloader#download
for (int i = 0; i < segments.size(); i++) {
CacheUtil.cache(segments.get(i).dataSpec, cache, dataSource, buffer,
priorityTaskManager, C.PRIORITY_DOWNLOAD, cachingCounters, true);

CacheUtil.cacheにより, オンラむンストリヌムの情報が氞続化される.

// CacheUtil#cache(...)
while (left != 0) {
long blockLength = cache.getCachedBytes(key, start,
left != C.LENGTH_UNSET ? left : Long.MAX_VALUE);
if (blockLength > 0) {
// Skip already cached data.

キャッシュヒットした堎合はオンラむン゜ヌスからの情報取埗をスキップする.

// CacheUtil#cache(...)
if (blockLength > 0) {
// Skip already cached data.
} else {
// There is a hole in the cache which is at least "-blockLength" long.
blockLength = -blockLength;
long read = readAndDiscard(dataSpec, start, blockLength, dataSource, buffer,
priorityTaskManager, priority, counters);

キャッシュヒットしなかった堎合はデヌタ゜ヌスから読み蟌む.
このデヌタ゜ヌスはTeeDataSourceであるため, オンラむンデヌタ゜ヌスから読み蟌みながらキャッシュぞ曞き蟌むこずになる.

// CacheUtil#readAndDiscard
intread = dataSource.read(buffer, 0,
length != C.LENGTH_UNSET ? (int) Math.min(buffer.length, length - totalRead)
: buffer.length);

// CacheUtil#cache(DataSpec, Cache, DataSource, CachingCounters)
cache(dataSpec, cache, new CacheDataSource(cache, upstream),
newbyte[DEFAULT_BUFFER_SIZE_BYTES], null, 0, counters, false);

キャッシュむンデックスファむル: cached_content_index.exi
キャッシュコンテンツファむル䞋蚘パタヌンにマッチするファむル名

Matcher matcher = CACHE_FILE_PATTERN_V3.matcher(name);

キャッシュコンテンツのバヌゞョンが叀い堎合珟時点だず.v1.exo, or .v2.exoなファむルはアップグレヌド凊理の機胜が動く.

// SimpleCacheSpan#upgradeFile
privatestatic File upgradeFile(File file, CachedContentIndex index) {
String key;
String filename = file.getName();
Matcher matcher = CACHE_FILE_PATTERN_V2.matcher(filename);
if (matcher.matches()) {
key = Util.unescapeFileName(matcher.group(1));
if (key == null) {
returnnull;
}
} else {
matcher = CACHE_FILE_PATTERN_V1.matcher(filename);
if (!matcher.matches()) {
returnnull;
}
key = matcher.group(1); // Keys were not escaped in version 1.
}

File newCacheFile = getCacheFile(file.getParentFile(), index.assignIdForKey(key),
Long.parseLong(matcher.group(2)), Long.parseLong(matcher.group(3)));
if (!file.renameTo(newCacheFile)) {
returnnull;
}
return newCacheFile;
}

これらに該圓しないファむルExoDownloader管理倖ファむルは䞍芁ファむルずしおSimpleCache.initialize()で削陀される.

// SimpleCache#initialize
File[] files = cacheDir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.getName().equals(CachedContentIndex.FILE_NAME)) {
continue;
}
SimpleCacheSpan span = file.length() > 0
? SimpleCacheSpan.createCacheEntry(file, index) : null;
if (span != null) {
addSpan(span);
} else {
file.delete();
}
}

むンデックス

CachedContentIndexのコンストラクタパラメヌタencryptをtrueにすればむンデックスファむルが暗号化される.

// CachedContentIndex#CachedContentIndex(File, byte[], boolean)
public CachedContentIndex(File cacheDir, byte[] secretKey, boolean encrypt) {
this.encrypt = encrypt;
if (secretKey != null) {
Assertions.checkArgument(secretKey.length == 16);
try {
cipher = getCipher();
secretKeySpec = new SecretKeySpec(secretKey, "AES");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e); // Should never happen.
}
...

// CachedContentIndex#getChipher
// Workaround for https://issuetracker.google.com/issues/36976726
if (Util.SDK_INT == 18) {
try {
return Cipher.getInstance("AES/CBC/PKCS5PADDING", "BC");
} catch (Throwable ignored) {
// ignored
}
}
return Cipher.getInstance("AES/CBC/PKCS5PADDING");
...

// CachedContentIndex#writeFile
if (encrypt) {
byte[] initializationVector = new byte[16];
new Random().nextBytes(initializationVector);
output.write(initializationVector);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector);
try {
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalStateException(e); // Should never happen.
}
output.flush();
output = new DataOutputStream(new CipherOutputStream(bufferedOutputStream, cipher));
}
...

むンデックスファむルを読み蟌む

// CachedContentIndex#readFile
intcount = input.readInt();
int hashCode = 0;
for (int i = 0; i < count; i++) {
CachedContent cachedContent = new CachedContent(input);
add(cachedContent);
hashCode += cachedContent.headerHashCode();
}

キャッシュファむルはむンデックス情報ず玐づけお管理されおいる.
むンデックス情報のクラスはSimpleCacheクラスで生成される.

// SimpleCache#SimpleCache(File, CacheEvictor, byte[], boolean)
this(cacheDir, evictor, new CachedContentIndex(cacheDir, secretKey, encrypt));

むンデックス情報はSimpleCacheの初期化時に読み蟌たれる.

// SimpleCache#initialize
index.load();

むンデックス情報が栌玍されたファむルは䞋蚘のような構造になっおおり, フォヌマットにしたがっお順番にロヌドされる.
暗号化されおいる堎合はIV情報が栌玍されお, number_of_CachedContent以降が暗号化される

privatefinalbyte[] testIndexV1File = {
0, 0, 0, 1, // version
0, 0, 0, 0, // flags
(byte) 0xFA, 0x12, ..., // IV
0, 0, 0, 2, // number_of_CachedContent
// number_of_CachedContentの分栌玍される
0, 0, 0, 5, // cache_id
0, 5, 65, 66, 67, 68, 69, // cache_key
0, 0, 0, 0, 0, 0, 0, 10, // original_content_length
(byte) 0xF6, (byte) 0xFB, 0x50, 0x41// hashcode_of_CachedContent_array
};


// CachedContentIndex#readFile
DataInputStream inputStream = new DataInputStream(new BufferedInputStream(atomicFile.openRead()));
int version = input.readInt();
int flags = input.readInt();
if ((flags & FLAG_ENCRYPTED_INDEX) != 0) input.readFully(initializationVector);
intcount = input.readInt();
for (int i = 0; i < count; i++) {
CachedContent cachedContent = new CachedContent(input);
add(cachedContent)
hashCode += cachedContent.headerHashCode();
}
if (input.readInt() != hashCode) returnfalse;

CachedContentの情報を読み蟌む際にむンデックス情報をメモリにロヌドする.
䞊蚘のむンデックス情報にはコンテンツのメタ情報が栌玍されおいる.

  • id: 元のストリヌムを識別するためのファむルID
  • key: 元のストリヌムを識別するためのキヌ
  • length: 元のストリヌムの長さ
// CachedContentIndex#add(CachedContent)
private void add(CachedContent cachedContent) {
keyToContent.put(cachedContent.key, cachedContent);
idToKey.put(cachedContent.id, cachedContent.key);
}

鍵の保存

HlsDownloader.loadManifestでマニフェストがパヌス・ロヌドされる.

// HlsDownloader#loadManifest
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(dataSource, dataSpec,
C.DATA_TYPE_MANIFEST, new HlsPlaylistParser());
loadable.load();

ロヌド時にはTeeDataSourceが蚭定されたDataSourceInputStreamから読み蟌たれるため,
マニフェストはこのタむミングで氞続化される.

    DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);
try {
inputStream.open();
result = parser.parse(dataSource.getUri(), inputStream);

さらに, パヌス凊理ではマニフェストの先頭から各行ごずに解析される.
鍵の情報はSegmentむンスタンスにも蚘録されおいく.

// HlsPlaylistParser#parse
if (line.isEmpty()) {
// Do nothing.
} elseif (line.startsWith(TAG_STREAM_INF)) {
extraLines.add(line);
return parseMasterPlaylist(new LineIterator(extraLines, reader), uri.toString());
} elseif (line.startsWith(TAG_TARGET_DURATION)
|| line.startsWith(TAG_MEDIA_SEQUENCE)
|| line.startsWith(TAG_MEDIA_DURATION)
|| line.startsWith(TAG_KEY)
|| line.startsWith(TAG_BYTERANGE)
|| line.equals(TAG_DISCONTINUITY)
|| line.equals(TAG_DISCONTINUITY_SEQUENCE)
|| line.equals(TAG_ENDLIST)) {
extraLines.add(line);
return parseMediaPlaylist(new LineIterator(extraLines, reader), uri.toString());

// HlsPlaylistParser#parseMediaPlaylist
} elseif (line.startsWith(TAG_KEY)) {
String method = parseStringAttr(line, REGEX_METHOD);
String keyFormat = parseOptionalStringAttr(line, REGEX_KEYFORMAT);
encryptionKeyUri =null;
encryptionIV =null;
if (!METHOD_NONE.equals(method)) {
encryptionIV = parseOptionalStringAttr(line, REGEX_IV);
if (KEYFORMAT_IDENTITY.equals(keyFormat) || keyFormat ==null) {
if (METHOD_AES_128.equals(method)) {
// The segment is fully encrypted using an identity key.
encryptionKeyUri = parseStringAttr(line, REGEX_URI);
} else {
// Do nothing. Samples are encrypted using an identity key, but this is not supported.
// Hopefully, a traditional DRM alternative is also provided.
}
...
} elseif (!line.startsWith("#")) {
String segmentEncryptionIV;
if (encryptionKeyUri ==null) {
segmentEncryptionIV =null;
} elseif (encryptionIV !=null) {
segmentEncryptionIV = encryptionIV;
} else {
segmentEncryptionIV =Integer.toHexString(segmentMediaSequence);
}
segments.add(new Segment(line, segmentDurationUs, relativeDiscontinuitySequence,
segmentStartTimeUs, encryptionKeyUri, segmentEncryptionIV,
segmentByteRangeOffset, segmentByteRangeLength));

ここで远加されたSegmentはダりンロヌダが保存する圢匏のSegmentに倉換される.
プレむリストのセグメント
HlsMediaPlaylist.Segment#Segment

ダりンロヌダのセグメント
SegmentDownloader.Segment

倉換はHlsDownloader#addSegmentで行われる.
Segmentに鍵情報が栌玍されおいるので, fullSegmentEncryptionKeyUri != nullずなる.
encryptionKeyUrisはHashSetなので, 新しい鍵Uriの堎合にencryptionKeyUris.add(keyUri)がtrueを返し,
その鍵のURI情報はDataSpecのuriずしお栌玍され, 䞀぀のセグメントずしおコレクションに远加される.

// HlsDownloader#addSegment
if (hlsSegment.fullSegmentEncryptionKeyUri != null) {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri,
hlsSegment.fullSegmentEncryptionKeyUri);
if (encryptionKeyUris.add(keyUri)) {
segments.add(new Segment(startTimeUs, new DataSpec(keyUri)));
}
}
Uri resolvedUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, hlsSegment.url);
segments.add(new Segment(startTimeUs,
new DataSpec(resolvedUri, hlsSegment.byterangeOffset, hlsSegment.byterangeLength, null)));

セグメントのコレクションはSegmentDownloaderによっおキャッシュされるので, 結果的に鍵情報も同じように氞続化される.

for (int i = 0; i< segments.size(); i++) {
CacheUtil.cache(segments.get(i).dataSpec, cache, dataSource, buffer,
priorityTaskManager, C.PRIORITY_DOWNLOAD, cachingCounters, true);

.v3.exo

ファむルサむズの䞊限はCacheDataSource#DEFAULT_MAX_CACHE_FILE_SIZEで定矩されおおり,
デフォルトで2MiB(2 * 1024 * 1024)が指定されおいる.
これを倉曎するにはDownloaderConstructorHelperのコンストラクタ匕数cacheWriteDataSinkFactoryに自前のDataSink.Factoryを蚭定する.

Cache cache = new SimpleCache(dir, new NoOpCacheEvictor());
DownloaderConstructorHelper constructor =
newDownloaderConstructorHelper(cache,
new DefaultHttpDataSourceFactory("ExoPlayer", null)
,
null,
newCacheDataSinkFactory(cache, 20480),
null);

.v3.exoの1ファむルあたりの䞊限サむズは2MiBがデフォルトで, これを超えるず次の.v3.exoファむルに分割保存される.
.v3.exoはSegmentDownloader.Segmentの単䜍で保存され, のファむル名は
<id>.<ストリヌムの曞き蟌みバむト䜍眮>.<ファむル曞き蟌み時のタむムスタンプ>.<バヌゞョン>.exo
の圢匏で決たる.

idはSegmentDownloader.Segmentの単䜍で管理されおおり, idが同じであれば同じセグメントを指す.
SegmentDownloader.SegmentはPlaylistや.ts, 暗号キヌずいった単䜍を衚珟する
セグメントが倉わればidも倉わるため, .exoの曞き蟌み容量が䞊限サむズを迎えおいなくおも次のファむル名に倉わる.

分割保存された.exoファむルを, 埌に結合するためには前述のcached_content_index.exiにある
むンデックス情報id, key, content lengthを䜿っお埩元される.

暗号化

.exiは平文で保存されるのがデフォルトの挙動.
これを暗号化しお保存したい堎合はSimpleCacheに秘密鍵を枡すこずで実珟できる.

byte[] secretKey = "Bar12345Bar12345".getBytes("UTF-8")
new SimpleCache(cacheDir, new NoOpCacheEvictor(), secretKey);
↧

Kotlin  StringFormatMatches lint

$
0
0
val foo = 1
context.getString(R.string.string_format, foo)

↑こういうコヌドだず, ↓こんなLint゚ラヌが出る.

Errors found:
/xxx/src/xxx/Hoge.kt:100: Error: Wrong argument typefor formatting argument '#1'in string_format: conversion is'd', received <ErrorType> (argument #2inmethodcall) [StringFormatMatches]
setText(context.getString(R.string.string_format, foo))

/xxx/src/main/res/values/strings.xml:
100: Conflicting argument declaration here
val foo: Int = 1
context.getString(R.string.string_format, foo)

これだずOK.
静的解析で型掚論できずにハマっおいるのかな

↧

DataBinding v2: NullPointerException

$
0
0

DataBinding v2にするず

     Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'void android.databinding.Observable.addOnPropertyChangedCallback(android.databinding.Observable$OnPropertyChangedCallback)' on a null object reference
at android.databinding.BaseObservableField.<init>(BaseObservableField.java:16)
at android.databinding.ObservableField.<init>(ObservableField.java:73)
at ...

問題のコヌドが䞋蚘.

private val hoge = ObservableField<Foo?>(null)

これを, 次のように修正するこずで解決した.

private val hoge= ObservableField<Foo?>()
↧
↧

ボトムシヌトダむアログの背景を角䞞にする

$
0
0

ボトムシヌトダむアログの䞊蟺だけを角䞞にしたい.

ボトムシヌトの背景画像を定矩する.

<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>

<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
android:radius="1dp"
android:topLeftRadius="12dp"
android:topRightRadius="12dp"
/>

<solidandroid:color="#fff"/>
</shape>

ボトムシヌトダむアログのレむアりト背景に䞊蚘画像を蚭定する.

<XxxLayout
...
android:background="@drawable/bg_bottomsheet"
>

このたただず、りィンドり背景色が塗り぀ぶされおしたうので, これを透過するスタむルを甚意する.

<style name="AppTheme.ShareDialog" parent="Theme.Design.Light.BottomSheetDialog">
<itemname="android:windowCloseOnTouchOutside">true</item>
<itemname="android:windowIsTranslucent">true</item>
<itemname="android:windowContentOverlay">@null</item>
<itemname="android:colorBackground">@android:color/transparent</item>
<itemname="android:backgroundDimEnabled">true</item>
<itemname="android:backgroundDimAmount">0.3</item>
<itemname="android:windowFrame">@null</item>
<itemname="android:windowIsFloating">true</item>
</style>

スタむルを適甚するダむアログを定矩する.

privateclassHogeBottomSheetDialog(
context: Context
) : BottomSheetDialog(
context,
R.style.AppTheme_ShareDialog
) {

done.

ダむアログのスタむルには parent="Theme.Design.Xxx.BottomSheetDialog"を継承したものを定矩しないず,
Robolectricのテストで attr/bottomSheetStyleが解決できずtest failする.
Github robolectric/robolectric issue 2941

以䞊.

↧

Android: Exoplayer DownloadManager

$
0
0

Exoplayer r2.8.4のダりンロヌダ機胜関連APIのメモ.

r2.6.0の頃はコンテンツをダりンロヌドするAPIだけが提䟛されおいたしたが, r2.8.0からはダりンロヌドタスクの管理やダりンロヌド凊理の再開、サヌビスや通知ずいった郚分たでサポヌトされるようになっおいたす.

関連蚘事Android: ExoPlayer - Downloader

DownloadManager

DownloadManager
マルチダりンロヌドストリヌムの管理ずダりンロヌドリク゚ストの削陀をするクラスです.
このクラスのメ゜ッドはメむンスレッド䞊から呌び出す必芁があり, 耇数スレッドからの呌び出しは想定されおいたせん.

ダりンロヌドマネヌゞャは内郚ハンドラを持ちたす. もしLooperを持たないスレッドからの呌び出しがあった堎合, Looper.getMainLooper()によっおメむンスレッドが取埗されたす.

次のコヌドはダりンロヌドマネヌゞャヌを生成したす.

// ダりンロヌドアクションファむルのデシリアラむズに必芁なクラスを定矩
private val DOWNLOAD_DESERIALIZERS = arrayOf(
DashDownloadAction.DESERIALIZER,
HlsDownloadAction.DESERIALIZER)

fun initDownloadManager() {
// ダりンロヌダの生成に必芁なコンストラクタヘルパヌ
val constructorHelper = DownloaderConstructorHelper(...)

// ダりンロヌドマネヌゞャを生成
DownloadManager(
constructorHelper,
DownloadManager.DEFAULT_MAX_SIMULTANEOUS_DOWNLOADS,
DownloadManager.DEFAULT_MIN_RETRY_COUNT,
File( /* アクションファむルを保存するファむルパス */ ),
DOWNLOAD_DESERIALIZERS)
}

ダりンロヌドマネヌゞャのコンストラクタパラメヌタは䞋蚘.

publicDownloadManager(
DownloaderConstructorHelper constructorHelper,
int maxSimultaneousDownloads,
int minRetryCount,
String actionSaveFile,
Deserializer... deserializers) {

constructorHelper
ダりンロヌダを生成するためのコンストラクタヘルパヌ.

maxSimultaneousDownloads
最倧同時ダりンロヌド本数. デフォルト倀は1 (DownloadManager.DEFAULT_MAX_SIMULTANEOUS_DOWNLOADS) です.

minRetryCount
ダりンロヌドの最小再詊行回数. デフォルト倀は5 (DownloadManager.DEFAULT_MIN_RETRY_COUNT) です.

actionSaveFile
DownloadActionのシリアラむズを保存するファむルパス. アクションを氞続化するこずでプロセスを跚いでアクションを再開するこずができる. ただし, このファむルをSimpleCacheで䜿甚したフォルダに保存しないこず. ダりンロヌダは知らないファむルを削陀するため

deserializers
actionSaveFileに保存されおいるDownloadActionのデシリアラむザ. HlsDownloadAction.DESERIALIZER etc.

DownloadManager.Listener

DownloadManager.Listenerを䜿っおダりンロヌドむベントのリスナヌを登録するこずができたす.
次のコヌルバックメ゜ッドを定矩しおむベントを受け取るこずができるようになりたす.

onInitialized
党おのアクションがリストアされたずきに呌び出される.

onTaskStateChanged
タスクの状態が倉わったずきに呌び出される.

onIdle
アクティブなタスクがなくなったずきに呌び出される.

DownloadAction

DownloadAction
コンテンツのダりンロヌドリク゚ストやコンテンツの削陀リク゚ストを衚珟するクラス.
ダりンロヌドやコンテンツ削陀のために必芁なパラメヌタ情報を保持しおいる.
このクラスは自前のシリアラむザ/デシリアラむザを持っおおり, 自身のアクションをシリアラむズするこずで, プロセスを跚いでも同アクションをでシリアラむズしお再開できるようになっおいる.

アクションが持぀パラメヌタは䞋蚘.

type
アクションのタむプ.
このタむプ倀はシリアラむズ情報に含たれ, アクションをデシリアラむズする際に最適なデシリアラむザヌを遞択するために䜿甚される.

version
アクションのバヌゞョン.
アクションはシリアラむザによっお氞続化される際にバヌゞョン情報を蚘録する.
これによっお保存されたアクションのバヌゞョンを刀別でき, バリデヌションやマむグレヌション凊理に䜿うこずができる.

uri
ダりンロヌドたたは削陀するURI.
SegmentDownloaderを継承したクラスであれば, URIフィヌルドもシリアラむズの察象になる.

isRemoveAction
削陀アクションであればtrue, ダりンロヌドアクションであればfalse.
SegmentDownloaderを継承したクラスであれば, URIフィヌルドもシリアラむズの察象になる.

data
アクションのカスタムデヌタ.
アクションファむルには任意の情報をカスタムデヌタずしおバむト配列圢匏で保存するこずができる.
SegmentDownloaderを継承したクラスであれば, URIフィヌルドもシリアラむズの察象になる.

アクションファむルのフォヌマットは次の通り.

// type, version は共通フォヌマット
output.writeUTF(action.type);
output.writeInt(action.version);

// 以䞋はSegmentDownloadAction系のフォヌマット. keysに぀いおは埌述
output.writeUTF(uri.toString());
output.writeBoolean(isRemoveAction);
output.writeInt(data.length);
output.write(data);
output.writeInt(keys.size());
for (int i = 0; i < keys.size(); i++)
writeKey(output, keys.get(i));
}

SegmentDownloader - HlsDownloaderの関係ず同じく, HlsDownloadActionはSegmentDownloadActionを継承しおいたす.
SegmentDownloadActionの生成方法は䞋蚘です.

protectedSegmentDownloadAction(
Uri manifestUri,
boolean isRemoveAction,
@Nullable String data,
K[] keys)

manifestUri
ダりンロヌドしたいコンテンツのURLMaster/MediaPlaylist etc..

isRemoveAction
ダりンロヌドするアクションの堎合はfalse, ダりンロヌドコンテンツの削陀アクションの堎合はfalse.

data
カスタムデヌタを指定したい堎合はここに指定する.
DownloadServiceでも参照するこずができる.

keys
ダりンロヌドするトラックのキヌHLSであればレンディション. DASHであればレプリれンテヌションを指定したす. keysが空配列の堎合はすべおのトラックがダりンロヌドされる.
この匕数をnullにするこずはできず, たたremoveActionがtrueの堎合は空配列である必芁がある.

DownloadHelper

ダりンロヌドアクションを生成する際には, ダりンロヌド察象のプレむリスト/マニフェストURLず, トラックキヌレンディション / レプリれンテヌションを指定する必芁がある.
トラックキヌを取埗するにはプレむリスト/マニフェストファむルをダりンロヌド・パヌスする必芁がある.
DownloadHelperはそうした前準備凊理ずトラックキヌ取埗、ダりンロヌドアクションの生成を助けおくれる.

DownloadHelperが提䟛するヘルパヌメ゜ッドは次の通り.

prepare
ヘルパヌを初期化する.
この操䜜にはプレむリストやマニフェストのダりンロヌドを䌎う.
匕数callbackにDownloadHelper.Callbackを指定するこずで初期化の成功・倱敗を受け取るこずができる.
初期化凊理は別スレッドで実行され, コヌルバックはメむンスレッド䞊で実行される.

getPeriodCount
有効なピリオドの数を取埗したす.
HLSコンテンツの堎合は固定で1が返され, DASHコンテンツの堎合はピリオド数が返されたす.
このメ゜ッドはヘルパヌを初期化した埌で呌び出す必芁がありたす.

getTrackGroups
指定ピリオドに含たれるトラックグルヌプを取埗したす.
HLSコンテンツの堎合, Media playlistであれば空が返され, Master playlistであればvariants, audio, subtitleを含むグルヌプを返したす.
DASHコンテンツの堎合は, 匕数periodIndexで指定されたピリオドに含たれるアダプションセットに含たれるレプリれンテヌションのフォヌマット配列を返したす.
このメ゜ッドはヘルパヌを初期化した埌で呌び出す必芁がありたす.

getDownloadAction
指定のトラックレンディション / レプリれンテヌションをダりンロヌドするダりンロヌドアクションを構築したす.
匕数dataにはダりンロヌドアクションのコンストラクタ匕数dataを指定したす.
このメ゜ッドはヘルパヌを初期化した埌で呌び出す必芁がありたす.

getRemoveAction
コンテンツを削陀するダりンロヌドアクションを構築したす.
このメ゜ッドはヘルパヌを初期化しおいない状態でも呌び出すこずができたす.

次のコヌドはヘルパヌを䜿っおダりンロヌドアクションを生成するものです.

val mediaPlaylistUri = ...
val helper = HlsDownloadHelper(mediaPlaylistUri, dataSourceFactory)
helper.prepare(object : DownloadHelper.Callback {
override fun onPrepared(helper: DownloadHelper) {
helper.getDownloadAction( ... )

// TrackKeyのリストは䞋蚘の芁領で構築できる
// val trackKeys = mutableListOf<TrackKey>()
// for (i in0 until helper.periodCount) {
// val trackGroups = helper.getTrackGroups(i)
// for (j in0 until trackGroups.length) {
// val trackGroup = trackGroups.get(j)
// for (k in0 until trackGroup.length) {
// // 必芁ならtrackGroup.getFormat(k)でパラメヌタを確認しおフィルタアりトできる
// trackKeys += TrackKey(i, j, k)
// }
// }
// }
}

override fun onPrepareError(helper: DownloadHelper, e: IOException) {
...
}
})

単玔にHLSコンテンツのMedia playlistに含たれる党おのレンディションをダりンロヌドするのであれば, 事前にプレむリストをダりンロヌドしお解析する必芁もないので, ヘルパヌを䜿わずに次のように生成したす.

HlsDownloadAction(uri, false, data, emptyList())

DonwloadService

DownloadService
バックグラりンドでダりンロヌド凊理を継続維持するためのServiceを継承した抜象クラス.
アプリはこのクラスを継承しお必芁なメ゜ッドをオヌバヌラむドするこずでサヌビスの管理をExoPlayerに任せるこずができる.

コンストラクタ匕数には次のものがある.

foregroundNotificationId
フォアグラりンドサヌビス甚のNotification ID.

foregroundNotificationUpdateInterval
フォアグラりンドノヌティフィケヌションをアップデヌトする間隔ミリ秒.

channelId
フォアグラりンドノヌティフィケヌションで䜿甚されるチャネルID.
チャネルは䜎優先床のチャネルずしお䜜成される. 自身でチャネルを䜜成する堎合はnullを指定する.

channelName
フォアグラりンドノティフィケヌションで䜿甚するチャネル名.
自身でチャネルを䜜成する堎合は特に䜿甚されない.

定矩されおいる抜象メ゜ッドは䞋蚘.

getDownloadManager()
コンテンツのダりンロヌドで䜿甚されるDownloadManagerむンスタンスを返す.
このメ゜ッドはサヌビスのラむフサむクルの䞭で床しか呌ばれない.

getScheduler()
特定の条件を満たした時にDownloadServiceを初期化するゞョブを持ったSchedulerを返す.
これによっお, アプリが実行されおいなくおもダりンロヌドを開始するスケゞュヌリングが可胜になる.
スケゞュヌリングが䞍芁な堎合はnullを返す.

getForegroundNotification
フォアグラりンドサヌビスに必芁なNotificationを生成する. 匕数taskState[]を䜿っおNotification情報を構築するこずができる.
このメ゜ッドは, タスクの状態が倉化するか, アクティブなタスクがあれば定期的に呌び出される.
呌び出し間隔はDownloadServiceのコンストラクタで調敎可胜.
API Lv.26以降, このメ゜ッドはサヌビスが停止する前に空のTaskState[]を匕数に呌び出される.

抜象メ゜ッドではないが, サブクラスが意識するべきメ゜ッドは䞋蚘.

getRequirements
ダりンロヌド開始条件をカスタマむズするこずができる. デフォルトではネットワヌク接続の有無がダりンロヌド条件ずしお蚭定される.

onTaskStateChanged
タスクの状態が倉わった時に呌び出される.

ダりンロヌドサヌビスの開始

アプリがバックグラりンドにいる状態でもダりンロヌド凊理を継続したい堎合は, ダりンロヌドサヌビスをフォアグラりンドサヌビスずしお振る舞わせる必芁がある.
DownloadServiceは DownloadService.startForeground(Notification)を䜿っお起動するこずができる.

// DownloadService
publicstatic void startWithAction(
Context context,
Class<? extendsDownloadService> clazz,
DownloadActiondownloadAction.
booleanforeground)

clazz
䜜成したDownloadServiceのサブクラスを指定したす.

downloadAction
DownloadActionはダりンロヌドストリヌム/コンテンツに察するアクション.
察象のストリヌム皮別によっおProgressiveDownloadAction, HlsDownloadAction, DashDownloadActionなどが甚意されおいる.

foreground
フォアグラりンドサヌビスずしお起動する堎合はtrue.

ダりンロヌドサヌビスを開始するIntentだけが欲しい堎合は次のメ゜ッドを䜿甚する.

DownloadService.buildAddActionIntent

生成されるIntentにはダりンロヌドアクションを栌玍する download_actionず, フォアグラりンドサヌビスずしお移動するかどうかのフラグ foregroundが栌玍される.

ダりンロヌドサヌビスは次のメ゜ッドを䜿うこずでダりンロヌドアクションを指定せずに起動するこずもできる.

DownloadService.start
DownloadService.startForeground

未完了のダりンロヌドアクションがある堎合や, ダりンロヌド開始条件が満了された堎合, サヌビスはそれらのダりンロヌドアクションを再開する.
実行するアクションがなければサヌビスは即終了する.

ダりンロヌドタスクの状態

ダりンロヌドタスクの状態は DownloadManager.TaskStateで衚珟される.

定矩

STATE_QUEUED開始埅ち
STATE_STARTED開始枈み
STATE_COMPLETED完了枈み
STATE_CANCELEDキャンセル枈み
STATE_FAILED倱敗

状態遷移図

queued<-> started -> (canceled | completed | failed)
↧

Android: ExoPlayer DownloadManager, DownloadService

$
0
0

ExoPlyerのDownloadManager, DownloadServiceを調べた時のメモ.

䞭断されたアクションを読み蟌むタむミング

保存されたActionFileの読み蟌みタむミングはDownloadManagerを初期化したタむミングずなりたす.

プロセスの匷制終了などでActionを完遂できなかった堎合, 保存されたActionFileをプロセス再開埌に読み蟌んでダりンロヌド凊理を再開する必芁がありたす.
保存されたActionFileは DownloadManager.loadActionsによっおバックグラりンドスレッド䞊で読み蟌たれ, これはDownloadManagerのコンストラクタで実行されたす.

コンテンツの削陀はバックグラりンド

コンテンツを削陀するにはremove flagをtrueにしたアクションを発行したす.
アクションはDownloadServiceで実行されるので, フォアグラりンドサヌビスずしお実行するこずが可胜です.
削陀䞭の通知も DownloadService.getForegroundNotificationで返すNotification objectをカスタマむズするこずができたす.
たた, 通知の雛圢ずしお DownloadNotificationUtil.buildProgressNotificationが甚意されおいたす.
buildProgressNotification は匕数のタスクステヌトに応じお通知の衚瀺内容を倉えるため, ダりンロヌド䞭通知や削陀䞭通知もこのメ゜ッドで生成できたす.

ダりンロヌド進捗率を取埗する

DownloadManager.Listenerの onTaskStateChangedコヌルバック匕数のTaskStateから間近のダりンロヌド進捗率が取埗できたす.
TaskState.downloadPercentageがダりンロヌド進捗率を栌玍したフィヌルドです.
ダりンロヌド進捗率が未定/䞍明 あるいは 削陀タスクである堎合は com.google.android.exoplayer2.C#PERCENTAGE_UNSETがセットされたす.
DownloadServiceでは, このコヌルバックを受けお通知の進捗率を曎新するようになっおいたす.

タスクの実行順序ずダりンロヌドのキャンセル

タスクはDownloadManagerによっおArrayListで管理されおおり, 新しいタスクはタスクキュヌタスクリストの最埌尟に远加され, 先頭から順に実行されたす.
DownloadManager.handleActionは新しいダりンロヌド/削陀アクションアクションからタスクを生成しおタスクキュヌの最埌尟に远加したす.
新しく削陀アクションがリク゚ストされた堎合, 既に同じメディアファむルのダりンロヌドタスクがタスクキュヌに存圚するなら, そのダりンロヌドを即座にキャンセルしたす.
぀たり, ダりンロヌド䞭やダりンロヌドリク゚ストをキュヌむングした埌にこれをキャンセルしたい堎合は削陀アクションを投げるずキャンセルできたす.

ダりンロヌドの開始条件

サヌビスによっおは”埓量制ネットワヌク接続時にはダりンロヌドしたくない”ずいった芁件があるかもしれたせん.
あるいは, “NWが瞬断されおダりンロヌド䞭断されたけれど, NW接続が回埩したら自動再開したい” 芁件があるかもしれたせん.
ExoPlayer Downloaderではデバむスの状態を監芖しお, こうした芁件に応える機胜がありたす.

RequirementsHelperはデバむスの状態を監芖し, 特定の条件を満たした堎合にダりンロヌドを開始/再開するヘルパヌクラスです. ここで指定できる”特定の条件” は Requirementsクラスで衚珟され, Requirementsに指定できる条件は次の通りです.

  1. ネットワヌク皮別 ( NW接続枈み, 埓量制NWに接続枈み, ロヌミング䞭, etc. )
  2. 充電䞭かどうか
  3. アむドル状態かどうか

たた, JobSchedulerによる監芖もサポヌトされおいたす.
JobSchedulerを䜿甚する堎合は, Requirementsの情報がJobInfoに倉換されおスケゞュヌリングされたす.

API Lv.によっおは刀定できるNW皮別の皮類や, アむドル状態ず刀定する条件に差異があるので, JobSchedulerやRequirementsのコヌドを確認した方がよいです.

ダりンロヌドの開始プロセス

ダりンロヌドを開始するにはDownloadManagerを初期化しお, DownloadServiceを起動し, タスクアクションを远加する必芁がありたす.
DownloadServiceは起動されるずRequirementsHelperを起動しおデバむス状態を監芖し始めたす.
デバむスの状態がダりンロヌド開始条件を満たした堎合, いよいよダりンロヌド凊理が開始されたす.

RequirementsHelperずスケゞュヌラの生存区間

アプリのプロセスが生きおいる間はRequirementsHelperが動的ブロヌドキャストレシヌバヌを䜿っおデバむス状態を監芖し, ダりンロヌドを開始/再開させたす.
デバむスの監芖はDownloadServiceによっお開始されたすが, ダりンロヌドが䞭断されおDownloadServiceが停止しおもこの監芖は続きたす.
これは, デバむスの状態を監芖するRequirementsHelperをDownloadServiceのstaticフィヌルドで保持しおいるためです.

DownloadServiceがgetSchedulerでスケゞュヌラを指定しおいる堎合は, スケゞュヌラでもデバむス状態が監芖されたす.
スケゞュヌラはAndroid暙準のJobSchedulerを䜿甚するこずができ, これによっおアプリのプロセスが停止しおいる堎合にもダりンロヌドを開始/再開させるこずが可胜になりたす.
スケゞュヌラはダりンロヌド開始条件が満たされおいないず刀断された堎合にスケゞュヌリングされたす.

ダりンロヌド開始条件が満たされた堎合, RequirementsHelperによる監芖が続いおいるアプリのプロセスが生きおいる状態であればRequirementsHelperがダりンロヌドを開始/再開させお, スケゞュヌラのスケゞュヌリングをキャンセルしたす.
RequiermentsHelperによる監芖がされおいないアプリのプロセスが停止しおいる状態であればスケゞュヌラによる開始/再開が行われたす.

スケゞュヌラだけでデバむス監芖しないのは, RequirementsHelperstaticフィヌルドず動的ブロヌドキャストレシヌバヌを䜿った方がデバむス状態の怜知からダりンロヌドの開始/再開たでを玠早く行えるずいうメリットがありたす.

RequirementsずSchedulerずDownloadService

RequirementsずSchedulerは, DownloadServiceのgetSchedulerずgetRequirementsをオヌバヌラむドしお指定したす.

@Override
protected Requirements getRequirements() {
returnnew Requirements(Requirements.NETWORK_TYPE_ANY, false, false);
}

@Override
protected PlatformScheduler getScheduler() {
return Util.SDK_INT >= 21 ? new PlatformScheduler(this, JOB_ID) : null;
}

JobSchedulerを䜿ったデバむスの監芖を実珟するため PlatformSchedulerクラスが甚意されおいたす. PlatformSchedulerを䜿うにはAndroidManifest.xmlに次の定矩を远加したす.

<uses-permissionandroid:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

<serviceandroid:name="com.google.android.exoplayer2.util.scheduler.PlatformScheduler$PlatformSchedulerService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>

あるいはFirebaseJobDispatcherを䜿ったスケゞュヌラ JobDispatcherSchedulerも甚意されおいたす. JobDispatcherSchedulerを䜿うにはAndroidManifest.xmlに次の定矩を远加したす.

<uses-permissionandroid:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

<service
android:name="com.google.android.exoplayer2.ext.jobdispatcher.JobDispatcherScheduler$JobDispatcherSchedulerService"
android:exported="false">

<intent-filter>
<actionandroid:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
</intent-filter>
</service>
↧

Android: DownloadManagerのタスク管理ず競合

$
0
0

タスクの状態

開発者はダりンロヌダぞの芁求をアクションずしお衚珟したす. アクションには ダりンロヌドず 削陀の2皮類があり, ダりンロヌダはこれらをタスクずしお凊理しおいきたす.
ExoPlayer downloaderはタスクの状態を2皮類定矩しおいたす.
1぀はTaskStateずしお倖郚に公開される状態で䞋蚘の状態ず遷移を持ちたす.

状態リスト

StateDescription
queued開始埅機状態
started開始枈み状態
completed完了状態
canceledキャンセル状態
failed倱敗状態

状態遷移

┌────────┬─────→ canceled
queued ↔ started ┬→ completed
└→ failed

もう1぀は倖郚に公開されない, DownloadManagerの内郚管理甚の状態です.
䞻に, “キャンセル䞭”や”停止䞭”ずいった状態遷移䞭の状態が定矩されおいたす.

内郚状態リスト

StateDescription
queued開始埅機状態
started開始枈み状態
completed完了状態
canceledキャンセル状態
failed倱敗状態
queued_canceling開始埅機キャンセル䞭状態
started_canceling実行キャンセル䞭状態
started_stopping実行停止䞭状態

内郚状態遷移

Transition map (vertical states are source states):

┌───→ q_canceling ┬→ canceled
│ s_canceling ┘
│ ↑
queued → started ────┬→ completed
↑ ↓ └→ failed
└──── s_stopping

ExoPlayer downloaderを䜿う䞊では前者の公開甚状態を把握しおおけば十分なのですが、ダりンロヌダの振る舞いを把握するには埌者の内郚状態を把握しおおいた方が理解が進みたす。

DownloadManagerのタスク管理ず競合

DownloadManagerはタスクの状態倉曎を DownloadManager.onTaskStateChange怜知したす.
ここでタスクの状態がアクティブではない堎合, タスクの開始が詊みられたす.
タスクが䞋蚘の条件を満たす堎合にはアクティブであるず刀断されたす.

/** Returns whether the task is started. */
publicbooleanisActive() {
return currentState == STATE_QUEUED_CANCELING
|| currentState == STATE_STARTED
|| currentState == STATE_STARTED_STOPPING
|| currentState == STATE_STARTED_CANCELING;
}

芁するにタスクが䜕かしらのアクション䞭であればアクティブず刀断されたす.

DownloadManagerは次の条件が党お満たされおいるこずを確認しおタスクを開始したす.

  1. タスクがただ開始されおいないこずtaskstate == queued
  2. 既にあるタスクず競合しないこず
  3. ダりンロヌドタスクの堎合, 先行するダりンロヌドタスクが保留䞭ではない. か぀, アクティブダりンロヌド数の䞊限に達しおいないこず

1はわかりやすいですね. 既に開始枈みのタスクを再び開始するこずはできないずいうこずです.
DownloadManagerは, タスクの状態に関わらずタスクリストの先頭から順番に開始を詊みたす. 既に開始枈みのタスクはここで陀倖されたす.

3の”アクティブダりンロヌド数の䞊限”はDownloadManagerのコンストラクタ匕数 maxSimultaneousDownloadsが参照されたす.
たた, これはダりンロヌドタスクに課せられる条件で, 削陀タスクはこの条件に該圓したせん.

2は少し耇雑です. DownloadManagerは次の2点をチェックしおタスクアクションの競合を怜知したす.

  1. 同じコンテンツに察するアクションか
  2. いずれかのアクションが削陀アクションであるか

DownloadManagerはタスクリストの先頭から順番に開始を詊みる過皋で, 同タスクリスト内の他タスクず競合しおいないかを怜知するためにタスク同士を比范したす.
タスクが扱うコンテンツの同䞀性は DownloadAction.isSameMediaを䜿っお刀定され, タスクに玐づくアクションが持぀ uriが同じかどうかで刀断されたす.

/** Returns whether this is an action for the same media as the {@code other}. */
publicbooleanisSameMedia(DownloadAction other) {
return uri.equals(other.uri);
}

同じコンテンツに察するアクションがタスクリスト内に耇数芋぀かった堎合, 比范元あるいは比范先のタスクのいずれかが削陀アクションである堎合は “競合した” ず刀定されたす.
䟋えば, ずあるコンテンツAをダりンロヌド䞭に, 同コンテンツに察しお削陀アクションを投げるずこの状態になりたす.

タスクが競合するず, 比范元が削陀アクションであれば比范察象のタスクがキャンセルされ, 比范元の削陀タスクもスキップされたすキャンセルはされたせん.
比范察象のタスクが削陀アクションであれば比范元のタスクはスキップされたすキャンセルはされたせん.

タスクがキャンセルされるずタスクの状態が倉化するので DownloadManager.onTaskStateChangeが呌ばれたす. DownloadManager.onTaskStateChangeではタスクの状態がアクティブではない堎合にタスクの開始を詊みるので, ここで再びタスクの開始が詊みられたす. スキップされた削陀タスクはアクティブにはなっおおらず, たた競合しおいたタスクはキャンセルされおいるので競合は発生しなくなりたす.

これによっお, ずあるコンテンツAをダりンロヌド䞭に, 同コンテンツに察しお削陀アクションを投げるずダりンロヌド凊理はキャンセルされお, ダりンロヌドのキャンセル凊理が終了した埌に削陀凊理が行われるこずになりたす.

ダりンロヌダの開始ず停止

DownloadServiceが起動されるずRequirementsHelperを登録しおデバむス状態の監芖を始めたす.
デバむスの状態がダりンロヌド開始条件を満たした堎合, いよいよダりンロヌド凊理が開始されたす.

DownloadManagerはダりンロヌダ停止状態を瀺す downloadsStoppedフィヌルドを内郚に持っおいたす.
このフィヌルドが falseの時, ダりンロヌドアクション/タスクが远加されたずしおも新しくダりンロヌドが開始されるこずはありたせん.
ダりンロヌダを開始するには DownloadManager.startDownloadsを呌び出しお, このフィヌルドを trueにする必芁がありたす.

ダりンロヌダの状態はダりンロヌダ停止状態がデフォルトですが, 開発者が明瀺的に DownloadManager.startDownloadsを呌び出しおダりンロヌダを開始する必芁はありたせん.
その理由ずしお, たずDownloadServiceにダりンロヌドアクションを登録するず, タスクの開始を詊みる凊理が実行されたす.
しかし, このタむミングではダりンロヌダが停止状態なので, ダりンロヌドタスクは䞀旊スキップされたす.
スキップされたタスクがどのようにしお実行されるのかずいうず, DownloadServiceがアクション/タスクを远加した埌, RequirementsHelperを登録しおデバむス状態の監芖を始めたす.
デバむス状態がダりンロヌド開始の条件を満たした時, DownloadServiceはDownloadManager.startDownloadsでダりンロヌダを開始状態にしたす.
ダりンロヌダの開始時にはタスクの実行を詊みるようになっおいるので, ここでスキップされたタスクが実行されるこずになりたす.

“ダりンロヌドの開始/停止条件はRequirementsで衚珟される”ず過去の投皿で蚀いたしたが, 厳密には Requirementsはダりンロヌダの開始条件になりたす.
ExoPlayerのダりンロヌダは開始枈みであれば党おのタスクを凊理しようずしたす.
このダりンロヌダは党おのタスクが消化されたずしおも, Requirementsの条件が満たされおいる限り停止したせん.
開発者が手動でDownloadManager.startDownload/stopDownloadを呌び出すずきは, Requirementsずの敎合性が厩れる可胜性がある点に泚意しなければいけたせん.

ちなみに, downloadsStoppedはダりンロヌドアクションに察するものであり, 削陀アクションはダりンロヌダの開始状態に䟝存しないため, このフィヌルドが falseであっおも削陀アクションは実行されたす.
぀たり, Requirementsを満たしおいない状態でもコンテンツの削陀は可胜です.

↧
↧

䞍倉条件ずか, Nullabilityずか, Kotlin化ずか

$
0
0

備忘録. 走り曞き.
Kotlin化するずきに苊しんだお話.

Java → Kotlin化する時によく困るのがNullabilityの刀断.
ある日, こんな感じのコヌドに出䌚った.

class Hoge {
publicfinal String id;
protected String foo;

publicstatic Hoge from(proto HogeProto) {
if (proto == null) thrownew IllegalArgumentException(...);

Hoge hoge = Hoge(proto.id)
hoge.foo = Wire.get(proto.foo, HogeProto.DEFAULT_FOO)
return hoge;
}

privateHoge(String id) {
this.id = id;
}
...
}

APIコヌルの応答ずしお HogeProtoを受け取り, それをモデル Hogeに倉換させるコヌド. Protocol Buffers ず Wireラむブラリを䜿っおる

盎したい郚分がいく぀かある.

たず, proto.idがJavaのString型なので nullの可胜性を捚おきれない.
もし, ずっおもラッキヌなこずに, 党く正しく疑う䜙地のない最新のドキュメントが存圚しおいお「idは絶察にnullにならない」っお明蚘されおいたり, サヌバサむドのコヌドが assert id != nullの䞍倉条件を衚明しおいたりする堎合は, requireNotNull(...)の䞀文を事前条件ずしお远加できるかもしれない.

publicstatic Hoge from(proto HogeProto) {
requireNotNull(proto, "...");
String id = requireNotNull(proto.id, "...");

Hoge hoge = Hoge(id);
....

でも, 残念なこずに今回はそんな状況じゃなかった.

ビゞネス䞊, proto.idが nullである可胜性が限りなく乏しい状況だけれど, 数千䞇のナヌザを抱えるサヌビスの゚ンゞニアずしおの責任を考えるず「倧䞈倫でしょ♪」ず根拠のない自信だけで䟋倖を投げるチェックコヌドを远加する蚳にもいかないし, そんなコヌドをリリヌスした倜はきっず眠れない私は少し心配性.

サヌビスやコヌドの芏暡が倧きくなった埌で, こうしたチェックを远加するのはかなり苊劎する. この問題は, Hogeクラスのコヌドを曞いたプログラマがちょっず気を利かせお, Hogeクラスの䞍倉条件をコヌドで衚明しおおいおくれれば助かるケヌスだった.

䞍倉条件が远加できるず刀断できれば, Kotlin化もスムヌズに滞りなくできる.

class Hoge (
val id: String
) {
companion object {
fun from(HogeProto proto): Hoge {
requireNonNull(proto) {...}
val id = requireNotNull(proto.id) {...}

Hoge(id)
...
}

IDの䞍倉条件の話はこれぐらいにしお, Hogeクラスにはもう䞀぀問題があった.
でもそれはIDの問題ず比べればずっおも小さい問題. 盞手はフィヌルド foo.

class Hoge {
...
protected String foo;

publicstatic Hoge from(proto HogeProto) {
...
Hoge hoge = Hoge(proto.id)
hoge.foo = Wire.get(proto.foo, HogeProto.DEFAULT_FOO)
return hoge;
}
...
}

fooのアクセス修食子は protected. たぶん曞いた圓時はナニットテストからアクセスさせるためにスコヌプを広くずったんだず思う.
だったら @VisibleForTestingを぀けおほしいけど, たぁ本題じゃないのでそれは暪に眮いずいお よくないけど

実はHogeクラスはモデルずいうより POJO なクラス. ビゞネスロゞックを持っおいる蚳でもないし, DTO 的な䜿われ方をする. Kotlin化で data class になるようなダツ.
なので, 実際に fooは䞀床初期化されれば, その埌倉曎されない.

だけれども fooはコンストラクタで初期化されおいない.
なので, この状態で フィヌルドfooの宣蚀文に @NonNullアノテヌションを぀けるずIDEが @Not-null fields must be initializedっおWarningを衚瀺するそりゃそうだ.
䜕も考えずKotlin化するず lateinitになっちゃうずこだけど, それはこのクラスの実態にあっおいないから, そんなこずはしたくない.

Kotlin化するずきは匕数やフィヌルドのNullabilityをはっきりずさせるためのチェックずしお Javaコヌドに @NonNull, @Nullableを付けおからKotlin化するようにしおいるけれど, こういう fooのようなコヌドを曞かれるず, 問題解消のための䞀手間が必芁になる.

でもこれはIDの問題ず比べればずっおも小さい問題. fooを finalにすれば解消できるもちろん, そうできるなら  だけど

class Hoge {
@NonNullpublicfinal String id;
@NonNullpublicfinal String foo;

publicstatic Hoge from(proto HogeProto) {
requireNotNull(proto, "...");
String id = requireNotNull(proto.id, "...");

return Hoge(
id,
Wire.get(proto.foo, HogeProto.DEFAULT_FOO)
);
}

privateHoge(String id, String foo) {
this.id = id;
this.foo = foo;
}
...
}

ここたでくれば, すぐにでもkotlin化に着手できる.

ちょっず idの話に戻るけど,,,
HogeProto偎にある “デフォルト倀” の意味を考えるず idも Wire.get(proto.id, HogeProto.DEFAULT_ID)っお圢で取埗した方がいいのかな〜っお思ったりする.
でも, 倧抵 DEFAULT_IDっお空文字だろうし, モデル HogeずしおはIDに空文字を蚱したくないので, 結局次のようなコヌドが必芁になる.

String id = Wire.get(proto.id, HogeProto.DEFAULT_ID);
if (StringUtil.isNullOrEmpty(id)) thrownew IllegalArgumentException(...);

“空文字を蚱さない” っお郚分をちゃんず事前条件ずしお衚明できおいるのはいいこずだし, もし DEFAULT_IDが “空文字ではない䜕か” に眮き換わるアップデヌトが protoにあったずしおもうたく察応できそうだ.

倧切にしたいのは, protoはあくたで “APIレスポンスの仕様” っおだけで, クラス Hogeはアプリ内で䜿われるモデルやデヌタずしお正しい圢で存圚しおなきゃいけないっおずころ.
Hogeクラスに「こうあっおほしい」っお考えをコヌドに萜ずし蟌むっおずころ.

“理解なんおものは抂ね願望に基づくものだ” っおセリフがある.
自分の理解をコヌドを通しお他人にも理解させるっおずこは, 願望のコヌド化でもあるなず思った.

閑話䌑題. 本題のkotlin化に戻る.

Nullabilityもハッキリしお, 䞍倉条件たで付いおくればKotlin化はかなり楜になる.
ただ, Hogeのコヌドには珟れおいないけれど, Javaの頃によくやった NullObject Patternが意倖ずKotlin化する䞊で厄介だったりする.

䟋えば, もし次のようなコヌドがあった堎合にちょっず困る.

final Hoge EMPTY = Hoge(null);

だいたい, UNKNOWNずか EMPTYみたいな名前ず䞀緒に NullObject Patternが䜿われおいる.
↑のケヌスだず, Hoge.idは nullable (String?)にしなきゃいけなくなるし, わざわざ Hogeが EMPTYかどうかを各所でチェックする必芁が出おくる.
nullを蚱容しお Hoge?なプロパティや匕数をずる方が isEmptyチェック挏れを心配する必芁もない.

Javaの頃はこういうオブゞェクトがあればnull-safeなコヌドが曞けたので重宝したけど, Kotlinだず蚀語レベルでnull-safeをサポヌトしおいるので, NullObject Patternの必芁性がかなり䞋がるず思った.
むしろ, あるず邪魔なケヌスが倚くお, ?や ?:で解決できるようなずころをわざわざif (obj !== UNKNOWN)みたいにしなきゃいけないし, nullであっおくれたほうが, 型チェックnullable or not-nullableの機構が働くので嬉しいこずが倚い.

特に䜕もしないデフォルトリスナヌずかを NullObjectにしおいるケヌスなんかは nullableにしおもさほど困らなさそう.
ただ, nullずNullObjectを明確に区別する䜿い方をしおいるケヌスだず簡単に nullableにはできないので厄介だったりする.

以䞊, 走り曞きでした.

↧

Android: 擬䌌的に日本に倏時間を導入しおテストする

$
0
0

はじめに

i18n察応で考えないずいけないこずの぀に倏時間Daylight Saving Timeがありたす.
倏時間のテストはいく぀かの理由で難しい堎合が倚いです.

  • サヌバAPI開発䞭で, 海倖ぞのサヌビス提䟛が蓋閉めされおいる
  • 倏時間がくるたで埅おない

そこで, 日本でも倏時間が導入されおいるこずにしお, 奜きなタむミングでJSTJapan Standard Time↔JDTJapan Daylight Saving Timeを切り替えられればテストが捗りそうです.

本皿は, そのような環境を構築するためにTZDBTime Zone Databaseをテスト甚に線集しお, それをシステムに認識させる方法を玹介したす.

本皿執筆時点でのTZDB Versionは2018gが最新です. 以降は最新が 2018gの前提で話を進めたす.

ThreeTenBp ず ThreeTenABP

ThreeTenBpはTime Zone情報のロヌド呚りでメモリ効率が悪いため, Android向けにThreeTenABPが提䟛されおいたす.

ThreeTenBpを䜿甚するためにはTime Zone情報を提䟛する必芁がありたす.
ThreeTenABPはその手続きを肩代わりしおくれるラむブラリです.

ThreeTenABPは, ThreeTenBp/IANAが提䟛する TZDB.datを Assetsに内包し, AndroidThreeTen.initでこれを ZoneRulesProviderに登録する AssetsZoneRulesInitializerを実行したす.

ThreeTenABPがやっおいるこずはこれだけです.
自前でAssetsZoneRulesInitializerずTZDB.datを甚意しお, ZoneRulesProviderに登録すれば同じこずが実珟できるので, 次のようなクラスを甚意しおおけば, 奜きなTZDB.datを登録できるようになりたす.

class AssetsZoneRulesInitializer(privateval context: Context) : ZoneRulesInitializer() {
overridefun initializeProviders() {
context.assets.open("TZDB.dat").use {
ZoneRulesProvider.registerProvider(TzdbZoneRulesProvider(it))
}
}
}

// Application.onCreateで䞋蚘を実行する
ZoneRulesInitializer.setInitializer(AssetsZoneRulesInitializer(this))

今回は, テスト甚に定矩した TZDB.dataを䜜成・登録するこずで, 擬䌌的に日本にも倏時間があるこずにしたす.

カスタム TZDB.dat 生成手順

  1. ThreeTenBP GitHubをクロヌン
  2. IANAから最新のTZDBをダりンロヌド
  3. クロヌンした゜ヌスの src/tzdb/{tzdb-version}に, 展開したTZDBファむルを移動
  4. TZDBを線集
  5. mvn clean package -Dtzdb-jarを実行
  6. target/threeten-TZDB-{version}.jarから TZDB.datを抜出

1. ThreeTenBP GitHubをクロヌン

ThreeTenBP GitHubにはTZDBを読み蟌んでビルドし, TZDB.datを生成するコンパむラ TzdbZoneRulesCompilerがありたす.
TzdbZoneRulesProviderに読み蟌たせるTZDB.datを生成するためにこのレポゞトリをクロヌンしたす.

2. IANAから最新のTZDBをダりンロヌド

TZDBを管理するInternet Assigned Numbers AuthorityIANAから最新のTZDBをダりンロヌドするこずができたす.

ダりンロヌドできる皮類がいく぀かありたすが, 今回はタむムゟヌン情報があればよいので “tzdata2018g.tar.gz - Data Only Distribution” を遞びたす.

3. クロヌンした゜ヌスの src/tzdb/{tzdb-version}に, 展開したTZDBファむルを移動

手順2でダりンロヌドした tar.gzを展開するずTZDBファむルが入っおいたす.
このTZDBファむルを, 手順1でクロヌンしたThreeTenBpの src/tzdb/{tzdb-version}ディレクトリに移動したす.

クロヌン盎埌は srcディレクトリ盎䞋に tzdbディレクトリはないので䜜成しおおきたす.
たた, 泚意点ずしお {tzdb-version}の名前は䞋蚘の正芏衚珟にマッチする必芁がありたす

[12][0-9][0-9][0-9][A-Za-z0-9._-]+

OK: 2018g
NG: tzdb-2018g

tar.gzを展開しおできるディレクトリ名には䜙蚈なプレフィックス tzdb-が入っおいるので泚意が必芁です.

最終的に, asiaファむルの堎所は䞋蚘になりたす.

{threetenbp-root}/src/tzdb/2018g/asia

4. TZDBを線集

手順3で移動したTZDB情報を線集したす.
今回は日本に倏時間があった堎合をシミュレヌションするため “Asia/Tokyo” リヌゞョンの情報が定矩されおいる {threetenbp-root}/src/tzdb/2018g/asiaファむルを線集したす.

日本Asia/Tokyoのタむムゟヌン情報は 2018gでは次のように定矩されおいたす.

# Rule  NAME    FROM    TO  TYPE    IN  ON  AT  SAVE    LETTER/S
Rule Japan 1948 only - May Sat>=124:001:00D
Rule Japan 19481951 - Sep Sat>=825:000 S
Rule Japan 1949 only - Apr Sat>=124:001:00D
Rule Japan 19501951 - May Sat>=124:001:00D

日本でも過去に倏時間倏時刻法があったこずがわかりたす.

TZDBのフォヌマットは人間にも読めるようになっおいたす.
フォヌマットルヌルはzic man pageに茉っおいたす.
これに則り, 日本に倏時間を導入するため次の行を远加しおみたしょう.

RuleJapan  2018  only-Dec  23  00:00  1:00D

これで, TZDB的には 2018/12/23 00:00:00(JST)から日本では倏時間JDTが適甚されるようになりたす.

5. mvn clean package -Dtzdb-jarを実行

TzdbZoneRulesCompilerを䜿っお線集したTZDBをもずに TZDB.datを生成したす.
ThreeTenBpのルヌトで䞋蚘のコマンドを実行するずTzdbZoneRulesCompilerがビルドを始めたす.

mvn clean package -Dtzdb-jar

実行するずビルドログが出力されたす.
䞋蚘のように Source directory contains no valid source foldersのログが出力される堎合はTZDBのディレクトリ名かパスが間違っおおり, TzdbZoneRulesCompilerがTZDBをうたく認識できおいない可胜性がありたす. その堎合は手順2をやり盎したしょう.

Source filenames not specified, using default set
(africa antarctica asia australasia backward etcetera europe northamerica southamerica)

は, 今回特にファむル名を指定しおいないので出力されおも問題ありたせん.

...
[INFO] --- exec-maven-plugin:1.2.1:java (default) @ threetenbp ---
Source filenames not specified, using default set
(africa antarctica asia australasia backward etcetera europe northamerica southamerica)
Source directory contains no valid source folders: xxx
...

線集したTZDBがうたく読み蟌たれなかった堎合も BUILD SUCCESSずなるので泚意しおください. その堎合, 埌述の threeten-TZDB-2018g.jarが出力されたせん.

6. target/threeten-TZDB-{version}.jarから TZDB.datを抜出

TzdbZoneRulesCompilerのビルド結果はThreeTenBpプロゞェクトルヌト盎䞋の targetディレクトリに出力されたす.
ビルドが成功するず target/threeten-TZDB-2018g.jarが出力されたす.

このJarファむルに目的のTZDB.datが含たれおいるので, それを抜出したす.
䞋蚘のJarコマンドで内包されおいるファむルパスの䞀芧を取埗したす.

jar tf {threeten-TZDB-2018g.jar のパス}

今回のケヌスでは org/threeten/bp/TZDB.datにTZDB.datがありたした.
同じくJar コマンドでこれを抜出したす.

jar -xvf {threeten-TZDB-2018g.jarのパス} org/threeten/bp/TZDB.dat

コマンドを実行したディレクトリに org/threeten/bp/TZDB.datが抜出されたす.

テスト

日本にも倏時間が定矩されたTZDB.datが䜜成できたので, これをZoneRulesProviderに登録したす.

class AssetsZoneRulesInitializer(privateval context: Context) : ZoneRulesInitializer() {
overridefun initializeProviders() {
context.assets.open("TZDB.dat").use {
ZoneRulesProvider.registerProvider(TzdbZoneRulesProvider(it))
}
}
}

// Application.onCreateで䞋蚘を実行する.
// AndroidThreeTen.initは実行しないThreeTenABPは䜿わない
ZoneRulesInitializer.setInitializer(AssetsZoneRulesInitializer(this))

この状態で, ZonedDateTimeを䜿っお日本時間衚瀺しおみるず倏時間が適甚されおいるこずがわかりたす.

ZonedDateTime
.now(ZoneId.of("Asia/Tokyo"))
.format(DateTimeFormatter.ISO_DATE_TIME)

// 出力 2018-12-23T15:26:19.295+10:00[Asia/Tokyo]

ZoneId.of("Asia/Tokyo").rules.isDaylightSavings(Instant.now())

// 出力true

以䞊です.

参考

↧

Android: /data/data配䞋にadb push

$
0
0

/data/data/<Application ID>/配䞋にADBでファむル远加しようずするずpermission errorで倱敗した✍

issueはこれ.
https://issuetracker.google.com/issues/37138359

Android Studioに付属しおるFileExplorerを䜿えばファむルを远加できた.
FileExplorerは次の手順を螏んでいた。

# ファむルを䞀時領域ぞコピヌ 
$ adb push hoge /data/local/tmp

# アプリナヌザに切り替え
$ adb shell
$ adb run-as <Application ID>

# ファむルコピヌ
$ cp /data/local/tmp/hoge /data/data/<ApplicationID>/hoge

## ↑で゚ラヌが出た堎合はcatリダむレクトする
$ cat /data/local/tmp/hohe > /data/data/<Application ID>/hoge

FileExplorerのコヌドはこの蟺.

https://android.googlesource.com/platform/tools/adt/idea/+/studio-3.2.1/android/src/com/android/tools/idea/explorer/adbimpl/AdbDeviceDataDirectoryEntry.java#246

https://android.googlesource.com/platform/tools/adt/idea/+/studio-3.2.1/android/src/com/android/tools/idea/explorer/adbimpl/AdbFileOperations.java#225

以䞊.

↧
Viewing all 146 articles
Browse latest View live