首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >自定义Androidk全量更新组件

自定义Androidk全量更新组件

作者头像
饮水思源为名
发布于 2018-09-06 05:01:02
发布于 2018-09-06 05:01:02
1.3K00
代码可运行
举报
文章被收录于专栏:Android小菜鸡Android小菜鸡
运行总次数:0
代码可运行

自动更新功能对于一个APP来说是必备的功能,特别是对于未投放市场下载的APP,每次都让用户删掉原来的,再下载新的版本,肯定是不合适的。

实现思路:

  1. 后台提供接口,返回服务端版本号serviceVersion以及APK下载地址
  2. 前端对接接口,用拿到的serviceVersion和APK配置的localVersion比较,如果serviceVersion>localVersion则提示可以更新,通过获取的APK下载地址下载,然后通过api打开安装完成更新。

注意:

  1. localVersion笔者使用的是versionCode,可以再AndroidManifest中配置,通过java代码获取。笔者与后台约定了Code的规则,采用更新时间编辑,例如2018年8月2号,则versionCode=“180802”
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.mintu.dcdb"
    android:versionCode="180802"
    android:versionName="3.6">
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  /**
     * 获取当前本地apk的版本号
     * @param mContext
     * @return
     */
    public static int getVersionCode(Context mContext) {
        int versionCode = 0;
        try {
            //获取软件版本号,对应AndroidManifest.xml下android:versionCode
            versionCode = mContext.getPackageManager().
                    getPackageInfo(mContext.getPackageName(), 0).versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return versionCode;
    }
  1. 对于Android7.0以上的手机,打开附件做了改变,无法使用以往的uri发布意图,详情可见笔者之前的一篇文章。Android7.0以上版本打开附件失败问题
  2. 本文的文件下载、附件打开方法使用的是笔者封装的OkHttp3工具类,使用者可以自己随意替换。只要将APK从url上下载下来,用API打开即可。

核心代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
     /***
     * 检查是否更新版本
     */
    private void checkVersion() {
        if (Integer.parseInt((String) sharedPreferencesUtil.getData(Constant.VERSION_CODE_LOCAL,"")) < Integer
                .parseInt(CommonUtil.getInstance().isNull(sharedPreferencesUtil.getData(Constant.VERSION_CODE,"")) ? "0"
                        : (String) sharedPreferencesUtil.getData(Constant.VERSION_CODE,""))) {
            showDialog(new DownLoadBroadCastReceiver());
        }else{
            if(!isAutoUpdate) Toast.makeText(activity, "未检查到新版本", Toast.LENGTH_SHORT).show();
        }
    }  
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /***
     * 开线程下载
     */
    public void createThread(final String downUrl) {
        final Message message = new Message();
        if (SystemUtils.isNetworkAvailable(getApplicationContext())) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        OkHttpUtil.getInstance().download(downUrl, newApkUrl, file_name, new OkHttpUtil.OnDownloadListener() {
                            @Override
                            public void onDownloadSuccess(File downfile, File file) {
                                hand();
                                stopSelf();
                            }
                            @Override
                            public void onDownloading(int progress, File file) {
                                LogUtil.i("update progress---"+progress);
                                broadCast.putExtra("download", progress);
                                sendBroadcast(broadCast);
                            }
                            @Override
                            public void onDownloadFailed(String error) {
                            }
                        });
                    } catch (Exception e) {
                    }
                }
            }).start();
        } else {
            Toast.makeText(getApplicationContext(), "网络无连接,请稍后下载!",
                    Toast.LENGTH_SHORT).show();
        }
    }
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 /**
     * 打开附件的方法
     * @param f
     * @param context
     */
    public void openFile(File f, Context context) {
        Log.i(LOGTAG, "正在打开附件打--------" + f.getName()+"。附件大小为"+f.length());
        try {
            String end = f.getName().substring(f.getName().lastIndexOf(".")
                    + 1, f.getName().length()).toLowerCase();
            if(end.equals("amr")){
                AudioRecoderUtils.getInstance().playerStart(f.getAbsolutePath());
            }else {
                Intent intent = new Intent();
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.setAction(android.content.Intent.ACTION_VIEW);
                intent.addCategory("android.intent.category.DEFAULT");
      /* 调用getMIMEType()来取得MimeType */
                String type = getMIMEType(f);
      /* 设置intent的file与MimeType */
                if(Build.VERSION.SDK_INT>=24){
                    Uri contenturi=FileProvider.getUriForFile(context, "com.mintu.dcdb.fileprovider",f);
                    intent.setDataAndType(contenturi,type);
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, contenturi);
                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }else{
                    intent.setDataAndType(Uri.fromFile(f), type);
                }
                context.startActivity(intent);
            }
        } catch (Exception e) {
//            Toast.makeText(context,"打开附件---"+f.getName()+",发生了错误",Toast.LENGTH_SHORT).show();
            Log.e(LOGTAG, "打开附件" + f.getName() + "报错了,错误是-----" + e.getMessage());
        }
    }

