当Android应用程序有新版本可用时,强制更新该应用程序。

160
我在Google Play商店上有一个应用程序。当更新版本可用时,旧版本将变得无法使用 - 也就是说,如果用户不更新应用程序,他们就无法进入应用程序。当新版本可用时,我该如何强制用户更新应用程序?

1
这个开源的GitHub项目(MAHAndroidUpdater)完全提供了这个功能。试试看,非常简单。https://github.com/hummatli/MAHAndroidUpdater - Sattar Hummatli
1
最佳实践是支持旧版本 - 特别是针对Android设备,因为在Android应用上强制更新会将用户发送到Google Play应用程序,而该应用程序以缓存旧版本的应用程序页面而闻名,且无法刷新,除非强制退出应用程序 - 这是一个大问题,大多数人不知道,除非你在发布当天必须回答1000个与此相关的电话。 - BricoleurDev
2
对你的API进行版本控制。 - BricoleurDev
23个回答

137

我同意另一个回答中的Scott Helme所说的观点。但在某些极端情况下(安全问题,API变更等),您绝对需要用户更新以继续使用应用程序时,可以提供一个简单的版本控制API。该API应如下所示:

版本检查API:

请求参数:

  • int appVersion 应用版本号

响应

  1. boolean forceUpgrade 强制升级
  2. boolean recommendUpgrade 推荐升级

当您的应用启动时,可以调用此API,并传递当前应用程序版本,然后检查版本控制API调用的响应。

如果 forceUpgradetrue,则显示弹出对话框,让用户选择退出应用程序或前往Google Play商店升级应用程序。

否则,如果recommendUpgradetrue,则显示弹出对话框,让用户选择是更新还是继续使用应用。

即使已经实现了强制升级功能,除非绝对必要,否则您应继续支持旧版本。


1
你会在用户体验方面,每次应用启动且需要推荐升级时都显示弹出窗口吗?还是只显示一次弹出窗口,如果用户拒绝升级就不再显示。 - Aneesh
12
对于推荐升级,如果用户拒绝了,我认为您不应再次显示它。您也可以将其"在用户打开应用程序的每n次中仅显示一次"。您可以自行决定n值并实现它。 - Michael
10
我认为更好的解决方案是在所有API调用的标头中系统地传递应用程序的版本,然后如果API检测到版本过时,则返回特定状态码,并在应用程序内部处理它。这比必须为此检查调用另一个专用API,并且必须弄清楚何时/在何处应进行此API调用要好。 - Raphael C
1
@RaphaelC 这取决于您的服务器端实现。在某些情况下,添加检查所有API版本的过滤器也不是完美的解决方案。 - Sepehr GH
11
自从那时以来,情况发生了变化,现在安卓提供了一个API,您可以使用它来强制用户升级,并阻止他继续使用应用程序,如果他不升级到最低版本:https://developer.android.com/guide/app-bundle/in-app-updates - Raphael C

61

试试这个: 首先你需要发起一个请求调用到Play商店的链接,从那里获取当前版本,然后将其与您当前的版本进行比较。

String currentVersion, latestVersion;
Dialog dialog;
private void getCurrentVersion(){
 PackageManager pm = this.getPackageManager();
        PackageInfo pInfo = null;

        try {
            pInfo =  pm.getPackageInfo(this.getPackageName(),0);

        } catch (PackageManager.NameNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        currentVersion = pInfo.versionName;

   new GetLatestVersion().execute();

}

private class GetLatestVersion extends AsyncTask<String, String, JSONObject> {

        private ProgressDialog progressDialog;

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected JSONObject doInBackground(String... params) {
            try {
//It retrieves the latest version by scraping the content of current version from play store at runtime
                Document doc = Jsoup.connect(urlOfAppFromPlayStore).get();
                latestVersion = doc.getElementsByClass("htlgb").get(6).text();

            }catch (Exception e){
                e.printStackTrace();

            }

            return new JSONObject();
        }

        @Override
        protected void onPostExecute(JSONObject jsonObject) {
            if(latestVersion!=null) {
                if (!currentVersion.equalsIgnoreCase(latestVersion)){
                   if(!isFinishing()){ //This would help to prevent Error : BinderProxy@45d459c0 is not valid; is your activity running? error
                    showUpdateDialog();
                    }
                }
            }
            else
                background.start();
            super.onPostExecute(jsonObject);
        }
    }

private void showUpdateDialog(){
        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("A New Update is Available");
        builder.setPositiveButton("Update", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse
                        ("market://details?id=yourAppPackageName")));
                dialog.dismiss();
            }
        });

        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                background.start();
            }
        });

        builder.setCancelable(false);
        dialog = builder.show();
    }

