web-dev-qa-db-ja.com

Androidアプリ内更新APIを使用するには

最近、Google Playによって提供された可能性のある新しい種類のアプリ更新フローに遭遇しました。 Androidアプリケーションを更新するシームレスなフローが好きでした。Hotstarアプリで以下の手順を確認しました。

  1. 更新を示す下部からポップアップされたカードが利用可能です
  2. 「ホットスターの更新」ボタンをクリックすると、1つのダイアログがポップアップしました(Google Playから提供されているようです)

enter image description here

  1. アプリの実行中にダウンロードがバックグラウンドで開始されました
  2. ダウンロードが完了すると、1つのSnackBarがポップアップし、インストールの準備ができたアプリが表示されます
  3. インストール後にアプリを再起動しました

enter image description here

どうすればこれを達成できますか? Google Playと通信する方法が必要です。多くのブログを読みました。しかし、解決策は見つかりませんでした。自動アプリ更新がユーザーによって無効にされている場合、これは開発者にとって素晴らしい機能になる可能性があります。

17
Pratik ED

公式ドキュメント:https://developer.Android.com/guide/app-bundle/in-app-updates

制約:アプリ内アップデートは、Android 5.0(APIレベル21)以降を実行しているデバイスでのみ機能します

ステップ1:依存関係を追加:

dependencies {

    implementation 'com.google.Android.play:core:1.6.3'
    ...
}

ステップ2:アップデートが利用可能かどうかを確認し、利用可能であれば開始します

mAppUpdateManager = AppUpdateManagerFactory.create(this);

mAppUpdateManager.registerListener(installStateUpdatedListener);

mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(appUpdateInfo -> {

        if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
                && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){

            try {    
                    mAppUpdateManager.startUpdateFlowForResult(
                            appUpdateInfo, AppUpdateType.FLEXIBLE, MainActivity.this, RC_APP_UPDATE);
                }

            } catch (IntentSender.SendIntentException e) {
                e.printStackTrace();
            }

        } else if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED){
            popupSnackbarForCompleteUpdate();
        } else {
            Log.e(TAG, "checkForAppUpdateAvailability: something else");
        }
    });

ステップ3:状態の更新をリッスンします

InstallStateUpdatedListener installStateUpdatedListener = new 
  InstallStateUpdatedListener() {
    @Override
    public void onStateUpdate(InstallState state) {
        if (state.installStatus() == InstallStatus.DOWNLOADED){
            popupSnackbarForCompleteUpdate();
        } else if (state.installStatus() == InstallStatus.INSTALLED){
            if (mAppUpdateManager != null){
          mAppUpdateManager.unregisterListener(installStateUpdatedListener);
            }

        } else {
            Log.i(TAG, "InstallStateUpdatedListener: state: " + state.installStatus());
        }
    }
};

ステップ4:更新ステータスのコールバックを取得します

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == RC_APP_UPDATE) {
        if (resultCode != RESULT_OK) {
            Log.e(TAG, "onActivityResult: app download failed");
        }
    }
}

ステップ5:柔軟な更新

private void popupSnackbarForCompleteUpdate() {

    Snackbar snackbar =
            Snackbar.make(
                    findViewById(R.id.coordinatorLayout_main),
                    "New app is ready!",
                    Snackbar.LENGTH_INDEFINITE);

    snackbar.setAction("Install", view -> {
        if (mAppUpdateManager != null){
            mAppUpdateManager.completeUpdate();
        }
    });


snackbar.setActionTextColor(getResources().getColor(R.color.install_color));
    snackbar.show();
}

テストには、FakeAppUpdateManagerを使用できます

https://developer.Android.com/reference/com/google/Android/play/core/appupdate/testing/FakeAppUpdateManager.html

21
Pratik ED

Androidは本日、アプリ内アップデートをすべての人に公式に発表しました。 https://developer.Android.com/guide/app-bundle/in-app-updates

Update:IMMEDIATEとFLEXIBLEの両方の更新を1つのアクティビティで処理します。コトリンの方法。

