【Android】-- 如何对APP版本控制/更新?

这篇具有很好参考价值的文章主要介绍了【Android】-- 如何对APP版本控制/更新?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、 前提准备

1、获取服务器

2、使用工具操作云服务器

 二、Json格式网页

 三、创建file_paths.xml及修改AndroidManifest.xml

四、在java代码加入更新检测代码


效果如图:

        可以强制更新和非强制更新,和浏览器下载安装包。

android更新,Android,android,android studio,版本更新,云服务器

一、 前提准备

1、获取服务器

        首先去获取云服务器,如:阿里云服务器(学生可免费领取六个月)、腾讯云服务器、华为云服务器等。具体操作参考:从零开始用阿里云服务器搭建网页_阿里云怎么装修网页打开_zstar-_的博客-CSDN博客

        云服务器用于存放json格式网页和安装包,json可以对app进行版本控制更新、显示版本、显示更新内容、提供安装包下载位置等信息。

2、使用工具操作云服务器

        可以使用指令控制,也可以使用工具控制服务器,选用putty软件进行远程控制,winSCP软件进行文件传输。下载链接

       以下是winSCP界面的服务器文件:可按下面文件找到存放安装包和网页的文件夹。

android更新,Android,android,android studio,版本更新,云服务器android更新,Android,android,android studio,版本更新,云服务器android更新,Android,android,android studio,版本更新,云服务器android更新,Android,android,android studio,版本更新,云服务器android更新,Android,android,android studio,版本更新,云服务器

如下就是对APP进行版本更新的网页和安装包存放的文件夹:

android更新,Android,android,android studio,版本更新,云服务器

 二、Json格式网页

以下是html网页代码:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>UFCFans</title>
</head>
<body>
<pre><code id="json"></code></pre>
</body>
<script type="text/javascript">
let btn = document.querySelector('#json');
let data = {"hasUpdate": true,"NoIgnorable": true,
"versionCode": 2,
"versionName": "2.0",
"updateLog": "\n1、新增本地缓存。\n2、解决部分BUG。\n3、优化使用体验。",
"apkUrl": "http://8.130.127.118:8080/ufcfans.apk",
"webUrl": "http://8.130.127.118:8080/ufcfans.apk",
"apkSize": "29.5MB"};
btn.textContent = JSON.stringify(data, null, 2);
</script>
</html>

下面是相关变量说明:

{
    hasUpdate: true,  //是否有更新 默认true
    NoIgnorable: true, //不 可忽略更新  强制:true 非强制:false
    versionCode: 51,   //服务端的版本号  
    versionName: "2.4.1", //服务端的版本名
    updateLog: "\n1、更改保存图片的存储路径。\n2、更改软件更新的提示模式。\n3、调整非强制更新控制方式。\n4、新增存储权限的申请授权位置。", //更新提示内容
    apkUrl: "https://www.yuming.com/assets/a某o130.apk",//新版本APK直链下载地址
    webUrl: "https://yirj.gitee.io/update111",//浏览器更新链接,随意放(直链、蓝奏、官网均可)
    apkSize: "29.5MB" //新版本的大小 随意写就好
}

 三、创建file_paths.xml及修改AndroidManifest.xml

 创建file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

 修改AndroidManifest.xml

 文章来源地址https://www.toymoban.com/news/detail-654344.html

    <!-- 拥有完全的网络访问权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 修改或删除您的USB存储设备中的内容 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <!-- 查看网络连接 -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
        .....
        android:label="@string/app_name"
        android:requestLegacyExternalStorage="true">

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
        .........
</application>

四、在java代码加入更新检测代码

        在软件的检测更新界面的Oncreate方法下,加入检测更新的代码。

        NoIgnorable变量为true则强制更新,无取消按钮,为false有取消按钮;

versionName, updateLog, apkSize, apkUrl, webUrl和upl变量为你的网页网址,如:UFCFanshttp://8.130.127.118:8080

