AndroidStudio 实现APP版本自动更新(内部更新,不涉及第三方市场)

这篇具有很好参考价值的文章主要介绍了AndroidStudio 实现APP版本自动更新(内部更新,不涉及第三方市场)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

实现效果图

android 第三方更新服务,Java,java,android,开发语言
android 第三方更新服务,Java,java,android,开发语言文章来源地址https://www.toymoban.com/news/detail-592624.html

调用自动更新

  //Activity创建或者从被覆盖、后台重新回到前台时被调用
    @Override
    protected void onResume() {
        super.onResume();
        //查询APP版本
        selectAPPVesion();
    }

1、获取更新版本号

    private int getVersion(final SysNotice sysNotice, int vision) {

        if (sysNotice == null) {
            return 0;
        }
        //网络请求获取当前版本号和下载链接
        //实际操作是从服务器获取
        String sdcardRoot = getExternalFilesDir(null) + File.separator + "test/apk";
        String apkSavePath = sdcardRoot + "/1.apk";
        String newversion = sysNotice.getVersion();//更新新的版本号
        String content = sysNotice.getNoticeContent();//更新内容
        String url = "127.0.0.1:8080" + Interface.APPapk;//安装包下载地址

        double newversioncode = Double
                .parseDouble(newversion);
        int cc = (int) (newversioncode);

        if (cc != vision) {
            if (vision < cc) {
                System.out.println(newversion + "v"
                        + vision);
                // 版本号不同
                ShowDialog(vision, newversion, sysNotice, url);
            }
            return 1;
        } else {
            return 0;
        }
    }

2、弹出弹窗升级系统

    private void ShowDialog(int vision, String newversion, SysNotice sysNotice,
                            final String url) {
        new android.app.AlertDialog.Builder(this)
                .setTitle(sysNotice.getNoticeTitle())
                .setMessage(Html.fromHtml(sysNotice.getNoticeContent()).toString())
                .setPositiveButton("更新", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        pBar = new CommonProgressDialog(MainActivity.this);
                        pBar.setCanceledOnTouchOutside(false);
                        pBar.setTitle("正在下载");
                        pBar.setCustomTitle(LayoutInflater.from(
                                MainActivity.this).inflate(
                                R.layout.title_dialog, null));
                        pBar.setMessage("正在下载");
                        pBar.setIndeterminate(true);
                        pBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                        pBar.setCancelable(true);
                        // downFile(URLData.DOWNLOAD_URL);
                        final DownloadTask downloadTask = new DownloadTask(
                                MainActivity.this);
                        downloadTask.execute(url);
                        pBar.setOnCancelListener(new DialogInterface.OnCancelListener() {
                            @Override
                            public void onCancel(DialogInterface dialog) {
                                downloadTask.cancel(true);
                            }
                        });
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                })
                .show();
    }

