Android产品研发(十四)-->App升级与更新

2023-11-03

转载请标明出处:一片枫叶的专栏

上一篇文章中我们讲解了Android app中的轮询操作,讲解的内容主要包括:我们在App中使用轮询操作的情景,作用以及实现方式等。一般而言我们使用轮询操作都是通过定时任务的形式请求服务器并更新用户界面,轮询操作都有一定的使用生命周期,即在一定的页面中启动轮询操作,然后在特定的情况下关闭轮询操作,这点需要我们尤为注意,我们还介绍了使用Timer和Handler实现轮询操作的实例,更多关于App中轮询操作的信息,可参考我的:Android产品研发(十三)–>App轮询操作

本文将讲解app的升级与更新。一般而言用户使用App的时候升级提醒有两种方式获得:

  • 一种是通过App Store获取

  • 一种是打开应用之后提醒用户更新升级

而更新操作一般是在用户点击了升级按钮之后开始执行的,这里的升级操作也分为两种形式:

  • 一般升级

  • 强制升级

app升级操作:

  • App Store升级

在App Store中升级需要为App Store上传新版App,我们在新版本完成之后都会上传到App Store中,不同的应用市场审核的时间不同,一般除了第一次上传时间较长之外,其余的审核都是挺快的,一般不会超过半天(不排除例外情况奥),在审核完成之后就相当于完成了这个应用市场的发布了,也就是发布上线了。这时候如果用户安装了这个应用市场,那么就能看到我们的App有新版本的升级提醒了。

  • 应用内升级

除了可以在应用市场升级,我们还可以在应用内升级,在应用内升级主要是通过调用服务器端接口获取应用的升级信息,然后通过获取的服务器升级应用信息与本地的App版本比对,若服务器下发的最新的App版本高于本地的版本号,则说明有新版本发布,那么我们就可以执行更新操作了,否则忽略掉即可。

应用内升级其实已经有好多第三方的SDK了,常见的友盟,百度App开发工具包都已经集成了升级的功能,部分SDK厂商还提供增量更新的功能。增量更新的内容不是我们这里的讨论重点,想了解更多增量更新的内容可参考:浅谈Android增量升级

这里我们先简单介绍一下友盟的App升级功能,友盟其实已经有了App升级的API,我们只需要简单的调用即可。

  • 友盟更新接口API
/**
 * 请求友盟更新API,判断是否弹出更新弹窗
 */