public static JSONObject jSONObject = null;
    private static boolean hasUpdate = true;
    private static boolean NoIgnorable; //是否有更新。 不可忽略的更新
    private static int versionCode = 0;
    private static String versionName, updateLog, apkSize, apkUrl, webUrl;
    private static  String[] upl;
   /**
     * 获取当前使用的软件包的版本号
     */
    public int getVersionCode() {
        try {
            //获取packagemanager的实例
            PackageManager packageManager = getPackageManager();
            //getPackageName()是你当前类的包名,0代表是获取版本信息
            PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0);
            Log.e("TAG", "版本号" + packInfo.versionCode);  //更新软件用的是版本号
            return packInfo.versionCode;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 1;
    }

    /**
     * 提示版本更新的对话框
     */
    public void showDialogUpdate() {
        //hasUpdate为true且程序版本号<服务端版本号,提示用户更新
        if (NoIgnorable) { //NoIgnorable为true 就是强制更新,无“取消”按钮
            // 这里的属性可以一直设置,因为每次设置后返回的是一个builder对象
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setCancelable(false); //开启强制更新,无法触摸外部关闭
            builder.setTitle("是否升级到" + versionName + "版本?").
                    // 设置提示框的图标
//                            setIcon(R.drawable.ic_launcher).
                    // 设置要显示的信息
                            setMessage("新版本大小:" + apkSize + "\n" + updateLog).
                    // 设置确定按钮
                            setPositiveButton("更新", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            loadNewVersionProgress();//程序内直接下载最新的版本程序
                        }
                    }).
                    setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮 应用内下载失败可用它更新
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Uri uri = Uri.parse(webUrl);
                    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                    startActivity(intent);
                    finish(); //强制更新,点击后销毁应用
                }
            });
            // 显示对话框
            builder.create().show();
        } else {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
//            builder.setCancelable(false); //非强制更新,屏蔽此行,触摸外部或退出键可关闭
            builder.setTitle("是否升级到" + versionName + "版本?").
                    setMessage("新版本大小:" + apkSize + "\n" + updateLog).
                    setPositiveButton("更新", new DialogInterface.OnClickListener() {//正按钮
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            loadNewVersionProgress();//下载最新的版本程序
                        }
                    }).setNegativeButton("取消", new DialogInterface.OnClickListener() {//负按钮
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
//                        finish(); //屏蔽销毁,不做任何处理
                        }
                    }).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Uri uri = Uri.parse(webUrl);
                            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                            startActivity(intent);
                            //finish(); //屏蔽销毁,访问浏览器,程序不会退出
                        }
                    });
            // 显示对话框
            builder.create().show();
        }
    }

    //轮询验证两个更新链接,返回有效链接
    public static String checkUrl(String[] ltl) {
        String resultUrl = null;
        for (String url : ltl) {
            resultUrl = url;
            try {
                //调用检查链接是否有效的方法
                String result = get(url);
                if (result != null && result.length() != 0) {
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return resultUrl;
    }

    //检查更新链接是否有效的方法
    public static String get(String url) {
        URL infoUrl = null;
        InputStream inStream = null;
        String line = "";
        try {
            infoUrl = new URL(url);
            URLConnection connection = infoUrl.openConnection();
            HttpURLConnection httpConnection = (HttpURLConnection) connection;
            int responseCode = httpConnection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                inStream = httpConnection.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, "utf-8"));
                StringBuilder strber = new StringBuilder();
                while ((line = reader.readLine()) != null)
                    strber.append(line + "\n");
                inStream.close();
                int start = strber.indexOf("{");
                int end = strber.indexOf("}");
                String json = strber.substring(start, end + 1);
                return json;
            }
        } catch (MalformedURLException e) {
        } catch (IOException e) {
        }
        return "";
    }

    /**
     * 使用检查过的有效链接,获取服务端json数据
     */
    public static JSONObject GetServerJson() {
        URL infoUrl = null;
        InputStream inStream = null;
        String line = "";
        try {
            String uurl = checkUrl(upl);
            infoUrl = new URL(uurl); //json格式信息的API,使用案例。
            URLConnection connection = infoUrl.openConnection();
            HttpURLConnection httpConnection = (HttpURLConnection) connection;
            int responseCode = httpConnection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                inStream = httpConnection.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, "utf-8"));
                StringBuilder strber = new StringBuilder();
                while ((line = reader.readLine()) != null)
                    strber.append(line + "\n");
                inStream.close();
                int start = strber.indexOf("{");
                int end = strber.indexOf("}");
                String json = strber.substring(start, end + 1);
                if (json != null) {
                    try {
                        jSONObject = new JSONObject(json);
                        hasUpdate = jSONObject.getBoolean("hasUpdate");
                        NoIgnorable = jSONObject.getBoolean("NoIgnorable");
                        versionCode = jSONObject.getInt("versionCode");
                        versionName = jSONObject.getString("versionName");
                        updateLog = jSONObject.getString("updateLog");
                        apkSize = jSONObject.getString("apkSize");
                        apkUrl = jSONObject.getString("apkUrl");
                        webUrl = jSONObject.getString("webUrl");
                        return jSONObject;
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 应用内直链升级方法,下载新版本程序
     */
    private void loadNewVersionProgress() {
        ProgressDialog pd = new ProgressDialog(this);
        pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        //获取手机的根目录存储位置,以及直链链接最后一个“/”后文字作为文件名展示在界面
        pd.setMessage("下载最新版本安装包到:" + Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + apkUrl.substring(apkUrl.lastIndexOf("/") + 1));
        pd.setCancelable(false); //开启强制更新,触摸屏幕其他位置无法关闭
        pd.show();

        //启动子线程下载任务
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                try {
                    File file = getFileFromServer(apkUrl, pd);  //调用下载服务方法动态显示进度
                    //sleep(2000);//设置休眠两秒之后再启动安装
                    installApk(file);  //下载完成直接安装
//                    pd.dismiss(); //屏蔽,结束掉进度条对话框,防止强制更新出Bug
                } catch (Exception e) {
                    //直链下载apk异常失败提示容错。
                    AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                    builder.setCancelable(false);//开启强制更新,触摸屏幕其他位置无法关闭
                    builder.setTitle("下载失败:").setMessage("1.请检查存储权限是否开启。\n2.请检查网络连接是否正常。\n3.使用浏览器下载新版本。\nother:"+e.getMessage());
                    builder.setPositiveButton("退出", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface arg0, int arg1) {
                            finish();
                        }
                    }).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮 应用内下载失败可用它更新
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Uri uri = Uri.parse(webUrl);
                            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                            startActivity(intent);
                            if (NoIgnorable) {
                                finish(); //强制更新,点击后销毁应用
                            }else{
                                pd.dismiss(); //不强制更新,跳转到浏览器并销毁应用内下载失败的进度条
                            }
                        }
                    });
                    builder.create().show();
                }
                Looper.loop();
            }
        }.start();
    }

    /**
     * 安装apk
     */
    protected void installApk(File file) {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(Intent.ACTION_VIEW);

        if (Build.VERSION.SDK_INT >= 24) {
            Uri apkUri = FileProvider.getUriForFile(this, "com.example.ufcfans.fileprovider", file); //这里要写你程序的包名,已实验不可使用${applicationId}
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        } else {
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        }
        this.startActivity(intent);
    }


    /**
     * 从服务器获取apk文件的代码
     * 传入网址uri,进度条对象即可获得一个File文件
     * (要在子线程中执行哦)
     */
    public static File getFileFromServer(String uri, ProgressDialog pd) throws Exception {
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            URL url = new URL(uri);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            //获取到文件的大小
            pd.setMax(conn.getContentLength());  //字节的方式显示下载进度
            InputStream is = conn.getInputStream();
            //获取直链链接最后一个“/”后文字作为文件名,下载存储到手机
            File file = new File(Environment.getExternalStorageDirectory(), apkUrl.substring(apkUrl.lastIndexOf("/", apkUrl.lastIndexOf("")) + 1));
            FileOutputStream fos = new FileOutputStream(file);
            BufferedInputStream bis = new BufferedInputStream(is);
            byte[] buffer = new byte[1024];
            int len;
            int total = 0;
            while ((len = bis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
                total += len;
                //获取当前下载量
                pd.setProgress(total);//字节方式显示下载量
            }
            fos.close();
            bis.close();
            is.close();
            return file;
        } else {
            return null;
        }
    }

    /**
     * 权限的验证及处理,相关方法
     */
    private void getReadPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    ActivityCompat.requestPermissions(this,
                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                                    Manifest.permission.READ_EXTERNAL_STORAGE}, 10001);
                } else {//没有则请求获取权限,示例权限是:存储权限,需要其他权限请更改或者替换
                    ActivityCompat.requestPermissions(this,
                            new String[]{
                                    Manifest.permission.READ_EXTERNAL_STORAGE,
                                    Manifest.permission.WRITE_EXTERNAL_STORAGE}, 10001);
                }
            } else {//如果已经获取到了权限则直接进行下一步操作
                Log.e(TAG, "全部权限已经授权成功");
            }
        }

    }

    /**
     * 一个或多个权限请求结果回调
     * 循环回调获取权限,除非勾选禁止后不再询问,之后提示用户引导用户去设置
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode) {
            case 10001:
                for (int i = 0; i < grantResults.length; i++) {
//                   如果拒绝获取权限
                    if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                        //判断是否勾选禁止后不再询问
                        boolean flag = ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i]);
                        if (flag) {
                            getReadPermissions();
                            return;//用户权限是一个一个的请求的,只要有拒绝,剩下的请求就可以停止,再次请求打开权限了
                        } else { // 勾选不再询问,并拒绝
                            Toast.makeText(this, "请到设置中打开权限", Toast.LENGTH_LONG).show();
                            return;
                        }
                    }
                }
                //Toast.makeText(this, "权限开启完成", Toast.LENGTH_LONG).show();
                break;
            default:
                break;
        }

    }

    /**
     * 忽略https的证书校验 的相关方法
     */
    public static void handleSSLHandshake() {
        try {
            TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                }
            }};

            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });
        } catch (Exception ignored) {
        }
    }

