AndroidはJava 8の新機能をサポート

10023 ワード

AndroidはKitKat(Android SDK 4.4,API Level 19)からJava 7をサポートしていたが、それから長い間、GoogleはJava 8をサポートする計画(OracleのJava APIの著作権争いの原因と推定される)を提供していなかった.その間、大神がRetrolmbda、Jack toolchain(Google出品)などの代替案を出してJava 8の新しい特性を一部サポートしていた.開発者のニーズを満たす.しかし、Googleは今年、Android開発者のブログでJack toolchainを廃止し、最新のAndroid StudioにJava 8をサポートする新しい特性を内蔵することを発表した.
これらのスキームの実現原理は、コンパイル時にバイトコードを一度処理することが基本であり、下図に示すように、
Google desugar bytecode transformations
Android Studio 3.0+
近年騒がれているGoogleとOracleのJava APIの著作権争いは、Googleの失敗に伴ってついに一段落した.当時、GoogleはJavaのバージョン更新を全面的にサポートしない可能性が高いと推測していたが、案の定、Google I/O 2017大会で官側が将来KotlinがAndroidの第一公式言語になると正式に発表し、私の推測をさらに確認した.
しかし、現段階ではKotlinは普及しておらず、Android Studioでも現在正式に発表されていない3.0バージョンでKotlinのデフォルトサポートを開始する必要があり、3.0以下のバージョンでは拡張プラグインと協力してKotlinをサポートする必要があります.そのため、Javaは長い間Androidの主力開発言語だった.
そのため、GoogleもAndroid Studio 3.0+にJava 8の一部をサポートする新しい特性を内蔵しています.
  • Lambda式
  • メソッド
  • 参照
  • デフォルトおよび静的インタフェースメソッド
  • タイプ注記
  • 繰り返し注記
  • ただし、前方互換性のためか、IDEがプロジェクトでJack、Retrolambda、またはDexGuardが使用されていることを検出した場合、IDEが独自のJava 8プロパティサポートはアクティブになりません.したがって、Android Studioの拡張プラグインスキームを使用するには、既存のサードパーティプラグインスキームを削除する必要があります.詳細は、マニュアル:Use Java 8 language featuresを参照してください.
    Retrolambda
    Retrolambdaは、Java 7,6,5で以下の特性をサポートできる、比較的成熟したサードパーティ製プラグインスキームです.
  • Java 8プロパティ
  • Lambda式
  • メソッド
  • 参照
  • デフォルトおよび静的インタフェースメソッドオプションプロパティ、デフォルトオフ、手動で前方互換性
  • を行う必要があります.
  • Java 7プロパティ(Java 6,5で一部のJava 7プロパティをサポート可能)
  • Try-with-resources statements
  • Objects.requireNonNull
  • Catching Multiple Exception
  • Strings in switch Statements


  • Retrolambdaの導入
    RetrolambdaはJDKの拡張のみに対応しており、Androidで使用される場合は、Retrolambdaに依存するGradle Retrolambda Pluginプラグインを使用することができ、Gradle(依存するAndroid Gradle Pluginの最小バージョン1.5.0、Gradle Pluginの最小バージョン2.5)に合わせてAndroidエンジニアリングを自動構築することができます.
    Note: The minimum android gradle plugin is 1.5.0 and the minimum gradle plugin is 2.5.
    build.gradleには、次の内容が追加されます.
    buildscript {
       dependencies {
          classpath 'me.tatarka:gradle-retrolambda:3.6.1'
       }
    }
    
    apply plugin: 'me.tatarka.retrolambda'
    
    android {
      compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
      }
    }
    

    Retrolambdaの構成
    Retrolambdaの構成項目を変更するには、次のように追加します.
    apply plugin: 'me.tatarka.retrolambda'
    retrolambda {
        javaVersion JavaVersion.VERSION_1_7
        jvmArgs '-noverify'
        defaultMethods false
        incremental true
    }
    

    次のように追加することもできます.
    apply plugin: 'me.tatarka.retrolambda'
    
    android {
        retrolambda {
            javaVersion JavaVersion.VERSION_1_7
            jvmArgs '-noverify'
            defaultMethods false
            incremental true
        }
    }
    

    この2つの位置に追加する効果は同じです.詳細な構成パラメータの説明は、マニュアルを参照してください.
    Retrolambdaの構成で注目すべきはjavaVersionの構成です
    javaVersion Set the java version to compile to. The default is 6. Only 5, 6 or 7 are accepted.
    Java 6とJava 7のコンパイルの違い
    プラグインのデフォルトではjava 6を使用してコンパイルされます.これは理由があります.前述のRetrolambdaはJava 5,6で一部のJava 7特性をサポートできるが、AndroidはKitkatでJava 7のサポートを開始したばかりなので、アプリのminSdkVarsionが19未満の場合はJava 6を使用してコンパイルしたほうがいい.そうしないとRetrolambdaはJava 7特性に関するコードを処理しない.古いバージョンの携帯電話で実行すると発効しない可能性があります(一部の携帯電話メーカーは自分で処理しているので、すべての携帯電話に問題があるわけではありませんが、大体率の事件です).同様に、アプリのminSdkValersionが19以上の場合は、Java 7を使用してコンパイルすることが望ましい.これ以上翻訳処理をする必要はないからだ.Objects.requireNonNull()を例にJava 6とJava 7のコンパイル後のコードの違いを見てみましょう.HomeActivity.javaに次の方法を追加し、Java 6と7をそれぞれ使用してコンパイルします.
    private String parseContent(@NonNull String content) {
        return Objects.requireNonNull(content) + ".suffix";
    }
    

    コンパイル後、パスapp -> build -> intermediates -> transforms -> retrolambda -> ... -> HomeActivity.classを参照してjavaファイルに対応するバイトコードファイルを見つけることができる.
  • JavaVersion.VERSION_1_7何も処理していないことがわかる
  • private String parseContent(@NonNull String content) {
        return (String)Objects.requireNonNull(content) + ".suffix";
    }
    
  • JavaVersion.VERSION_1_6処理を行うことが分かる:object.getClass()Objects.requireNonNull()中の対空ポインタ投げ異常の処理
  • をシミュレートする.
    private String parseContent(@NonNull String content) {
        StringBuilder var10000 = new StringBuilder();
        content.getClass();
        return var10000.append((String)content).append(".suffix").toString();
    }
    

    Java 7コンパイル構成
    Java 7を構成してコンパイルする場合、javaVersion JavaVersion.VERSION_1_7のみを構成し、jvmArgs '-noverify'を構成しないと、コンパイル時にエラーが発生します.
    Error:Execution failed for task ':app:transformClassesWithRetrolambdaForDebug'. Process 'command '/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/bin/java'' finished with non-zero exit value 1
    これは、システムがjdk 7をインストールしていない可能性がありますが、推測(javaVersionを指定しない場合やjavaVersion JavaVersion.VERSION_1_6のみを構成する場合、システムがjdk 6をインストールしていなくても問題はありません)、環境を構築して検証していません.現在、2つの構成項目を追加すると正常にコンパイルされ、しばらく問題は発生しません.
    Proguardの構成
    Proguardファイルに次の内容を追加する必要があります.
    -dontwarn java.lang.invoke.*
    -dontwarn **$$Lambda$*
    

    Lintの非互換エラーの処理
    LintはLambda式を認識しない
    古いバージョンのAndroid Gradle PluginのLintはJava 8の新しい特性の文法に互換性がなく、Lambda式のようなJava 8の新しい特性の文法が間違っています(具体的にどのバージョンからサポートされているのか分かりません).android-retrolambda-lombokを導入して解決することができます.
    Lintはtry-with-resourcesを認識しない
    AndroidエンジニアリングのminSdkValersonが19未満(AndroidがAPI 19からJava 7をサポートしている)の場合、Lintはtry-with-resourcesのようなJava 7の新しい特性の文法を誤報する.Lintルールを設定することで解決できます.
    Lintルールの構成には、あるエンジニアリングのグローバル構成、あるコードの個別構成、システムのグローバル構成など、さまざまな方法があります.
    特殊なシーンがコードに対して個別に構成する必要がない限り、一般的にはプロジェクトのグローバル構成を推奨し、システムのグローバル構成を推奨しません.結局、複数のプロジェクトを同時に開発する可能性があり、プロジェクト間の差異構成に影響を与える可能性があります.
    Androidエンジニアリンググローバル構成
    AndroidエンジニアリングのSRC同級ディレクトリの下にlint.xmlファイルを新規作成または修正し、以下の内容を追加します.
    
    
        
            
            
            
        
    
    

    Note:公式マニュアルではlint.xmlはAndroidエンジニアリングのルートディレクトリの下に置くと誤解されがちですが、実はSRC同級ディレクトリの下に置く必要があります.以下に示します.
    Translator/
    app/
    src/build.gradle lint.xml
    コード別構成
    注記@SuppressLint("xxx")によってLintエラーが無視され、特定の注記タイプは、コマンドlint --listによって表示されます.
    @SuppressLint("NewApi")
    public final void setView(@NonNull T view) {
        mView = Objects.requireNonNull(view);
    }
    

    不要なアラームを閉じる
    Retrolambdaを導入すると、いくつかの新しい特性実現方式の提案的なアラームがありますが、あってもなくてもいいです.少し強迫症の人には、見ていても苦しくないように、これらのアラームを消すように構成することができます.
    アラームタイプ
  • Anonymous can be replaced with lambdaまたは@SuppressWarnings("Convert2Lambda")
  • mLoginBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startActivity(LoginActivity.class);
        }
    });
    
  • Statement lambda can be replaced with expression lambdaまたは@SuppressWarnings("CodeBlock2Expr")
  • mLoginBtn.setOnClickListener(view -> {
        startActivity(LoginActivity.class);
    });
    

    グローバル構成方法
    Android Studioでプロジェクトやシステムのグローバル構成を行うと便利です.次の設定ページに進み、関連するアラームの選択をチェックしないでください.
  • プロジェクトグローバル構成Preferences->Inspections
  • システムグローバル構成Other Settings->Default Settings->Inspections
  • コード別構成方法
    アラームは注釈@SuppressWarnings("xxx")で無視され、具体的な注釈タイプはAndroid SuppressWarnings listを見ることができ、このお兄さんがまとめた比較的完全です.
    @SuppressWarnings("Convert2Lambda")
    mLoginBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startActivity(LoginActivity.class);
        }
    });
    

    Lambda式
    Lambda式(Javaでは閉パッケージまたは匿名関数とも呼ばれる)は、関数式プログラミングに由来し、関数式インタフェース(SAM、Single Abstract Method、単一抽象メソッドタイプとも呼ばれる)の伝統的な内部クラス文法の代わりに、関数式インタフェース(SAM、Single Abstract Method、単一抽象メソッドタイプとも呼ばれる.単純に1つの方法しかないインタフェースと理解できる)を用いて、よくからかわれる高度な問題を解決する.だからRxJavaのようなストリームの書き方では、Lambda式はコードをもっと紹介することができます.
    //    
    mLoginBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Activity activity = HomeActivity.this;
            Intent intent = new Intent(activity, LoginActivity.class);
            activity.startActivity(intent);
        }
    });
    
    // Lambda   
    mLoginBtn.setOnClickListener(view -> {
        Intent intent = new Intent(this, LoginActivity.class);
        this.startActivity(intent);
    });
    

    Lambda式の使い方と実現原理は以下のいくつかの文章を参考にすることができ,ここでは注目しない.
  • java 8から関数式プログラミング
  • といえば
  • State of the Lambda(訳文)
  • State of the Lambda: Libraries Edition
  • Translation of Lambda Expressions

  • ここで重点的に注目しているのはLambda式と伝統的な文法の違いです.結局、この文法糖は両刃の剣であり、コードをより簡単に紹介することができますが、コードの可読性を低下させ、使用が悪いと問題になる可能性があります.
    this
    内部クラスのthisは、外部クラスへの参照ではなく、内部クラスオブジェクトへの参照を指します.Lambda式のthisは外部クラスへの参照です.
    外部変数へのアクセス
    内部クラスでアクセスする外部変数はfinal修飾のみであり、コンパイラはこれらのルールに厳しいため、final修飾がなければコンパイルできません.
    Gradle Retrolambda Pluginが導入された場合、プラグインはコンパイル時に条件を満たす外部変数に自動的にfinal修飾子を付け、外部変数にアクセスするルールはLambda式と同じです.
    final String message = "test";
    mLoginBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Log.d(TAG, message);
        }
    });
    

    Lambda式でアクセスする外部変数はfinal修飾を強制するのではなく、「有効読み取り専用」の変数を満たすだけでよい(コンパイラはコンテキストに基づいて導出される).Lambda式では変数の値を変更できないと簡単に理解できる.
    String message = "test";
    mLoginBtn.setOnClickListener(view -> Log.d(TAG, message));
    

    次のように外部変数値を変更したLambda式はコンパイルエラーになります.
    String message = "test";
    mLoginBtn.setOnClickListener(view -> {
        message += "changed";
        Log.d(TAG, message);
    });