3、下载应用

 /**
     * 下载应用
     *
     * @author Administrator
     */
    class DownloadTask extends AsyncTask<String, Integer, String> {

        private Context context;
        private PowerManager.WakeLock mWakeLock;

        public DownloadTask(Context context) {
            this.context = context;
        }

        @Override
        protected String doInBackground(String... sUrl) {
            InputStream input = null;
            OutputStream output = null;
            HttpURLConnection connection = null;
            File file = null;
            try {
                URL url = new URL(sUrl[0]);
                connection = (HttpURLConnection) url.openConnection();
                connection.connect();
                // expect HTTP 200 OK, so we don't mistakenly save error
                // report
                // instead of the file
                if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                    return "Server returned HTTP "
                            + connection.getResponseCode() + " "
                            + connection.getResponseMessage();
                }
                // this will be useful to display download percentage
                // might be -1: server did not report the length
                int fileLength = connection.getContentLength();
                String sdcardRoot = getExternalFilesDir(null) + File.separator + "test/apk";
                final String apkSavePath = sdcardRoot + "/1.apk";
                System.err.println(apkSavePath);

                file = new File(apkSavePath);

                if (!file.exists()) {
                    // 判断父文件夹是否存在
                    if (!file.getParentFile().exists()) {
                        file.getParentFile().mkdirs();
                    }
                }

                input = connection.getInputStream();
                output = new FileOutputStream(file);
                byte data[] = new byte[4096];
                long total = 0;
                int count;
                while ((count = input.read(data)) != -1) {
                    // allow canceling with back button
                    if (isCancelled()) {
                        input.close();
                        return null;
                    }
                    total += count;
                    // publishing the progress....
                    if (fileLength > 0) // only if total length is known
                        publishProgress((int) (total * 100 / fileLength));
                    output.write(data, 0, count);

                }
            } catch (Exception e) {
                System.out.println(e.toString());
                return e.toString();

            } finally {
                try {
                    if (output != null)
                        output.close();
                    if (input != null)
                        input.close();
                } catch (IOException ignored) {
                }
                if (connection != null)
                    connection.disconnect();
            }
            return null;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            // take CPU lock to prevent CPU from going off if the user
            // presses the power button during download
            PowerManager pm = (PowerManager) context
                    .getSystemService(Context.POWER_SERVICE);
            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                    getClass().getName());
            mWakeLock.acquire();
            pBar.show();
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            // if we get here, length is known, now set indeterminate to false
            pBar.setIndeterminate(false);
            pBar.setMax(100);
            pBar.setProgress(progress[0]);
        }

        @Override
        protected void onPostExecute(String result) {
            mWakeLock.release();
            pBar.dismiss();
            if (result != null) {
                // 申请多个权限。
                AndPermission.with(MainActivity.this)
                        .requestCode(REQUEST_CODE_PERMISSION_SD)
                        .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
                        // rationale作用是:用户拒绝一次权限,再次申请时先征求用户同意,再打开授权对话框,避免用户勾选不再提示。
                        .rationale(rationaleListener
                        )
                        .send();


                Toast.makeText(context, "您未打开SD卡权限" + result, Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(context, "File downloaded",
                        Toast.LENGTH_SHORT)
                        .show();
                update();
            }

//            }
        }
    }

4、解决权限获取

  private static final int REQUEST_CODE_PERMISSION_SD = 101;

    private static final int REQUEST_CODE_SETTING = 300;
    private RationaleListener rationaleListener = new RationaleListener() {
        @Override
        public void showRequestPermissionRationale(int requestCode, final Rationale rationale) {
            // 这里使用自定义对话框,如果不想自定义,用AndPermission默认对话框:
            // AndPermission.rationaleDialog(Context, Rationale).show();

            // 自定义对话框。
            com.yanzhenjie.alertdialog.AlertDialog.build(MainActivity.this)
                    .setTitle(R.string.title_dialog)
                    .setMessage(R.string.message_permission_rationale)
                    .setPositiveButton(R.string.btn_dialog_yes_permission, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.cancel();
                            rationale.resume();
                        }
                    })

                    .setNegativeButton(R.string.btn_dialog_no_permission, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.cancel();
                            rationale.cancel();
                        }
                    })
                    .show();
        }
    };

    //        ----------------------------------SD权限----------------------------------//
    @PermissionYes(REQUEST_CODE_PERMISSION_SD)
    private void getMultiYes(List<String> grantedPermissions) {
        Toast.makeText(this, R.string.message_post_succeed, Toast.LENGTH_SHORT).show();
    }

    @PermissionNo(REQUEST_CODE_PERMISSION_SD)
    private void getMultiNo(List<String> deniedPermissions) {
        Toast.makeText(this, R.string.message_post_failed, Toast.LENGTH_SHORT).show();

        // 用户否勾选了不再提示并且拒绝了权限,那么提示用户到设置中授权。
        if (AndPermission.hasAlwaysDeniedPermission(this, deniedPermissions)) {
            AndPermission.defaultSettingDialog(this, REQUEST_CODE_SETTING)
                    .setTitle(R.string.title_dialog)
                    .setMessage(R.string.message_permission_failed)
                    .setPositiveButton(R.string.btn_dialog_yes_permission)
                    .setNegativeButton(R.string.btn_dialog_no_permission, null)
                    .show();

            // 更多自定dialog,请看上面。
        }
    }

    //----------------------------------权限回调处理----------------------------------//

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
            grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        /**
         * 转给AndPermission分析结果。
         *
         * @param object     要接受结果的Activity、Fragment。
         * @param requestCode  请求码。
         * @param permissions  权限数组,一个或者多个。
         * @param grantResults 请求结果。
         */
        AndPermission.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case REQUEST_CODE_SETTING: {
                Toast.makeText(this, R.string.message_setting_back, Toast.LENGTH_LONG).show();
                //设置成功,再次请求更新
                getVersion(sysNotice, Tools.getVersion(MainActivity.this));
                break;
            }
        }
    }

    private void update() {
        //安装应用
        //获取SD卡的根路径
        String sdcardRoot = getExternalFilesDir(null) + File.separator + "test/apk";
        final String apkSavePath = sdcardRoot + "/1.apk";
        System.err.println(apkSavePath);
        //安装应用
        Intent intent = new Intent(Intent.ACTION_VIEW);
        File apkFile = new File(apkSavePath);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri uri = FileProvider.getUriForFile(MainActivity.this, this.getPackageName() + ".fileprovider", apkFile);
            intent.setDataAndType(uri, "application/vnd.android.package-archive");
            startActivity(intent);
        } else {
            //  intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
        }

    }