项目中全量更新源码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.mintu.dcdb.util.updateAppUtil;

import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.Html;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.google.gson.Gson;
import com.mintu.dcdb.R;
import com.mintu.dcdb.config.Constant;
import com.mintu.dcdb.config.RequestUrl;
import com.mintu.dcdb.main.bean.UpdateBean;
import com.mintu.dcdb.main.view.UpdateDialog;
import com.mintu.dcdb.util.LogUtil;
import com.wusy.wusylibrary.base.BaseActivity;
import com.wusy.wusylibrary.util.CommonUtil;
import com.wusy.wusylibrary.util.OkHttpUtil;
import com.wusy.wusylibrary.util.SharedPreferencesUtil;

import java.io.IOException;
import java.util.ArrayList;

import okhttp3.Call;

/**
 * Created by XIAO RONG on 2018/7/19.
 */

public class UpdateAppUtil {
    private SharedPreferencesUtil sharedPreferencesUtil;
    private BaseActivity activity;
    private  ProgressDialog m_pDialog;
    private boolean isAutoUpdate=false;

    public UpdateAppUtil(BaseActivity activity,boolean isAutoUpdate){
        sharedPreferencesUtil=SharedPreferencesUtil.getInstance(activity);
        this.activity=activity;
        this.isAutoUpdate=isAutoUpdate;
    }

    public void updateApp(){
        String url = RequestUrl.getInstance().getUpdateUrl((String) SharedPreferencesUtil.getInstance(activity).getData(Constant.VERSION_CODE, ""));
        OkHttpUtil.getInstance().asynGet(url, new OkHttpUtil.ResultCallBack() {
            @Override
            public void successListener(Call call, String responseStr) {
                LogUtil.i("update result str--"+responseStr);
                Gson gson = new Gson();
                UpdateBean bean = gson.fromJson(responseStr, UpdateBean.class);
                sharedPreferencesUtil.saveData(Constant.VERSION_CODE,bean.getVersion());
                sharedPreferencesUtil.saveData(Constant.VERSION_URL,bean.getUrl());
                sharedPreferencesUtil.saveData(Constant.VERSION_CONTENT,bean.getContent());
                sharedPreferencesUtil.saveData(Constant.VERSION_PATCH_FILE_PATH,bean.getPatchFilePath());
                sharedPreferencesUtil.saveData(Constant.VERSION_IS_PATCH_FILE_PATH,bean.getIsPatchFilePath());
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        checkVersion();
                    }
                });
            }