import Android.app.Activity
import Android.content.Intent
import Android.content.IntentSender
import Android.os.Bundle
import Android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.Android.material.snackbar.Snackbar
import com.google.Android.play.core.appupdate.AppUpdateManager
import com.google.Android.play.core.appupdate.AppUpdateManagerFactory
import com.google.Android.play.core.install.InstallState
import com.google.Android.play.core.install.InstallStateUpdatedListener
import com.google.Android.play.core.install.model.AppUpdateType
import com.google.Android.play.core.install.model.InstallStatus
import com.google.Android.play.core.install.model.UpdateAvailability
import timber.log.Timber

class BaseUpdateCheckActivity : AppCompatActivity() {

    private val appUpdateManager: AppUpdateManager by lazy { AppUpdateManagerFactory.create(this) }
    private val appUpdatedListener: InstallStateUpdatedListener by lazy {
        object : InstallStateUpdatedListener {
            override fun onStateUpdate(installState: InstallState) {
                when {
                    installState.installStatus() == InstallStatus.DOWNLOADED -> popupSnackbarForCompleteUpdate()
                    installState.installStatus() == InstallStatus.INSTALLED -> appUpdateManager.unregisterListener(this)
                    else -> Timber.d("InstallStateUpdatedListener: state: %s", installState.installStatus())
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_ad_view)
        checkForAppUpdate()
    }

    private fun checkForAppUpdate() {
        // Returns an intent object that you use to check for an update.
        val appUpdateInfoTask = appUpdateManager.appUpdateInfo

        // Checks that the platform will allow the specified type of update.
        appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
            if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
                // Request the update.
                try {
                    val installType = when {
                        appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE) -> AppUpdateType.FLEXIBLE
                        appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) -> AppUpdateType.IMMEDIATE
                        else -> null
                    }
                    if (installType == AppUpdateType.FLEXIBLE) appUpdateManager.registerListener(appUpdatedListener)

                    appUpdateManager.startUpdateFlowForResult(
                            appUpdateInfo,
                            installType!!,
                            this,
                            APP_UPDATE_REQUEST_CODE)
                } catch (e: IntentSender.SendIntentException) {
                    e.printStackTrace()
                }
            }
        }
    }


    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == APP_UPDATE_REQUEST_CODE) {
            if (resultCode != Activity.RESULT_OK) {
                Toast.makeText(this,
                        "App Update failed, please try again on the next app launch.",
                        Toast.LENGTH_SHORT)
                        .show()
            }
        }
    }

    private fun popupSnackbarForCompleteUpdate() {
        val snackbar = Snackbar.make(
                findViewById(R.id.drawer_layout),
                "An update has just been downloaded.",
                Snackbar.LENGTH_INDEFINITE)
        snackbar.setAction("RESTART") { appUpdateManager.completeUpdate() }
        snackbar.setActionTextColor(ContextCompat.getColor(this, R.color.accent))
        snackbar.show()
    }


    override fun onResume() {
        super.onResume()
        appUpdateManager
                .appUpdateInfo
                .addOnSuccessListener { appUpdateInfo ->

                    // If the update is downloaded but not installed,
                    // notify the user to complete the update.
                    if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                        popupSnackbarForCompleteUpdate()
                    }

                    //Check if Immediate update is required
                    try {
                        if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
                            // If an in-app update is already running, resume the update.
                            appUpdateManager.startUpdateFlowForResult(
                                    appUpdateInfo,
                                    AppUpdateType.IMMEDIATE,
                                    this,
                                    APP_UPDATE_REQUEST_CODE)
                        }
                    } catch (e: IntentSender.SendIntentException) {
                        e.printStackTrace()
                    }
                }
    }

    companion object {
        private const val APP_UPDATE_REQUEST_CODE = 1991
    }
}

ソースの要旨: https://Gist.github.com/saikiran91/6788ad4d00edca30dad3f51aa47a4c5c

12
Sai

これを実装しようとすると、受け入れられた回答で引用されている公式のGoogleドキュメントは構文的に正しくありません。ある程度の調査が必要でしたが、最終的に正しい構文を見つけました。

の代わりに:

// Creates an instance of the manager.
AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfo = appUpdateManager.getAppUpdateInfo();