1
我猜这需要使用外部库JSoup吗? - Abraham Philip
1
是的,但那是为了获取HTML div页面。我的意思是说,如果你可以在没有JSoup的情况下爬取这个HTML元素,你可能不需要使用任何第三方库来实现这个功能,它仍然可以工作。 - AshuKingSharma
2
如果他们改变了itemprop的值会怎样? - asok Buzz
1
如果他们更改了itemprop的值,那么我的应用程序也会受到影响,我会立即编辑我的答案来支持它,所以在那之前不用担心 :) - AshuKingSharma
@sunily:背景是一个线程,用于运行活动。我在闪屏中调用此函数,并使用背景线程变量来运行主活动。如果用户不想更新,则会调用它。 - AshuKingSharma
显示剩余9条评论

56

当新版本发布时,您不应立即停止支持旧版本,这将导致可怕的用户体验。我不知道有哪家软件供应商会这样做,这很有道理。

如果用户无法更新或在此时不想更新,会发生什么?他们就不能使用您的应用程序,这是不好的。

谷歌不提供类似版本跟踪的选项,因此您需要自己开发此功能。一个简单的Web服务可以返回您的应用程序当前的最新版本进行检查。然后您可以更新版本,应用程序就会知道它已经过时了。我只建议使用此功能让您的用户更快地了解到有更新,而不是依赖Google Play。它应该不会真正阻止应用程序运行,只是促使用户更新。


2
@ashraful 如果这个或任何答案解决了你的问题,请考虑通过点击复选标记接受它。这向更广泛的社区表明你已经找到了解决方案,并为回答者和你自己赢得了一些声誉。没有义务这样做。 - Sufian
1
答案取决于你知道有多少现存的软件供应商。你能量化吗? - Lou Morda
1
是否可以从应用商店或Play商店获取当前应用程序版本?还是我需要在我的服务器上保持最新的应用程序版本?因为现在我在我的服务器上有一个服务调用来返回最新的应用程序版本,但困难在于每次发布应用程序时都需要更新服务器。因此,是否可以从各自的商店获取当前版本号? - subha
4
Supercell只支持其游戏的最新版本。每当他们在Google Play推出新版本后,任何试图连接的旧客户端都会从服务器收到“需要更新”的响应,并看到连接关闭。他们每年会这样做多次。因此,除非您不将“游戏公司”视为“软件供应商”,否则现在您已经知道其中一个了 :) 考虑到他们仍然每天赚取几百万美元,显然这种糟糕的用户体验并没有成为阻碍者。 - Arjan
@Subha 是的,这是可能的。但是官方没有提供API,所以您需要爬取网页。这也意味着如果网页发生变化,它可能会失效。我在一个第三方应用程序中完成了这项工作,并发布了相当简单的代码作为答案在此处。目前,该代码将适用于Play和iTunes页面。然而,在生产环境中,最好寻找一种解决方案,可以自动更新服务器上的最新应用程序版本,而不是依赖于Google或Apple页面的格式? - Arjan
显示剩余4条评论

39

Google团队的解决方案

从Android 5.0开始,这是通过新的Google Play应用内更新机制很容易实现的。要求使用版本为1.5.0+的Play Core库,并使用App Bundles分发而不是apk。

该库提供了两种不同的方法来通知用户更新:

  1. 弹性方式,当用户在后台更新应用程序时可以继续使用该应用程序。

  2. 立即方式-阻止屏幕,直到用户更新它为止,不允许用户进入应用程序。

要实施它,请按照以下步骤进行:

  1. 检查更新是否可用
  2. 启动更新
  3. 获取更新状态的回调
  4. 处理更新

所有这些步骤的实现都在官方网站上详细描述:https://developer.android.com/guide/app-bundle/in-app-updates


35

2
这是最好的答案! - fvaldivia
2
该功能实际上依赖于Play缓存在用户启动应用程序时保持最新状态,即如果缓存中应用程序的版本号与您在Play商店上最新部署的版本号不同,则不会提示用户。 - kiran01bm
有任何自定义布局的选项吗? - sejn
2
IMMEDIATE模式仍然允许用户退出该屏幕 :( - Alex

16

Scott和Michael的回答是正确的。托管一个提供你支持的最小版本号的服务,并将其与已安装的版本进行比较。希望你永远不需要使用它,但如果某个版本存在严重缺陷而必须摧毁它,它就像救命稻草一样。

我只是想添加下一步要做的代码。接下来是如何启动Google Play Intent并在提示用户必须升级后将他们带到您在商店中的新版本。

public class UpgradeActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_upgrade);
        final String appName = "com.appname";
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                    startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id="+appName)));

            }
        });
    }
}

如果每次发布都需要强制升级,那么您应该重新考虑您的设计。


14