            @Override
            public void failListener(Call call, IOException e) {

            }
        });
    }
    /***
     * 检查是否更新版本
     */

    private void checkVersion() {

        if (Integer.parseInt((String) sharedPreferencesUtil.getData(Constant.VERSION_CODE_LOCAL,"")) < Integer
                .parseInt(CommonUtil.getInstance().isNull(sharedPreferencesUtil.getData(Constant.VERSION_CODE,"")) ? "0"
                        : (String) sharedPreferencesUtil.getData(Constant.VERSION_CODE,""))) {
            showDialog(new DownLoadBroadCastReceiver());
        }else{
            if(!isAutoUpdate) Toast.makeText(activity, "未检查到新版本", Toast.LENGTH_SHORT).show();
        }
    }
    private void showDialog(final BroadcastReceiver receiver) {
        final Dialog alert = new UpdateDialog(activity, R.style.MyDialogStyle);
        alert.setContentView(R.layout.upgrade_dialog);
        TextView tvView = (TextView) alert.findViewById(R.id.upgradeText);
        if (!CommonUtil.getInstance().isNull(sharedPreferencesUtil.getData(Constant.VERSION_CONTENT, ""))) {
            tvView.setText(Html.fromHtml((String) sharedPreferencesUtil.getData(Constant.VERSION_CONTENT, "")));
        }

        tvView.setWidth(activity.getWindowManager().getDefaultDisplay().getWidth() * 3 / 4);
        Button sureBtn = (Button) alert.findViewById(R.id.btn_sure);
        Button cancleBtn = (Button) alert.findViewById(R.id.btn_cancle);

        sureBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {


                ArrayList<String> actionList = new ArrayList<>();
                actionList.add(Constant.DOWNLOAD_ACTION);
                activity.addBroadcastAction(actionList, receiver);
                Intent updateIntent = new Intent(activity,
                        UpdateService.class);
                updateIntent.putExtra("app_name", activity.getResources()
                        .getString(R.string.app));

                activity.startService(updateIntent);
                alert.dismiss();
            }
        });
        cancleBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                alert.dismiss();
            }
        });
        alert.show();
    }
    class DownLoadBroadCastReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
                case Constant.DOWNLOAD_ACTION:
                    int progress = intent.getIntExtra("download", 0);
                    showProgress(progress);
                    break;

                default:
                    break;
            }

        }
    }
    private void showProgress(Integer progress) {
        if (m_pDialog == null) {
            createProgressDialog("正在更新请稍后...");
        }
        m_pDialog.setProgress(progress);
        if (progress == 100) {
            m_pDialog.dismiss();
        }
    }
    private void createProgressDialog(String title) {

        // 创建ProgressDialog对象
        m_pDialog = new ProgressDialog(activity);

        // 设置进度条风格,风格为长形
        m_pDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);

        // 设置ProgressDialog 标题
        m_pDialog.setTitle(title);
        // 设置ProgressDialog 进度条进度
        m_pDialog.setProgress(100);

        // 设置ProgressDialog 的进度条是否不明确
        m_pDialog.setIndeterminate(false);

        // 设置ProgressDialog 是否可以按退回按键取消
        m_pDialog.setCancelable(true);
        // 设置点击进度对话框外的区域对话框不消失
        m_pDialog.setCanceledOnTouchOutside(false);
        // 让ProgressDialog显示
        m_pDialog.show();
    }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.mintu.dcdb.util.updateAppUtil;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

import com.mintu.dcdb.config.Constant;
import com.mintu.dcdb.util.LogUtil;
import com.mintu.dcdb.util.SystemUtils;
import com.wusy.wusylibrary.util.CommonUtil;
import com.wusy.wusylibrary.util.OkHttpUtil;
import com.wusy.wusylibrary.util.SharedPreferencesUtil;

import java.io.File;

/**
 *
 * Filename: UpdateService.java Description: 今天修改了当增量包合成失败的时候,重新下载整个最新的apk
 * Company: minto
 *
 * @author: chjr
 * @version: 2.8.0 Create at: 2016年5月20日 下午3:25:52
 *
 */
public class UpdateService extends Service {
    private final int TIMEOUT = 10 * 1000;// 超时
    private final int DOWN_OK = 1;
    private final int DOWN_ERROR = 0;
    private String app_name;
    private String file_name;