// Checks that the platform will allow the specified type of update.
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
  // For a flexible update, use AppUpdateType.FLEXIBLE
  && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request the update.

    appUpdateManager.startUpdateFlowForResult(
        // Pass the intent that is returned by 'getAppUpdateInfo()'.
        appUpdateInfo,
        // Or 'AppUpdateType.FLEXIBLE' for flexible updates.
        AppUpdateType.IMMEDIATE,
        // The current activity making the update request.
        this,
        // Include a request code to later monitor this update request.
        MY_REQUEST_CODE);
}

これを行う:

    private AppUpdateManager appUpdateManager;
    ...
    // onCreate(){ 
    // Creates instance of the manager.
    appUpdateManager = AppUpdateManagerFactory.create(mainContext);

    // Don't need to do this here anymore
    // Returns an intent object that you use to check for an update.
    //Task<AppUpdateInfo> appUpdateInfo = appUpdateManager.getAppUpdateInfo();

    appUpdateManager
            .getAppUpdateInfo()
            .addOnSuccessListener(
                    appUpdateInfo -> {

                        // Checks that the platform will allow the specified type of update.
                        if ((appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE)
                                && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE))
                        {
                            // Request the update.
                            try {
                                appUpdateManager.startUpdateFlowForResult(
                                        appUpdateInfo,
                                        AppUpdateType.IMMEDIATE,
                                        this,
                                        REQUEST_APP_UPDATE);
                            } catch (IntentSender.SendIntentException e) {
                                e.printStackTrace();
                            }
                        }
                    });

次に、インストールが途中でハングアップした場合に備えて、onResume()オーバーライドで同様のコードをコーディングします。

//Checks that the update is not stalled during 'onResume()'.
//However, you should execute this check at all entry points into the app.
@Override
protected void onResume() {
    super.onResume();

    appUpdateManager
            .getAppUpdateInfo()
            .addOnSuccessListener(
                    appUpdateInfo -> {

                        if (appUpdateInfo.updateAvailability()
                                == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
                            // If an in-app update is already running, resume the update.
                            try {
                                appUpdateManager.startUpdateFlowForResult(
                                        appUpdateInfo,
                                        AppUpdateType.IMMEDIATE,
                                        this,
                                        REQUEST_APP_UPDATE);
                            } catch (IntentSender.SendIntentException e) {
                                e.printStackTrace();
                            }
                        }
                    });
}
6
Michael Dougan

Googleは このブログ投稿 で説明されているように、アプリ内アップデートAPIの初期バージョンをテストしています。

現在、一部の初期テストパートナーのみが利用できますが、最終的にはすべての開発者が利用できるようになるはずです。 Android Developers BlogとPlay Consoleでのアナウンスに注目してください。

0
Nick Fortescue

私の推測では、それはGoogle Playではなくアプリ自体によって制御されています。起動時にAPI呼び出しを行って「最新」のバージョン番号を読み取り、そのバージョンが「必須」の更新であるかどうかを確認し、それを実行中のアプリのバージョンと比較するアプリを開発しました。新しいバージョンが利用可能である場合、更新が利用可能であることをユーザーに警告するダイアログボックスが表示されます(ユーザーの方がはるかに優れていますが)。更新が「必須」の場合、メッセージは、続行する前にアプリを更新する必要があることを伝えます。ユーザーが[更新]をクリックすると、App Storeページが表示され、通常どおり更新のダウンロードが開始され、アプリが終了します。ユーザーが[閉じる]をクリックすると、アプリは終了します。更新が必須ではない場合、今すぐ更新するか、続行するかを尋ねられます。ユーザーが[更新]をクリックすると、App Storeページが表示され、通常どおり更新のダウンロードが開始され、アプリが終了します。ユーザーが[続行]をクリックすると、アプリの既存のバージョンに移動します。

アプリを終了する前に、バックグラウンドダウンロードを管理してアプリの更新を開始した方法がわかりません。それはとてもいいことですが、上記の方法も非常に簡単で、開発者に多くの機能を提供します。

0
Michael Dougan