至此,已完成对应用的版本控制更新,通过

更改html文件的versionVode控制更新,如:默认为1,改为2时,用户APP会弹出更新框。

android更新,Android,android,android studio,版本更新,云服务器

 


到了这里,关于【Android】-- 如何对APP版本控制/更新?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 开发Android App,令人头疼的Android Studio、Gradle、JDK、Andrroid Gradle Plugin版本问题

    开发Android App,各种报错。真是炼狱般的体验,一弄弄一天,一抬头半夜两点半,时间废了还没成果,真是服了。 记录下问题,便于后期查阅。 看到网友搞笑评论:Gradle 在不做人方面,一直很稳定 Android Studio 版本:Flamingo | 2022.2.1 Patch 1 默认支持的JDK: 默认自带的 JDK 17 创建

    2024年01月25日
    浏览(61)
  • 如何下载android studio的历史版本

    1、浏览器输入下载地址:Android Studio download archives  |  Android Developers (google.cn) 2、一定不要翻译 3下滑页面,同意 4、找到对应的版本进行下载

    2024年04月13日
    浏览(46)
  • 如何下载安装最新版本的 Android Studio

    最新版本的 Android Studio 软件打开的时候,如下图: 下载安装android studio的步骤: 第一步骤:下载最新版本的 Android Studio 地址:https://developer.android.google.cn/studio 第二步骤:点击 Download Android Studio Electric Eel (默认64位) 注意:上面中文必须要改英语 ,如下图: 按下图操作: 第

    2024年02月03日
    浏览(83)
  • android studio 如何下载指定版本的NDK

    安装及配置 NDK 和 CMake  |  Android 开发者  |  Android Developers 如果官方文档看不懂的,就看我写的口水话版本吧; 1、AS打开SDK、NDK下载的那个对话框,如图:    2、非常关键的一步,默认情况下选项的子项未展开,所以需要勾选右下角 “Show Package Detail” ,CMake也是一样的哈

    2024年02月12日
    浏览(57)
  • Android Studio开发入门教程:如何更改APP的图标?

    环境:Windows10、Android Studio版本如下图、雷电模拟器。 推荐图标库 默认APP图标 将新图标拉进src/main/res/mipmap-hdpi文件夹(一般app的icon图标是存放在mipmap打头的文件夹下的) 更改src/main/AndroidManifest.xml文件内容 引入我们刚刚导入的新图标 保存并运行,在雷电模拟器查看效果

    2024年02月03日
    浏览(58)
  • Android Studio如何修改JDK版本和获知使用的Java版本

    什么是JDK和Java版本? 首先,让我们简单了解一下什么是JDK和Java版本。JDK(Java Development Kit)是Java开发工具包的缩写,它包含了Java的运行环境(JRE)和开发工具,用于开发Java应用程序。而Java版本是指Java编程语言的不同版本,每个版本都会有一些新的特性和改进。 Android St

    2024年03月15日
    浏览(52)
  • Android Studio:如何修改JDK版本和获知使用的Java版本

    JDK的含义: JDK代表Java Development Kit(Java开发工具包),它是一种用于开发和构建Java应用程序的软件包。JDK包含了开发Java应用程序所需的工具、编译器(javac)、运行时环境(JRE)以及其他辅助工具和库。 在Java开发领域中,JDK版本和Java版本通常是相互对应的,例如:JDK 8代表

    2024年02月09日
    浏览(47)
  • 新版Android Studio如何回退使用老版本的Logcat

    File - Settings - Experimental - Logcat 取消勾选 Enable new Logcat tool window 即可,如图:

    2024年02月11日
    浏览(49)
  • Android Studio制作手机App:通过手机蓝牙(Bluetooth)与STM32上的低功耗蓝牙(HC-42)连接通信,实现手机端对单片机的控制。

    背景: 本文的内容是针对单片机蓝牙模块(HC-42)开发的手机App。在这之前,我想先声明一点,手机与手机间的蓝牙连接方式”与“手机与HC间的蓝牙连接方式”是不一样的。原因就是手机搭配的是“经典蓝牙”模块,HC等蓝牙属于“低功耗蓝牙”模块。(二者的区别想了解的

    2024年02月04日
    浏览(56)
  • 如何在Android studio导入jdk9及以上版本中依赖包,如'rt.jar',' dt.jar'等

    1、如何获取jdk9及以上版本中依赖包,如\\\'rt.jar\\\',\\\' dt.jar\\\'等 ​在jdk9及后续版本中,jdk开始使用模块化规则,实现更好的封装和定义良好的接口,近一步加强了java的自由度,开发者可以定制化SDK ​包括rt.jar在内的依赖均已移除,以模块化形式更高效的存诸在 JAVA_HOME/jmods 目录下

    2023年04月25日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包