public static void updateVersion(final Activity mContext, final MainActivity.UpdateCallback updateCallback, final boolean isShow) {
        UmengUpdateAgent.setUpdateListener(new UmengUpdateListener() {
            @Override
            public void onUpdateReturned(int updateStatus, UpdateResponse updateInfo) {
                switch (updateStatus) {
                    //判断是否有新版本需要更新
                    case UpdateStatus.Yes: // has update
                        try {
                            //在线读取更新参数
                            String value = MobclickAgent.getConfigParams(mContext, "FORCE_UPDATE_MIXVERSION");
                            if (value != null && !value.trim().equals("")) {
                                int versionCode = Config.changeVersionNameToCode(value);
                                if (versionCode != 0) {
                                    String localVersionName = getVersionName(mContext);
                                    int localVersionCode = Config.changeVersionNameToCode(localVersionName);
                                    //判断当前版本号于友盟中的最低版本号,若当前版本号小于最低版本号,则强制更新,否则非强制更新
                                    if (localVersionCode <= versionCode) {
                                        // 弹窗更新弹窗
                                        updateCallback.onUpdateSuccess(updateInfo);
                                    } else {
                                        UmengUpdateAgent.setUpdateAutoPopup(true);
                                        UmengUpdateAgent.showUpdateDialog(mContext, updateInfo);
                                    }
                                } else {
                                    UmengUpdateAgent.setUpdateAutoPopup(true);
                                    UmengUpdateAgent.showUpdateDialog(mContext, updateInfo);
                                }
                            } else {
                                UmengUpdateAgent.setUpdateAutoPopup(true);
                                UmengUpdateAgent.showUpdateDialog(mContext, updateInfo);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                        break;
                    case UpdateStatus.No: // has no update
                        if (isShow) {
                            Config.showToast(mContext, "您当前使用的友友用车已是最新版本");
                        }
                        break;
                }
            }
        });
        UmengUpdateAgent.setUpdateAutoPopup(false);
        UmengUpdateAgent.forceUpdate(mContext);
        UmengUpdateAgent.setChannel(ChannelUtil.getChannel(mContext));
    }

以上是友盟的升级API,在调用之前需要先继承友盟的SDK,这样经过调用之后我们就可以通过友盟实现更新接口的提示功能了,默认的友盟提供了静默安装,更新提示弹窗,强制更新等几种,可以根据自身App的需求来确定更新的方式。

如果不喜欢使用第三方的更新方式,我们也可以通过调用服务器接口的方式实现自己的更新弹窗提示,主要的逻辑也是通过判断服务器下发的最新App版本号与本地版本号对比,若服务器端的App版本号大于本地的App版本号,则说明当前App不是最新的版本,需要升级,这里我们简单看一下友友用车中自定义的更新接口实现:

/**
     * 检测App是否需要更新
     *
     * @param mContext
     * @param isShow   若不需要更新是否需要弹出文案
     */
    public static void queryAppBaseVersionInfo(final Activity mContext, final boolean isOneUpdate, final boolean isShow) {
        try {
            // 若当前网络异常,则直接return
            if (!Config.isNetworkConnected(mContext)) {
                // 关闭进度条
                dismissProgress(isShow);
                return;
            }
            // 控制变量,App更新接口进程生命周期中只会调用一次
            if (isQueryAppUpdated && isOneUpdate) {
                return;
            }
            L.i("开始调用请求是否需要版本更新的接口....");
            ExtInterface.QueryAppBaseVersionInfoNL.Request.Builder request = ExtInterface.QueryAppBaseVersionInfoNL.Request.newBuilder();
            request.setClientChannel(CHANNEL_Android);
            // 查询最新的版本信息,不需要传入版本号
            // request.setVersionCode(VersionUtils.getVersionName(mContext));
            NetworkTask task = new NetworkTask(Cmd.CmdCode.QueryAppBaseVersionInfo_VALUE);
            task.setBusiData(request.build().toByteArray());
            NetworkUtils.executeNetwork(task, new HttpResponse.NetWorkResponse<UUResponseData>() {
                @Override
                public void onSuccessResponse(UUResponseData responseData) {
                    if (responseData.getRet() == 0) {
                        try {
                            isQueryAppUpdated = true;
                            ExtInterface.QueryAppBaseVersionInfoNL.Response response = ExtInterface.QueryAppBaseVersionInfoNL.Response.parseFrom(responseData.getBusiData());
                            if (response.getRet() == 0) {
                                L.i("请求检测App是否更新接口成功,开始解析返回结果");
                                // 解析检测结果
                                parserUpdateResule(mContext, response, isShow);
                            } else {
                                if (isShow) {
                                    showDefaultNetworkSnackBar(mContext);
                                }
                            }
                        } catch (InvalidProtocolBufferException e) {
                            e.printStackTrace();
                            if (isShow) {
                                showDefaultNetworkSnackBar(mContext);
                            }
                        }
                    }
                }

                @Override
                public void onError(VolleyError errorResponse) {
                    L.e("请求检测更新接口失败....");
                    if (isShow) {
                        showDefaultNetworkSnackBar(mContext);
                    }
                }

                @Override
                public void networkFinish() {
                    L.i("请求检测更新接口完成....");
                    // 关闭进度条
                    dismissProgress(isShow);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

该接口只会在App打开时调用一次,判断App是否需要更新,然后在请求服务器成功之后,会解析请求结果,我们继续看一下我们的解析逻辑:

/**
     * 解析更新检查结果
     *
     * @param response
     */
    private static void parserUpdateResule(Activity mContext, ExtInterface.QueryAppBaseVersionInfoNL.Response response, boolean isShow) {
        if (mContext == null) {
            return;
        }
        // 判断是否需要更新
        ExtInterface.AppBaseVersionInfo appBaseVersionInfo = response.getAppBaseVersionInfo();
        // 若当前更新是否有效
        if (appBaseVersionInfo.getIsDel() == ENEFFECT) {
            return;
        }
        String updateVersionCode = appBaseVersionInfo.getVersionCode();
        int updateCode = changeVersionNameToCode(updateVersionCode);
        int localCode = changeVersionNameToCode(VersionUtils.getVersionName(mContext));
        // 本地应用版本号小于更新的应用版本号,则需要更新
        L.i("本地版本号:" + localCode + "  " + VersionUtils.getVersionName(mContext) + "  远程版本号:" + updateCode
                            + "  " + updateVersionCode);
        if (localCode < updateCode) {
            // 显示更新文案
            L.i("开始显示更新弹窗...");
            showUpdateDialog(mContext, appBaseVersionInfo);
        }
        // 不需要更新
        else {
            if (isShow) {
                Config.showToast(mContext, mContext.getResources().getString(R.string.about_new));
            }
        }
    }

解析更新接口信息的时候,会判断App的更新操作是普通更新还是强制更新,若是强制更新的话,则没有取消按钮,并且更新弹窗不可关闭。若是普通的更新的话则有暂不更新按钮,点击暂不更新更新弹窗会取消,但是当下次打开App的时候,弹窗提醒还是会弹窗。

普通更新包含暂不更新和立即更新两个按钮操作:
这里写图片描述

强制更新只有立即更新按钮,弹窗不可取消:
这里写图片描述

app更新操作:

app的更新操作就是下载App并安装了,下面我们还是分两部分看,应用市场的更新与应用内更新

  • App store更新App

在应用市场中更新App很简单就是执行简单的下载操作,然后顺着App的提醒,一步步安装即可,这里没有什么需要注意的地方。

  • 应用内更新

应用内更新操作主要是当用户点击了更新按钮之后执行的,下载,安装等逻辑,下面我们看一下友友用车应用内更新的实践。

应用内更新主要包含了:普通更新和强制更新两种,其中普通更新弹窗可以选择更新也可以选择忽略,而强制更新只能选择更新,并且更新弹窗不可取消。

下面的代码是执行下载操作的核心逻辑:

/**
     * 开始执行下载动作
     */
    private static void doDownLoad(final Activity mContext, String downloadUrl, final String actionButtonMsg, final boolean isFocusUpdate) {
        // 强制更新
        if (isFocusUpdate) {
            DownLoadDialog.updateRela.setVisibility(View.VISIBLE);
            DownLoadDialog.progressBar.setProgress(0);
            DownLoadDialog.progressBar.start();
            DownLoadDialog.updatePercent.setText("0%");
            DownLoadDialog.materialDialog.getPositiveButton().setEnabled(false);
            DownLoadDialog.materialDialog.getPositiveButton().setText("下载中");
        }
        Config.showToast(mContext, "开始下载安装包.......");
        // 删除下载的apk文件
        doDeleteDownApk(mContext);
        L.i("安装包下载地址:" + downloadUrl);
        DownloadManager.getInstance().cancelAll();
        DownloadManager.downloadId = DownloadManager.getInstance().add(DownloadManager.getDownLoadRequest(mContext, downloadUrl, new DownloadStatusListenerV1() {
            @Override
            public void onDownloadComplete(DownloadRequest downloadRequest) {
                L.i("onDownloadComplete_____...");
                // 设置按钮是否可点击
                showPositiveText(false, actionButtonMsg);
                if (isFocusUpdate) {
                    // 更新进度条显示
                    DownLoadDialog.updatePercent.setText("100%");
                    DownLoadDialog.progressBar.stop();
                } else {
                    String title = "正在下载友友用车...";
                    String content = "下载成功";
                    DownloadNotification.showNotification(mContext, title, content, DownloadNotification.notofyId);
                    // 关闭通知栏消息
                    UUApp.notificationManager.cancel(DownloadNotification.notofyId);
                }
                // 下载完成,执行安装逻辑
                doInstallApk(mContext);
                // 退出App
                UUApp.getInstance().exit();
            }

            @Override
            public void onDownloadFailed(DownloadRequest downloadRequest, int errorCode, String errorMessage) {
                L.i("onDownloadFiled______...");
                L.i("errorMessage:" + errorMessage);
                // 设置按钮是否可点击
                showPositiveText(false, actionButtonMsg);
                if (isFocusUpdate) {
                    // DownLoadDialog.progressBar.stop();
                    DownLoadDialog.updatePercent.setText("更新失败");
                } else {
                    String title = "正在下载友友用车...";
                    String content = "下载失败";
                    DownloadNotification.showNotification(mContext, title, content, DownloadNotification.notofyId);
                }
            }

            @Override
            public void onProgress(DownloadRequest downloadRequest, long totalBytes, long downloadedBytes, int progress) {
                if (lastProgress != progress) {
                    lastProgress = progress;
                    L.i("onProgress_____progress:" + progress + "  totalBytes:" + totalBytes + "  downloadedBytes:" + downloadedBytes);
                    // 设置按钮是否可点击
                    showPositiveText(true, actionButtonMsg);
                    // 强制更新则更新进度条
                    if (isFocusUpdate) {
                        String content = downloadedBytes * 100 / totalBytes + "%";
                        float result = progress / (float)100.00;
                        DownLoadDialog.progressBar.setProgress(result);
                        DownLoadDialog.updatePercent.setText(content);
                    } else {
                        String title = "正在下载友友用车...";
                        String content = downloadedBytes * 100 / totalBytes + "%";
                        DownloadNotification.showNotification(mContext, title, content, DownloadNotification.notofyId);
                    }
                }
            }
        }));
    }

这里的下载操作包含了三个回调方法:

  • onDownloadComplete()

  • onDownloadFailed()

  • onProgress()

其中onDownlaodComplete方法在下载完成时回调,onDownloadFailed方法在下载失败是回调,而onProgress方法则用于刷新下载进程,我们在onProcess方法中更新通知栏下载进度,具体我们可以看一下更新通知栏消息的方法:

/**
     * 更新通知栏显示
     * @param title
     * @param content
     * @param notifyId
     */
    public static void showNotification(Activity mContext, String title, String content, int notifyId) {
        NotificationCompat.Builder mNotifyBuilder = new NotificationCompat.Builder(mContext)
                .setSmallIcon(R.mipmap.icon)
                .setContentTitle(title)
                .setContentText(content)
                .setSmallIcon(Android.R.drawable.stat_sys_download);

        Notification notification = mNotifyBuilder.build();
        // notification.flags = Notification.FLAG_NO_CLEAR;
        UUApp.notificationManager.notify(notifyId, notification);
    }

而在onDownloadFailed方法中,执行的代码逻辑是提示用户下载失败,
而在onDownloadComplete方法中,执行安装下载apk文件的操作,我们可以继续看一下我们是如何执行安装逻辑的。

/**
     * 执行安装apk文件
     */
    private static void doInstallApk(Activity mContext) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setDataAndType(Uri.fromFile(new File(DownloadManager.getApkPath(mContext))),
                "application/vnd.Android.package-archive");
        mContext.startActivity(intent);
    }

这段代码会调用Android的安装apk程序,这样我们就执行了下载文件的安装操作,不同的手机安装程序及展示界面略有不同。

总结:

  • App升级操作分为两种,在应用市场提示升级和在应用内提示升级,而在应用内提示升级可以继承第三方升级API(如:友盟),也可以自己实现;

  • 应用升级的提示主要逻辑是根据服务器端的APK版本号与本地的应用版本号对比,若服务器端的应用版本号高于本地版本号,则说明应用需要升级;

  • 应用升级可以分为普通升级和强制升级两种,一般不太建议使用强制升级(用户体验很差),除非是一些严重的线上bug;

  • App的更新操作包含下载与安装两部分,下载操作时可以选择继承第三方服务,也可以自己实现。


另外对产品研发技术,技巧,实践方面感兴趣的同学可以参考我的:
Android产品研发(一)–>实用开发规范
Android产品研发(二)–>启动页优化
Android产品研发(三)–>基类Activity
Android产品研发(四)–>减小Apk大小
Android产品研发(五)–>多渠道打包
Android产品研发(六)–>Apk混淆
Android产品研发(七)–>Apk热修复
Android产品研发(八)–>App数据统计
Android产品研发(九)–>App网络传输协议
Android产品研发(十)–>不使用静态变量保存数据
Android产品研发(十一)–>应用内跳转scheme协议
Android产品研发(十二)–>App长连接实现
Android产品研发(十三)–>App轮询操作


本文以同步至github中:https://github.com/yipianfengye/AndroidProject,欢迎star和follow


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android产品研发(十四)-->App升级与更新 的相关文章

  • SVN文件夹图标不正常显示解决方案(win10)android studio

    在使用Android Studio提交代码时发现svn图标莫名其妙的不显示 其他操作都正常 在网上搜了一堆资料都有各种说法 结合了操作 一步步来试终于给我找到了 在这我自己总结一下 一部分也是拷贝别的图片 写一篇清楚文章好希望能帮助和我遇到
  • frp实现内网穿透(内网服务器到公网访问的方案)

    目录 背景 一 frp的简介 二 Frp Server的配置 三 Frp Client的配置 背景 我使用python写了一个http后端 如代码所示 ip为10 1 136 73 port为8000 现在需要把http后端在公网可以被使用
  • echarts 中x轴 设置步长,间隔的距离

    如果你已经使用了 echarts xAxis axisLabel interval 5 在 xAxis 下面 axisLabel 里面的 interval 值即可 interval 为 0 时 所有的标签都显示出来 interval 表示步
  • 详解ThreadLocal

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 1 ThreadLocal介绍 1 1 官方介绍 1 2 基本用法 1 2 1 常用方法 1 2 2 使用案例 1 3 ThreadLocal与synchroniz
  • [Python人工智能] 四.TensorFlow创建回归神经网络及Optimizer优化器

    从本篇文章开始 作者正式开始研究Python深度学习 神经网络及人工智能相关知识 前一篇文章讲解了TensorFlow基础和一元直线预测的案例 以及Session 变量 传入值和激励函数 这篇文章将详细介绍TensorFlow创建回归神经网
  • 少儿编程有必要吗?

    这几年 人工智能正以难以想象的速度向前开展 AlphaGo赢了柯洁 百度无人巴士量产 京东开端启用机器人送快递 谷歌的AI都学会了自行freestyle 科技的推翻式立异 随之引发教育风向大变革 除了语数外 老三样 的根底教育外 一门新兴学
  • STM32驱动HC05蓝牙串口通信模块

    前言 时不可以苟遇 道不可以虚行 今天分享一下最近学习的 HC05 蓝牙模块 通过用 手机蓝牙控制 STM32 单片机 进行 点灯 传输数据 显示波形 等基础操作 一 介绍 HC05模块是一款高性能主从一体蓝牙串口模块 说白了 只是个蓝牙转
  • oracle排序后从相同的顺序中随机取一行

    要求 要求从这个表取数据 v2字段相同的 随机取一个出来 第1 2随机取一行 第5 6 7行随机取一行 其他的3 4行都保留 效果展示 查询语句写法 Select s from select t row number over partit
  • 【数据分析】数据分析方法(六):相关分析 & 群组分析

    数据分析方法 六 相关分析 群组分析 1 相关分析方法 当我们研究两种或者两种以上数据之间有什么关系的时候 就要用到相关分析 在解决问题的过程中 相关分析可以帮助我们扩大思路 将视野从一种数据扩大到多种数据 通过计算相关系数 我们可以看到两
  • 栈的应用——深度优先搜索(走迷宫)

    栈应用到走迷宫 寻路算法 的做法 迷宫就是下图所示的这种 这次主要是先用代码画出一个迷宫 利用二维数组 然后寻路走到出口 代码如下 在C 中运行 mystack h include
  • 统计文件数目

    编写一个程序 统计某个目录下 含子目录 里的所有目录数和文件数 import os path 在引号里加入需要统计的文件夹目录 def list files path file num 0 files num 0 for root dirs
  • 快手只发作品不直播的赚钱方法

    快手只发作品不直播的赚钱方法 玩快手也不开直播 只发段子 有这三种变现方式 我只告诉你可以往下看吗 以下几种方式 你千万记住点开左上角的三条杠 点击更多点击创作者中心 这个时候我们到里面找到什么任务中心点进来 这个时候到你该转米的时候了 随
  • spring 增强顺序改变的原因

    spring 增强顺序改变的原因 spring 5 2 7版本正式改变增强的顺序 网上的大部分文章主要从spring的代码层面的变动 来解释增强顺序的变动 而我想要了解的是这个变动 是因为导致了某些bug的出现吗 所以自己在github和s
  • Java中数据类型分类?

    转自 Java中数据类型分类 下文笔者讲述java中数据类型的分类 如下所示 基本数据类型boolean 布尔类型 short 短整型 int 整型 long 长整型 byte 字节型 char 字符型 float 单精度浮点型 doubl
  • 数据对象属性分类

    数据集由数据对象组成 一个数据对象代表一个实体 数据对象又称样本 实例 数据点或对象 属性 attribute 是一个数据字段 表示数据对象的一个特征 属性向量 或特征向量 是用来描述一个给定对象的一组属性 属性有不同类型 标称属性 nom
  • 线程基础---基础方法

    线程启动 在Thread类中注释标明有两种方式创建新的执行线程 一种是声明一个类是Thread的子类 这个子类应该重写类Thread的run方法 然后可以分配和启动子类的实例 创建线程的另一种方法是声明一个实现Runnable接口的类 这个
  • STM32F0开发笔记8: 在keil中使用不初始化变量

    我们进行程序设计的时候 都会知道 系统上电或复位时 会执行变量初始化操作 但是有些情况下 我们并不希望变量初始化 例如 在系统异常复位发生后 我们希望系统能够迅速恢复复位前的现场状况 这样就希望变量能够保留原先的值 而不被初始化 实际上 大
  • 第八届“泰迪杯”数据挖掘挑战赛C题“泰迪杯”奖论文(基于卷积神经网络及集成学习的网络问政平台留言文本挖掘与分析)

    目 录 第一章 引言 1 1挖掘背景 1 2挖掘意义 1 3问题描述 第二章 群众留言分类 2 1数据准备 2 1 1数据描述 2 1 2数据预处理 2 2特征提取 2 3建立模型 2 3 1卷积神经网络 2 3 2模型设计 2 3 3模型
  • [pg]数据库的并发控制

    参考 章 13 并发控制 数据库并发事务控制四 postgresql数据库的锁机制二 表锁 PostgreSQL 事务处理和并发控制 PostgreSQL并发控制 MVCC 事务 事务隔离级别 数据库中Select For update语句

随机推荐

  • Python使用Opencv图像处理方法完成手势识别(三)tkinter制作GUI界面

    前面对手势识别已经差不多完成 这一章来制作一个手势识别GUI界面和说一下精确度不够问题所在 首先是精确度不够的问题 让手势更规范 手掌张开点 首先应该调节Hsv阈值 因为手掌和环境颜色与我的可能有差异 调整面积 周长阈值 距离阈值 面积阈值
  • 2022黑马SpringBoot跟学笔记(一)

    2022黑马SpringBoot跟学笔记一 SpringBoot 1 SpringBoot简介 1 1 SpringBoot快速入门 1 1 1 开发步骤 1 1 1 1 创建新模块 1 1 1 2 创建 Controller 1 1 1
  • R语言调色板及填充实战:scale_colour_brewer与scale_fill_brewer函数

    R语言调色板及填充实战 scale colour brewer与scale fill brewer函数 在使用ggplot2进行数据可视化时 我们经常需要对图像的颜色进行调整以增强其视觉效果 R语言中提供了scale colour brew
  • 告别宽表,用 DQL 成就新一代 BI

    BI商业智能这个概念已经提出好几十年了 这个概念本身比较宽泛 不同人也有不同的理解和定义 但落实到技术环节 特别是面向业务用户的环节 所称的BI 基本就是指的多维分析或者自助报表 不管是叫自助报表还是多维分析 也都是一回事 都是让用户自己去
  • 数据库中查询的数据是多条,可是显示出来的只有一条,为什么?

    1 首先附上代码 public List
  • Redis学习笔记5:Jedis、RedisTemplate

    一 Jedis是什么 Jedis是Redis官方推荐的Java连接开发工具 要在Java开发中使用好Redis中间件 必须对Jedis熟悉才能写成漂亮的代码 详细了解 https www jianshu com p a1038eed6d44
  • nacos登录 提示权限认证失败 没有命名空间的访问权限

    前言 环境 centos7 9 nacos 2 2 2 问题描述 最近在部署nacos 2 2 2版本的时候 这是目前2023年4月份最新版本 发现按照start out日志给出的登录地址 http 192 168 158 128 8848
  • TensorFlow.js 和 Node-RED 图像识别应用程序

    在本文中 我们将看看您可以将这两种流行的开源软件工具组合起来做什么 使用 Node RED 创建示例图像识别流程 我们的目标是在 Node RED 中创建一个流来识别图像中的对象 如下面的屏幕截图所示 使用黄色节点组件从浏览器上传文件后 可
  • 【韧性设计模式】韧性设计模式:重试、回退、超时、断路器

    什么是韧性 软件本身并不是目的 它支持您的业务流程并使客户满意 如果软件没有在生产中运行 它就无法产生价值 然而 生产性软件也必须是正确的 可靠的和可用的 当谈到软件设计中的弹性时 主要目标是构建健壮的组件 这些组件既可以容忍其范围内的故障
  • IOS App 的图标和启动图的烦恼

    前言 好多iOS App的开发者都会面临的一个问题 那就是 App 的图标 icon 和启动图 launch images 一些没有经验 指的是没做过App审计的 的UI设计师 不知道该怎么做 他就会问开发者 这个时候就是你展示的时候 其实
  • git format-patch命令介绍

    git format patch的使用 1 在dev1分支上 打出所有dev1分支在master分支基础上的patch git format patch master 结果为d1c1 patch d1c2 patch 2 在dev1分支上
  • Win10 修改JAVAHOME环境变量无效,默认java -version未改变

    Win10 修改JAVAHOME环境变量无效 默认java version不变问题 1 造成该原因的三个路径 环境变量 2 解决方法 1 造成该原因的三个路径 环境变量 1 C Program Files x86 Common Files
  • 使用R读取并查看数据

    本篇文章介绍如何使用R读取并查看数据 包含一些最基础的函数使用方法和说明 后面还会陆续介绍数据清洗 匹配和提取等相关的操作 查看函数帮助 对于新手来说 在使用R时最重要的是了解不同函数的使用方法 很多时候我们都是边用边学的状态 拿到一个函数
  • CRM部署以流程为核心

    大多数国内企业还不太习惯流程 尤其是精细化管理的流程 如果有流程 也可能是粗放型的流程 因为我们还是更注重结果 不太习惯过程管理 我们还是更注重每个人干什么 而不太习惯整体协同 在这样的基础上 大部分企业在部署CRM的时候 就或左或右的进入
  • GitHub Copilot收费了

    今天一早收到邮件看到提示收费的邮件 才想起来还有个这个插件 废话少说直接链接 https github com features copilot 个人看法 功能 首先功能确实是比一般的代码提示强不少 但要做到 我 hello GitHub
  • vue-cli项目中静态文件过大的问题

    我们最近在做一个项目时 因为static 文件中含有几个视频文件 导致static 文件过大 build 的时候非常慢 在这我大概介绍一下上面问题的解决方法 首先我们先要了解一下 webpack 的一个插件 拷贝插件 copy webpac
  • Linux系统安装VMware Tools和同W7共享文件遇到的问题

    我用的系统为CentOS6 5 minimal版本 minimal版本默认不启动网络 所以要自己配置 vi etc sysconfig network script ifcfg eth0 1 NM CONTROLLED no 修改为no不依
  • C# Task Cancellation总结

    1 调用cancellationTokenSource Cancel 时 只有在task的函数体内使用token ThrowIfCancellationRequested 方法时才会触发ContinueWith Action
  • Java jdk1.5 新特性讲解

    JDK1 5 可以说是java 最经典的一个版本了 在 jdk1 5 发布时 就因他的改动大 而命令为jdl5 0 为后来 java 的壮大立下了汗马之劳 有网友在面试的时候被问到 jdk新特性 我这里索性就从 jdk1 5的特性说到1 8
  • Android产品研发(十四)-->App升级与更新

    转载请标明出处 一片枫叶的专栏 上一篇文章中我们讲解了Android app中的轮询操作 讲解的内容主要包括 我们在App中使用轮询操作的情景 作用以及实现方式等 一般而言我们使用轮询操作都是通过定时任务的形式请求服务器并更新用户界面 轮询