Knork:160行のコードを持つButterKnifeの最も簡単な代替手段

ハブラこんにちは!



以下では、ビューインジェクション、松葉杖、注釈、リフレクション、ジェイクウォートンを凌ぐための哀れな試み、そしてあなたの自転車が身体に近いことについて説明します。



ビューインジェクションとは何ですか? これは、このようなルーチンコードを回避する方法です。



Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // ... } });
      
      





たとえば、Jake Whartonによって書かれたButterKnifeを使用してビューインジェクションを使用すると、コードはより透明になります。



 @InjectView(R.id.button) Button mButton; @OnClick(R.id.button) public void onButtonClick() { // ... }
      
      





しかし、よく調べてみると、ButterKnifeは完璧ではないことがわかりました。



まず、コンパイル段階でヘルパークラスを生成し、多くのIDEやビルドシステムが時々狂ってしまいます(クラスを間違った順序でコンパイルします)。 もちろん、設計により、これによりブラックマジックがコードのパフォーマンスを低下させないようにできます。



第二に、ビューインジェクションを完全にキャンセルしません-ビューをリセットしますが、それに割り当てられたコールバックはリセットしません。 不適切に使用すると、メモリリークやその他のエラーが発生する可能性があります(たとえば、アダプターで繰り返し注入が行われる場合)。



第三に、たとえば可能な限りメソッドをView.OnKeyListenerにバインドするなど、独自のバインディングを追加することは非常に困難です。



そして最後に、古いAntベースのビルドシステムに接続することは非常に重要です。 しかし、多くのプロジェクトはまだGradleに切り替えていません。



だから私は考えた-それが意味するすべてで自分のButterKnifeを作ってみませんか? そのため、シンプルなKnorkライブラリ(カトラリー、 ナイフ+フォーク )も判明しました。 ライブラリの主要な機能-シンプルさと小さなサイズ。



単純化1.実行時の注釈の動的処理



「しかし、これはひどいです!」とあなたは言い、あなたは絶対に正しいでしょう。 これは本当に遅いですが、記事の最後に小さなベンチマークを示します。速度の点ですべてが悪いとは限りません。 しかし、この小さな恐怖は、コード生成やビルドプロセスエラーなどから私たちを救います。 また、必要に応じてライブラリを拡張できます。



簡略化2.わずか2つの注釈



覚えやすい2つの注釈だけに制限します。



Id-ウィジェットインジェクションに必要なクラスフィールドの前の注釈。

オン-メソッドの前の注釈。さまざまなリスナーを挿入する必要があります。



しかし、どのようにしてウィジェット識別子を@On()に渡し、さらに注釈付きメソッドをバインドするアクションも渡しますか? アノテーションは名前のない値を1つしか持つことができないことを知っています。また、より多くのパラメーターでは、名前を付ける必要があります。



 @On(R.id.button) // : @On(value=R.id.button, action=CLICK)
      
      







古い組み込みの開発スキルと救助への永続的な愛 自明でないソリューション。 IDは、0x7f000000..0xffffffffの範囲の整数にできることがわかっています。 そして、注釈では、64ビット長を使用できます。 これにより、個人のニーズに合わせて無料の高32ビットが提供されます。 そこで、メソッドを関連付ける必要があるイベント番号を保存します。 例:



 @Id(R.id.button) mButton; //   @On(CLICK + R.id.button) public void onButtonClick(Button b) { // ... } //     @On(LONGCLICK | R.id.button) public boolean onButtonLongClick(Button b) { // ... }
      
      





私の謙虚な意見では、このようなコードの読みやすさは、上記のパラメーター付き注釈よりもそれほど悪くありません。



簡素化3.柔軟なインジェクタークラス



インジェクションを扱うメインクラスのKnorkは、オブジェクトを調べて注釈を探し、各注釈Onに対応するインジェクターとデリゲートコントロールを見つけます。 そのため、開発者はプログラムのプロセスで独自のインジェクターを直接追加できます。 インジェクターは、メソッドをウィジェットにバインドし、作成されたリスナーを削除する責任があります。



全体像



すべてのコードは1つのKnorkクラスのフレームワーク内にあることが判明したため、接続のために記述する必要があるのは以下だけです。



 import static trikita.knork.Knork.*;
      
      







これはイデオロギー的に完全に正しいわけではありませんが、私たちのクラスはわずか150行なので、このアプローチをお許しください。



したがって、Knorkクラスでは次のようになります。



 class Knork { //      public static void inject(Object obj, View v) { ... } //   public static void reset(Object obj) { ... } //    public static void registerInjector(long action, Injector injector) { ... } //   public static interface Injector { void inject(View v, Invoker invoker); // Invoker -    method.invoke() void reset(View v); } //     - public final static long CLICK = 1L << 32; public static class ClickInjector implements Injector { public void inject(View v, final Invoker invoker) { v.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { invoker.invoke(view); } }); } public void reset(View v) { v.setOnClickListener(null); } } public final static long LONGCLICK = 2L << 32; public static class LongClickInjector implements Injector { ... } //  public static @interface Id { int value(); } public static @interface On { long value(); } //    static { registerInjector(CLICK, new ClickInjector()); registerInjector(LONGCLICK, new LongClickInjector()); } }
      
      







標準インジェクターは3つしかありません-1つはインジェクションの最後にメソッドを実行します(ウィジェットを好みに合わせてカスタマイズできます。たとえば、TextViewグループにフォントを割り当てることができます)。他の2つのインジェクターはそれぞれonClickおよびonLongClick処理を行います。 ただし、残りのインジェクター(OnTouch、OnBeforeTextChanged、OnItemClickなど)の追加は技術的な問題です。



Knorkクラスの完全なコードはこちらにあります



inject()およびreset()の実装はかなり簡単です-最初のメソッドはリフレクションによって注釈付きのフィールドとメソッドを反復処理し、埋め込まれたウィジェットとメソッドのリストを記憶します。2番目のメソッドはこれらのリストを調べ、インジェクターに対応するメソッドを解放するように要求します。



成功の代価。 ベンチマーク



簡単な例をスケッチしましたが、これは同時にベンチマークとして機能します。 1年半前の平均的な電話とネクサスでのコールドスタートの結果は次のとおりです。



通常のブレーキフォン
画像






Nexus 5
画像








最初と2番目のベンチマークでは、特定の(非表示の)ボタンに対してperformClick()とcallOnClick()を実行しました。 奇妙なことですが、method.invoke()からの損失は、直接的なメソッド呼び出しと比較して、予想より少なかった(数十または数百回と思った)



3番目のベンチマークでは、ビューを挿入、削除、再挿入などを行いました。 この場合のKnorkは、ButterKnifeと通常の手動実装と比較して、実際には10.100倍遅いです。 ButterKnifeはカット中にリスナーを削除しないことを忘れてはなりませんが、これは不正行為です。 掘り下げる場所があります-キャッシュで見つかったフィールドとメソッドを覚えておくと、リフレクションを再度使用しないようにできます。これにより、アダプターが大幅に向上します。 さらに、ORMLiteや他のライブラリと同様に、アノテーションの検索を高速化することもできます。



それでも、結局のところ、Knorkは高速ではないことを理解しています。 敗北を認める時が来たように思えますが、絶対的な数字では、通常、Knorkのビューインジェクターとイベントハンドラーに最大10ミリ秒が費やされています。 個人的には、フラグメントを開くことが自分に合っているときにこの遅延が好きなので、プロジェクトでKnorkを使用しようとしています。



プロジェクトのさらなる開発は非常に予測可能です-インジェクターを追加し、Onアノテーションにリストサポートを追加し(ButterKnifeのように、いくつかのアノテーションを記述しないように)、テストを追加し、メソッドキャッシュを追加してインジェクションを高速化できます。 ライブラリをAARリポジトリに追加することもありますが、これまでのところ私はこの領域で非常に暗く、Gradleでこれを正しく行う方法を理解していません(誰か助けてもらえますか?)。



まあ、それがすべてです。 ライブラリのソースとサンプル/ベンチマーク-bitbucket.org/trikita/knork 。 ライセンス-MIT。



All Articles