2
另请参见:https://android-developers.googleblog.com/2018/11/unfolding-right-now-at-androiddevsummit.html - albert c braun
1
这个 API 现在可用吗? - Yayayaya
1
我在谷歌上搜索并找到了这个链接:https://developer.android.com/guide/app-bundle/in-app-updates - TooCool
该功能实际上依赖于用户启动你的应用程序时Play缓存是最新的,即如果缓存中你的应用程序的版本号与Play商店最新部署的版本号不同,则不会提示用户。 - kiran01bm

11

我强烈建议查看Firebase的远程配置功能。

我使用一个参数-app_version_enabled-实现了它,并使用条件“已禁用的Android版本”进行了设置,如下所示:

applies if App ID == com.example.myapp and App version regular expression ^(5.6.1|5.4.2) 

默认参数为"true",但Disabled Android Versions的值为false。在我的Disabled Android Versions正则表达式中,您可以通过另一个用括号括起来的|{version name}来添加更多禁用版本。

然后我只需检查配置是否指定该版本已启用或未启用--我有一个活动,我会启动它来强制用户升级。我在应用程序仅能从外部启动的两个位置(我的默认启动器活动和一个处理意图的活动)中进行检查。由于远程配置是基于缓存的,如果缓存尚未失效,则不会立即捕获应用程序的"已禁用"版本,但根据他们推荐的缓存过期值,最多需要12小时。


这是一个非常好的选择。通过指定一组“禁用”的版本,您可以快速保护自己,以防应用程序存在安全问题或主要性能问题。它允许您继续支持每个版本,但在需要时禁止特定版本。 - Terry

4

检查本地和应用商店APK的版本代码

try {
        versionChecker VersionChecker = new versionChecker();
        String versionUpdated = VersionChecker.execute().get().toString();
        Log.i("version code is", versionUpdated);


        PackageInfo packageInfo = null;
        try {
            packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        int version_code = packageInfo.versionCode;
        String version_name = packageInfo.versionName;
        Log.i("updated version code", String.valueOf(version_code) + "  " + version_name);
        if (version_name != versionUpdated) {
            String packageName = getApplicationContext().getPackageName();//
            UpdateMeeDialog updateMeeDialog = new UpdateMeeDialog();
            updateMeeDialog.showDialogAddRoute(MainActivity.this, packageName);
            Toast.makeText(getApplicationContext(), "please updated", Toast.LENGTH_LONG).show();
        }
    } catch (Exception e) {
        e.getStackTrace();
    }

实现版本检查的类
class versionChecker extends AsyncTask<String, String, String> {
String newVersion;

@Override
protected String doInBackground(String... params) {

    try {
        newVersion = Jsoup.connect("https://play.google.com/store/apps/details?id=+YOR_PACKAGE_NAME+&hl=en")
                .timeout(30000)
                .userAgent("Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6")
                .referrer("http://www.google.com")
                .get()
                .select("div[itemprop=softwareVersion]")
                .first()
                .ownText();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return newVersion;
}
}

更新的对话框
public class UpdateMeeDialog {

ActivityManager am;
TextView rootName;
Context context;
Dialog dialog;
String key1,schoolId;
public void showDialogAddRoute(Activity activity, final String packageName){
    context=activity;
    dialog = new Dialog(context);

    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
    dialog.setCancelable(false);
    dialog.setContentView(R.layout.dialog_update);
    am = (ActivityManager)activity.getSystemService(Context.ACTIVITY_SERVICE);

    Button cancelDialogue=(Button)dialog.findViewById(R.id.buttonUpdate);
    Log.i("package name",packageName);
    cancelDialogue.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent=new Intent(Intent.ACTION_VIEW);                
    intent.setData(Uri.parse("https://play.google.com/store/apps/details?
    id="+packageName+"&hl=en"));
            context.startActivity(intent);
        }
    });
    dialog.show();
}
}

对话框布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#d4e9f2">


<TextView
    android:layout_width="match_parent"
    android:layout_height="40dp"

    android:text="Please Update First..!!"
    android:textSize="20dp"
    android:textColor="#46a5df"
    android:textAlignment="center"
    android:layout_marginTop="50dp"
    android:id="@+id/textMessage"
    />
<LinearLayout
    android:layout_width="match_parent"
    android:orientation="horizontal"
    android:weightSum="1"
    android:layout_marginTop="50dp"
    android:layout_below="@+id/textMessage"
    android:layout_height="50dp">

    <Button
        android:id="@+id/buttonUpdate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Update"
        android:background="#67C6F1"
        android:textAlignment="center" />
</LinearLayout>


3
我使用了简单的方法来实现这个:
  1. 创建 Firebase 数据库
  2. 添加你的最新版本
  3. 如果版本与已安装版本不同,则显示安装更新的弹窗。对我来说,它完美地工作了...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接