    private NotificationManager notificationManager;
    private Notification notification;

    private Intent updateIntent;
    private PendingIntent pendingIntent;

    private int notification_id = 0;
    ProgressDialog m_pDialog;
    private File updateDir = new File(Constant.FILEDIR);
    File updateFile;
    SharedPreferencesUtil vsPreference;
    String packageName = "com.minto.workhi";
    String patchUrl = Constant.FILEDIR;
    String newApkUrl = Constant.FILEDIR;
    private String downUrl = "";
    private int flag = 2;
    private Intent broadCast;
    @Override
    public IBinder onBind(Intent arg0) {
        stopSelf();
        return null;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        vsPreference = SharedPreferencesUtil.getInstance(getApplicationContext());
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (CommonUtil.getInstance().isNull(intent)) {
            stopSelf();
            Log.d("UpService", intent + "--------------------");
        } else {

            app_name = intent.getStringExtra("app_name");
            file_name = app_name + ".apk";


            patchUrl = patchUrl + app_name + ".patch";
            // 创建文件
            CommonUtil.getInstance().createFile(app_name + ".patch");
            CommonUtil.getInstance().createFile(file_name);
            updateFile = new File(updateDir + "/" + app_name + ".patch");
            if(flag==1)flag=2;
            broadCast = new Intent();
            broadCast.setAction(Constant.DOWNLOAD_ACTION);
            broadCast.putExtra("download", 0);
            sendBroadcast(broadCast);
            downFile();
        }
        return super.onStartCommand(intent, flags, startId);
    }