5、调用后端接口,查询APP版本和后端发布公告版本

    public void selectAPPVesion() {
        try {
            OkHttpTool.httpPost("127.0.0.1:8080" + Interface.selectAPPVesion, null, null, new OkHttpTool.ResponseCallback() {
                @Override
                public void onResponse(final boolean isSuccess, final int responseCode, final String response, Exception exception) {
                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            if (isSuccess && responseCode == 200) {
                                if (!TextUtils.isEmpty(response)) {
                                    //得到resultBean的数据
                                    JSONObject jsonObject = JSON.parseObject(response);
                                    Integer code = jsonObject.getInteger("code");
                                    if (code.equals(200)) {
                                        sysNotice = JSON.parseObject(jsonObject.getString("data"), SysNotice.class);
                                    }
                                }
                            }
                            // 获取本版本号,是否更新
                            int vision = Tools.getVersion(MainActivity.this);
                            int version = getVersion(sysNotice, vision);
                            if (version == 0) {
                                initseifLogin();
                            }
                        }
                    });
                }
            });

        } catch (Exception e) {
            Intent intent = new Intent(MainActivity.this, Configure_Activity.class);
            intent.putExtra("isLogin", "false");
            startActivity(intent);
            Toast.makeText(MainActivity.this, "Invalid URL port", Toast.LENGTH_SHORT).show();
        }

    }

6、工具封装Tools–获取APP版本号

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
/**
 *
 */
public class Tools {
    /**
     * 2 * 获取版本号 3 * @return 当前应用的版本号 4
     */
    public static int getVersion(Context context) {
        try {
            PackageManager manager = context.getPackageManager();
            PackageInfo info = manager.getPackageInfo(context.getPackageName(),
                    0);
            String version = info.versionName;
            int versioncode = info.versionCode;
            return versioncode;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }
}

7、网络请求封装–OkHttpTool

    //网络请求
    implementation 'com.squareup.okhttp3:okhttp:3.11.0'

import android.util.Log;

import androidx.annotation.NonNull;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

public class OkHttpTool {

    private static String TAG = "OkHttpTool";

    private static final OkHttpClient myOkHttpClient =  new OkHttpClient.Builder()
            .connectTimeout(15, TimeUnit.SECONDS)
            .writeTimeout(20, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            //添加cookie处理
            .cookieJar(new CookieJar() {//这里是设置cookie的,但是并没有做持久化处理;只是把cookie保存在内存中
                private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();

                @Override
                public void saveFromResponse(@NonNull HttpUrl url, @NonNull List<Cookie> cookies) {
                    cookieStore.put(url.host(), cookies);
                }
                @Override
                public List<Cookie> loadForRequest(@NonNull HttpUrl url) {
                    List<Cookie> cookies = cookieStore.get(url.host());
                    return cookies != null ? cookies : new ArrayList<Cookie>();
                }
            })
            //添加日志处理拦截器
            .addInterceptor(new Interceptor() {

                @Override
                public okhttp3.Response intercept(@NonNull Chain chain) throws IOException {
                    Request request = chain.request();
                    long startTime = System.currentTimeMillis();
                    okhttp3.Response response = chain.proceed(chain.request());
                    long endTime = System.currentTimeMillis();
                    long duration=endTime-startTime;
                    okhttp3.MediaType mediaType = response.body().contentType();
                    String content = response.body().string();
                    Log.d(TAG,"\n");
                    Log.d(TAG,"----------Start----------------");
                    Log.d(TAG, "| "+request.toString());
                    String method=request.method();
                    if("POST".equals(method)){
                        StringBuilder sb = new StringBuilder();
                        if (request.body() instanceof FormBody) {
                            FormBody body = (FormBody) request.body();
                            for (int i = 0; i < body.size(); i++) {
                                sb.append(body.encodedName(i) + "=" + body.encodedValue(i) + ",");
                            }
                            if (sb.length()>0){
                                //在参数不为空的情况下处理最后的 “,”
                                sb.delete(sb.length() - 1, sb.length());
                            }
                            Log.d(TAG, "| RequestParams:{"+sb.toString()+"}");
                        }
                    }
                    Log.d(TAG, "| Response:" + content);
                    Log.d(TAG,"----------End:"+duration+"毫秒----------");
                    return response.newBuilder()
                            .body(okhttp3.ResponseBody.create(mediaType, content))
                            .build();
                }
            })
            .build();

    public static void httpGet(String url, Map<String, Object> parameters, Map<String, Object> headers, ResponseCallback responseCallback){
        Request request=createGetRequest(url,parameters,headers);
        doRequest(request,responseCallback);
    }

    public static void httpPost(String url, Map<String, Object> parameters, Map<String, Object> headers, ResponseCallback responseCallback){
        Request request=createPostRequest(url,parameters,headers);
        doRequest(request,responseCallback);
    }

    public static void httpPostJson(String url, String json, Map<String, Object> headers, ResponseCallback responseCallback){
        Request request=createPostRequestJson(url,json,headers);
        doRequest(request,responseCallback);
    }
    public static void httpPostWithFile(String url, Map<String, Object> parameters, Map<String, File> files, Map<String, Object> headers, ResponseCallback responseCallback) {
        Request request=createPostRequestWithFile(url,parameters,files,headers);
        doRequest(request,responseCallback);
    }


    public static Request createGetRequest(String url, Map<String, Object> parameters, Map<String, Object> headers) {
        StringBuilder urlBuilder = new StringBuilder();
        urlBuilder.append(url);
        if (url.indexOf('?') <= -1) {
            //未拼接参数
            urlBuilder.append("?");
        }
        if (parameters != null) {
            for (Map.Entry<String, Object> entry : parameters.entrySet()) {
                urlBuilder.append("&");
                urlBuilder.append(entry.getKey());
                urlBuilder.append("=");
                urlBuilder.append(entry.getValue().toString());
            }
        }
        return getBaseRequest(headers).url(urlBuilder.toString()).build();
    }

    public static Request createPostRequest(String url, Map<String, Object> parameters, Map<String, Object> headers) {
        FormBody.Builder builder = new FormBody.Builder(Charset.forName("UTF-8"));
        if (parameters != null) {
            for (Map.Entry<String, Object> entry : parameters.entrySet()) {
                builder.add(entry.getKey(), entry.getValue().toString());
            }
        }
        FormBody formBody = builder.build();
        return getBaseRequest(headers).url(url).post(formBody).build();
    }

    public static Request createPostRequestJson(String url, String json, Map<String, Object> headers) {
        MediaType JSON = MediaType.parse("application/json; charset=utf-8");
        RequestBody body = RequestBody.create(JSON, json);
        return getBaseRequest(headers).url(url).post(body).build();
    }

    public static Request createPostRequestWithFile(String url, Map<String, Object> parameters, Map<String, File> files, Map<String, Object> headers) {
        // form 表单形式上传
        MultipartBody.Builder requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM);
        if (files != null) {
            for (Map.Entry<String, File> fileEntry : files.entrySet()) {
                File file = fileEntry.getValue();
                if (file != null) {
                    // MediaType.parse() 里面是上传的文件类型。
                    RequestBody body = RequestBody.create(MediaType.parse("application/octet-stream"), file);
                    String filename = file.getName();
                    // 参数分别为, 请求key ,文件名称 , RequestBody
                    requestBody.addFormDataPart(fileEntry.getKey(), filename, body);
                }
            }
        }
        if (parameters != null) {
            // map 里面是请求中所需要的 key 和 value
            for (Map.Entry<String, Object> entry : parameters.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue().toString();
                requestBody.addFormDataPart(key, value);
            }
        }
        return getBaseRequest(headers).url(url).post(requestBody.build()).build();
    }