    /***
     * 开线程下载
     */
    public void createThread(final String downUrl) {
        final Message message = new Message();
        if (SystemUtils.isNetworkAvailable(getApplicationContext())) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        OkHttpUtil.getInstance().download(downUrl, newApkUrl, file_name, new OkHttpUtil.OnDownloadListener() {
                            @Override
                            public void onDownloadSuccess(File downfile, File file) {
                                hand();
                                stopSelf();
                            }
                            @Override
                            public void onDownloading(int progress, File file) {
                                LogUtil.i("update progress---"+progress);
                                broadCast.putExtra("download", progress);
                                sendBroadcast(broadCast);
                            }
                            @Override
                            public void onDownloadFailed(String error) {
                            }
                        });
                    } catch (Exception e) {
                    }
                }
            }).start();
        } else {
            Toast.makeText(getApplicationContext(), "网络无连接,请稍后下载!",
                    Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 根据类型下载不同的文件
     *
     * @exception
     * @since 1.0.0
     */
    protected void downFile() {
        // 下载增量包路径
        if (1 == flag) {
            downUrl = (String) vsPreference.getData(Constant.VERSION_PATCH_FILE_PATH,"");
        } else if (2 == flag) {
            downUrl = (String) vsPreference.getData(Constant.VERSION_URL,"");
        }
        createThread(downUrl);
    }

    /**
     * 根据不同的包安装
     *
     * @exception
     * @since 1.0.0
     */
    protected void hand() {
        // 增量合成方法
        if (1 == flag) {
//          ApkUpdate update = new ApkUpdate(getApplicationContext(),
//                  packageName, patchUrl, newApkUrl);
//          // 检查低版本的apk 是否存在
//          if (update.isOldApkExist()) {
//              // 判断是否成功合成新的apk
//              if (update.newApkGet()) {
//                  update.installApk();
//                  delData();
//              } else {
//                  // Toast.makeText(getApplicationContext(),
//                  // "下载失败!:" + newApkUrl + "增量包地址:" + patchUrl,
//                  // Toast.LENGTH_SHORT).show();
//                  flag = 2;
//                  downFile();
//              }
//          }
        }
        // 整包安装方法
        else if (2 == flag) {
            File apkFile = new File(newApkUrl, file_name);
            OkHttpUtil.getInstance().openFile(apkFile,this);
        }
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.08.09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
持续发布公众号文章后终于吸引到同频的人找我一起合作做个小区智慧物业系统!
最近几乎每隔一天笔者就会发布一篇公众号文章,很明显的正向反馈也来了,一方面是公众号每天都有新的粉丝添加关注,每天的流量费也在慢慢地涨。最令我惊喜的是上周三把一篇刚公众号文章SpringBoot 整合 Vue3 与 Element-Plus 完成系统页面表单和表格功能开发 分享到自己的朋友圈后,就有一个之前给我推荐工作的技术小哥找我一起做一个小区智慧物业系统。
用户3587585
2024/05/20
3420
持续发布公众号文章后终于吸引到同频的人找我一起合作做个小区智慧物业系统!
智慧小区云平台解决方案有哪些_智慧社区平台解决方案
智慧云社区是智慧城市概念之下的社区管理的一种新理念,是新形势下社会管理创新的 一种新模式。智慧云社区是指充分利用物联网、云计算、移动互联网等新一代信息技术,为 居民提供一个安全、舒适、便利的生活环境,从而形成基于信息化、智能化社会管理与服务 的新型管理模式的社区。
全栈程序员站长
2022/11/19
2.6K0
智慧小区云平台解决方案有哪些_智慧社区平台解决方案
基于react/vue生态的前端集成解决方案探索与总结
接下来我将介绍项目的基本架构和设计思路,并使用shell脚本来实现自动化安装技术集成方案。最后会在文章的末尾附上github地址,感兴趣的朋友可以研究参考,也可直接使用。如果还不了解shell,可以看我的上一篇文章vue/react项目中不可忽视的自动化部署方案
徐小夕
2019/08/09
1.2K0
基于react/vue生态的前端集成解决方案探索与总结
基于react/vue生态的前端集成解决方案探索与总结
本文主要总结了笔者在多年前端工作中的技术方案选型,结合各种不同类型的项目,搭建了一套完整的前端集成解决方案,主要包含如下内容:
徐小夕
2019/07/18
9540
一步步使用SpringBoot结合Vue实现登录和用户管理功能
前后端分离开发是当今开发的主流。本篇文章从零开始,一步步使用SpringBoot结合Vue来实现日常开发中最常见的登录功能,以及登录之后对用户的管理功能。通过这个例子,可以快速入门SpringBoot+Vue前后端分离的开发。
三分恶
2021/02/01
2.6K0
一步步使用SpringBoot结合Vue实现登录和用户管理功能
SpringBoot跨域及后端解决方案
Access-Control-Allow-Origin:http://somehost.com 表示允许http://somehost.com发起跨域请求。
框架师
2021/08/05
4.8K0
mybatisplus+swagger【后端专题】
如果两个对象相等,那么它们的hashCode()值一定相同(这里的相等是指,通过equals()比较两个对象时返回true) ​ 如果两个对象hashCode()相等,它们并不一定相等。在散列表中hashCode()相等,即两个键值对的哈希值相等。 然而哈希值相等,并不一定能得出键值对相等,就出现所谓的哈希冲突场景,还需判断equals⽅法判断对象是否相等
高大北
2022/11/14
2.3K0
mybatisplus+swagger【后端专题】
前端快速入门之概述
以下是对(前端)可视化工作的并不系统的总结,新手向,主要是想说一下前端如何组成、功能如何实现、资源如何请求,进而说到数据如何显示,并在最后列举了一些十分重要的参考资料(非常重要)。
ZONGLYN
2019/08/08
1.6K0
瑞吉外卖实战项目全攻略——优化篇第三天
该系列将记录一份完整的实战项目的完成过程,该篇属于优化篇第三天,主要负责完成前后端分离问题
秋落雨微凉
2022/11/14
7230
瑞吉外卖实战项目全攻略——优化篇第三天
谷粒学院day0&day1——项目介绍与mybatis plus入门
java基础、数据库(mysql+jdbc),javaEE,SSM框架,redis,nigix,idea,maven,git,springboot
半旧518
2022/10/26
1.1K0
谷粒学院day0&day1——项目介绍与mybatis plus入门
SpringBoot项目拥抱Mybatis-Plus持久层框架实践,全面提升数据库层面开发效率!
自从 Mybatis-Plus推出以来,越来越多的公司在自己的项目中选择Mybatis-Plus框架替换了持久层框架Mybatis。因为Mybatis-Plus用起来既有Mybatis的手写复杂sql语句的灵活性,又兼具了Spring Data Jpa自动提供了单表CRUD操作的通用框架方法,只需要自定义一个Mapper并继承BaseMapper即可,为开发人员使用持久层框架节约了很多工作量。同时Mybatis-Plus还提供了链式查询和分页查询等诸多通用API方法,开发人员可直接使用。本文的目的是指导新手如何在自己的spring-boot项目中集成mybatis-plus持久层框架完成数据的增删改查功能。
用户3587585
2021/12/07
2K0
SpringBoot项目拥抱Mybatis-Plus持久层框架实践,全面提升数据库层面开发效率!
Mybatis-plus基础知识梳理—-(基础知识)
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
全栈程序员站长
2022/06/30
9780
Spring Boot + Vue + Shiro 实现前后端分离、权限控制
本文总结自实习中对项目的重构。原先项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Freemarker模版的ModelAndView,逐渐有了前后端分离的想法,由于之前,没有接触过,主要参考的还是网上的一些博客教程等,初步完成了前后端分离,在此记录以备查阅。
芋道源码
2019/06/15
3.9K0
学习mybatis-plus,这一篇就够了
因为公司的技术主管推荐我使用mybatis-plus插件之后,自己就跟着网上的教程学习了一下,学完之后,我尼玛是真的香
萌萌哒的瓤瓤
2021/01/13
5000
学习mybatis-plus,这一篇就够了
web前端开发需要学什么(包含前端学习路线)
初学者随便找一个就好,喜欢轻量级的使用vscode,喜欢工业风一样的使用webstorm;
孙叫兽
2021/03/23
3.3K0
重学Springboot系列之整合数据库开发框架---中
在实际的开发过程中,由于业务的复杂性,通常并不能做到一个model实体贯穿持久层、服务层、控制层。通常需要进行实体对象java bean的赋值转换。
大忽悠爱学习
2021/12/07
1.7K0
重学Springboot系列之整合数据库开发框架---中
Spring学习笔记
```java package com.kob.backend.controller.pk;
h3110_w0r1d
2024/02/19
1870
Spring学习笔记
怒肝最新保姆级前端学习路线,速成贴心全面!
是不是有点儿长,感觉要被劝退了?不过不用担心,为了帮助大家更轻松地了解前端知识体系,我对这份路线撒了点 糖 ~
程序员鱼皮
2021/06/07
1.2K0
医疗项目中所用到的技术点——以MyBatis-Plus为技术案例
惠医疗即为网上预约挂号系统,网上预约挂号是近年来开展的一项便民就医服务,旨在缓解看病难、挂号难的就医难题,许多患者为看一次病要跑很多次医院,最终还不一定能保证看得上医生。网上预约挂号全面提供的预约挂号业务从根本上解决了这一就医难题。随时随地轻松挂号!不用排长队!
用户10196776
2023/10/17
5260
最新Web前端面试题精选大全及答案「建议收藏」
JPEG,GIF,PNG,最流行的是jpeg格式,可以把文件压缩到最小 在ps以jpeg格式存储时,提供11级压缩级别
全栈程序员站长
2022/08/12
1.8K0
最新Web前端面试题精选大全及答案「建议收藏」
推荐阅读
相关推荐
持续发布公众号文章后终于吸引到同频的人找我一起合作做个小区智慧物业系统!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档