    private static void doRequest(final Request request, final ResponseCallback responseCallback) {
        //使用okhttp3的异步请求
        myOkHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {
                //回调
                Log.e(TAG,e.getMessage());
                responseCallback.onResponse(false, -1, null, e);
            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                int responseCode = response.code();//获取响应码
                ResponseBody responseBody = response.body();//获取 ResponseBody
                if (response.isSuccessful() && responseBody != null) {
                    String strResponse = responseBody.string();
                    //回调
                    responseCallback.onResponse(true, responseCode, strResponse, null);
                } else {
                    //回调
                    responseCallback.onResponse(false, responseCode, null, null);
                }
            }
        });
    }

    private static Request.Builder getBaseRequest(Map<String, Object> headers) {
        Request.Builder builder = new Request.Builder();
        builder.addHeader("client", "Android");
        if (headers != null) {
        // map 里面是请求中所需要的 key 和 value
        for (Map.Entry<String, Object> entry : headers.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue().toString();
            builder.addHeader(key, value);
        }
        }
        return builder;
    }


    public interface ResponseCallback {
        void onResponse(boolean isSuccess, int responseCode, String response, Exception exception);
    }


}

8、后端下载接口

 /**
     * 本地资源通用下载
     */
    @GetMapping("/api/download/APPapk")
    public void APPapk(HttpServletRequest request, HttpServletResponse response)
            throws Exception
    {

        File dir = new File("E:\\appPath\\");
        String[] children = dir.list();
        if (children == null) {
            System.out.println("该目录不存在");
            throw new Exception(StringUtils.format("该更新APK不存在"));
        }
        else {
            for (int i = 0; i < children.length; i++) {
                String filename = children[i];
                if(filename.contains(".apk")){
                    try
                    {
                        // 数据库资源地址
                        String downloadPath = dir.getAbsolutePath()+File.separator+filename;
                        // 下载名称
                        response.setContentLengthLong((int) new File(dir.getAbsolutePath()+File.separator+filename).length());
                        response.setContentLength((int) new File(dir.getAbsolutePath()+File.separator+filename).length());
                        response.setHeader("Content-Length", String.valueOf(new File(dir.getAbsolutePath()+File.separator+filename).length()));
                        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
                        FileUtils.setAttachmentResponseHeader(response, filename);
                        FileUtils.writeBytes(downloadPath, response.getOutputStream());
                    }
                    catch (Exception e)
                    {
                        log.error("下载文件失败", e);
                    }
                    return;
                }
            }
        }

    }

9、查询后端发布公告是否更新APP

 /**
     * 查找app版本信息   select  * from sys_notice  order by notice_id desc Limit 1
     * 查询最后一条数据 
     */
    @PostMapping("selectAPPVesion")
    public AjaxResult selectAPPVesion() {
        SysNotice sysNoticePa=new SysNotice();
        SysNotice sysNotice = noticeMapper.selectAPPVesion(sysNoticePa);
        return AjaxResult.success(sysNotice);
    }

到了这里,关于AndroidStudio 实现APP版本自动更新(内部更新,不涉及第三方市场)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • uniapp校验app版本并更新

    最近用uniapp写了一个安卓壳子做app,遇到一个需求,校验app版本并更新 通过对比线上版本号和app自己的版本号的差异,唤起更新弹窗 相关代码 App.vue pages下的upgrade    index.vue pages下的upgrade    upgrade.js pages.json 

    2024年02月20日
    浏览(26)
  • 【Android】-- 如何对APP版本控制/更新?

    目录 一、 前提准备 1、获取服务器 2、使用工具操作云服务器  二、Json格式网页  三、创建file_paths.xml及修改AndroidManifest.xml 四、在java代码加入更新检测代码 效果如图:         可以强制更新和非强制更新,和浏览器下载安装包。         首先去获取云服务器,如:阿

    2024年02月12日
    浏览(39)
  • iOS App的上架和版本更新流程

    作为一名iOSDeveloper,把开发出来的App上传到App Store是必要的。下面就来详细讲解一下具体流程步骤。 一个已付费的开发者账号(账号类型分为个人(Individual)、公司(Company)、企业(Enterprise)、高校(University)四种类型,每年资费分别为$99、$99、$299、免费。)。 一个已经

    2024年02月11日
    浏览(48)
  • uniapp 实战 -- app 的自动升级更新(含生成 app 发布页)

    uniapp 提供了 App升级中心 uni-upgrade-center ,可以便捷实现app 的自动升级更新,具体编码和配置如下: https://ext.dcloud.net.cn/plugin?id=4542 (不要配在第一项) pages/index/index.vue https://blog.csdn.net/weixin_41192489/article/details/135551800 本范例中,版本配置为1.0.1版 详见 https://blog.csdn.net/weixi

    2024年02月02日
    浏览(61)
  • 前端项目部署自动检测更新后通知用户刷新页面(前端实现,技术框架vue、js、webpack)——方案一:编译项目时动态生成一个记录版本号的文件

    当我们重新部署前端项目的时候,如果用户一直停留在页面上并未刷新使用,会存在功能使用差异性的问题,因此,当前端部署项目后,需要提醒用户有去重新加载页面。 vue、js、webpack 编译项目时动态生成一个记录版本号的文件 轮询(20s、自己设定时间)这个文件,判断版

    2024年02月02日
    浏览(64)
  • 【Android】app应用内版本更新升级(DownloadManager下载,适配Android6.0以上所有版本)

    版本的升级和更新是一个线上App所必备的功能,App的升级安装包主要通过 应用商店 或者 应用内下载 两种方式获得,大部分app这两种方式都会具备,应用商店只需要上传对应平台审核通过即可,而应用内更新一般是通过以下几种方式: 1.集成第三方库如 appupdateX、bugly 的更新

    2024年02月11日
    浏览(115)
  • 打包时,自动更新版本号,清空缓存

    1.创建 addVersion.js 文件 2.修改package.json 文件 serve 执行为测试用的,看版本是否生效 打包更新版本号,只需配置 build 相关指令即可 main.js 文件中添加 清除缓存功能 ok 完成以上执行 查看 package.json 中是否改变 ,结束

    2024年02月02日
    浏览(31)
  • uni.app如何检测小程序版本更新以及上线后如何关闭全局打印

    App.vue 入口文件中 添加如下代码 微信小程序 测试 重新编译后 即可看到以下内容 在main.js中 加上以下代码

    2024年02月07日
    浏览(35)
  • 【Selenium】chromedriver最新版本与Chrome自动更新版本不匹配问题

    使用Selenium时需要下载chromedriver 1、首先查看我的Chrome浏览器版本已自动更新到116: 2、查找与之对应的chromedriver版本:http://chromedriver.storage.googleapis.com/index.html 发现最新版本只到114: chromedriver与Chrome版本不匹配且没有匹配最新Chrome版本的chromedriver。 因此考虑降级Chrome版本且

    2024年02月16日
    浏览(66)
  • 360安全路由怎么关闭自动更新升级保留出厂版本

    360安全路由默认在联网情况下,会自动进行固件版本更新升级,如果我们需要保留出厂版本,避免当有新版本时,被自动升级的话,该如何操作呢?下面小编详细介绍一下360安全路由关闭自动升级更新系统方法。 360安全路由怎么关闭自动更新升级 一、登陆360安全路由后台管理

    2024年02月07日
    浏览(59)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包