精华内容
下载资源
问答
  • 安卓app自动更新功能完美实现
    万次阅读 多人点赞
    2018-11-15 16:53:39

    示例:
    在这里插入图片描述

    零、准备工作

    0.1第三方库

    implementation ‘io.reactivex.rxjava2:rxjava:2.2.2’
    implementation ‘io.reactivex.rxjava2:rxandroid:2.1.0’
    implementation ‘io.reactivex.rxjava2:rxkotlin:2.3.0’
    implementation ‘com.squareup.okhttp3:okhttp:3.11.0’
    implementation ‘com.squareup.okio:okio:2.0.0’

    0.2权限

    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 写入权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    

    0.3格式

    “Code”: 0,
    “Msg”: “”,
    “UpdateStatus”: 1,
    “VersionCode”: 3,
    “VersionName”: “1.0.2”,
    “ModifyContent”: “1、优化api接口。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。”,
    “DownloadUrl”: “https://raw.githubusercontent.com/xuexiangjys/XUpdate/master/apk/xupdate_demo_1.0.2.apk”,
    “ApkSize”: 2048
    “ApkMd5”: “…” //md5值没有的话,就无法保证apk是否完整,每次都会重新下载。

    一、检测是否是最新版本,不是则更新

    private Disposable downDisposable;
    private ProgressBar progressBar;
    private TextView textView4;
    private Button upgrade;
    private long downloadLength=0;
    private long contentLength=0;
    private String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE};
    

    //判断版本是否最新,如果不是最新版本则更新

    private void test(){
            Observable.create(new ObservableOnSubscribe<String>() {
                @Override
                public void subscribe(ObservableEmitter<String> emitter) throws Exception {
                    OkHttpClient client = new OkHttpClient();
                    Request request = new Request.Builder()
                            .url("url")
                            .build();
    
                    client.newCall(request).enqueue(new okhttp3.Callback() {
                        @Override
                        public void onFailure(Call call, IOException e) {
                            emitter.onError(e);
                        }
    
                        @Override
                        public void onResponse(Call call, Response response) throws IOException {
                            String result="";
                            if (response.body()!=null) {
                                result=response.body().string();
                            }else {
                                //返回数据错误
                                return;
                            }
                            emitter.onNext(result);
                        }
                    });
    //                emitter.onNext("123");
                }
            }).subscribeOn(Schedulers.io())// 将被观察者切换到子线程
                    .observeOn(AndroidSchedulers.mainThread())// 将观察者切换到主线程
                    .subscribe(new Observer<String>() {
                        private Disposable mDisposable;
                        @Override
                        public void onSubscribe(Disposable d) {
                            mDisposable = d;
                        }
                        @Override
                        public void onNext(String result) {
                            if (result.isEmpty()){
                                return;
                            }
                            //2.判断版本是否最新,如果不是最新版本则更新
                            String downloadUrl="https://raw.githubusercontent.com/xuexiangjys/XUpdate/master/apk/xupdate_demo_1.0.2.apk";
                            String title="是否升级到4.1.1版本?";
                            String size="新版本大小:未知";
                            String msg="1、优化api接口。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。";
                            int versionCode=20000;
                            try {
                                int version = getPackageManager().
                                        getPackageInfo(getPackageName(), 0).versionCode;
                                if (versionCode>version){
                                    LayoutInflater inflater = LayoutInflater.from(TestActivity.this);
                                    View view = inflater.inflate(R.layout.layout_dialog, null);
                                    AlertDialog.Builder mDialog = new AlertDialog.Builder(TestActivity.this,R.style.Translucent_NoTitle);
                                    mDialog.setView(view);
                                    mDialog.setCancelable(true);
                                    mDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
                                        @Override
                                        public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
                                            return keyCode == KeyEvent.KEYCODE_BACK;
                                        }
                                    });
                                    upgrade= view.findViewById(R.id.button);
                                    TextView textView1= view.findViewById(R.id.textView1);
                                    TextView textView2= view.findViewById(R.id.textView2);
                                    TextView textView3= view.findViewById(R.id.textView3);
                                    textView4= view.findViewById(R.id.textView4);
                                    ImageView iv_close= view.findViewById(R.id.iv_close);
                                    progressBar= view.findViewById(R.id.progressBar);
                                    progressBar.setMax(100);
                                    textView1.setText(title);
                                    textView2.setText(size);
                                    textView3.setText(msg);
                                    upgrade.setOnClickListener(new View.OnClickListener() {
                                        @Override
                                        public void onClick(View v) {
                                            //动态询问是否授权
                                            int permission = ActivityCompat.checkSelfPermission(getApplication(),
                                                Manifest.permission.WRITE_EXTERNAL_STORAGE);
                                            if (permission != PackageManager.PERMISSION_GRANTED) {
                                                ActivityCompat.requestPermissions(TestActivity.this, PERMISSIONS_STORAGE,
                                                    1);
                                            }else {
                                                upgrade.setVisibility(View.INVISIBLE);
                                                down(downloadUrl);
                                            }
                                        }
                                    });
                                    iv_close.setOnClickListener(new View.OnClickListener() {
                                        @Override
                                        public void onClick(View v) {
                                            finish();
                                        }
                                    });
                                    mDialog.show();
                                }else {
    
                                }
                            } catch (PackageManager.NameNotFoundException e) {
                                e.printStackTrace();
                            }
                            mDisposable.dispose();
                        }
                        @Override
                        public void onError(Throwable e) {
                            test();
                        }
                        @Override
                        public void onComplete() {
    
                        }
                    });
        }
    

    //下载apk并更新进度条

    private void down(String downloadUrl){
            Observable.create(new ObservableOnSubscribe<Integer>() {
                @Override
                public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                    downApk(downloadUrl,emitter);
                }
            }).subscribeOn(Schedulers.io())// 将被观察者切换到子线程
                    .observeOn(AndroidSchedulers.mainThread())// 将观察者切换到主线程
                    .subscribe(new Observer<Integer>() {
    
                        @Override
                        public void onSubscribe(Disposable d) {
                            downDisposable = d;
                        }
                        @Override
                        public void onNext(Integer result) {
                            //设置ProgressDialog 进度条进度
                            progressBar.setProgress(result);
                            textView4.setText(result+"%");
                        }
                        @Override
                        public void onError(Throwable e) {
                            Toast.makeText(getApplication(),"网络异常!请重新下载!",Toast.LENGTH_SHORT).show();
                            upgrade.setEnabled(true);
                        }
                        @Override
                        public void onComplete() {
                            Toast.makeText(getApplication(),"服务器异常!请重新下载!",Toast.LENGTH_SHORT).show();
                            upgrade.setEnabled(true);
                        }
                    });
        }
    

    二、下载apk

    //下载apk

    private void downApk(String downloadUrl,ObservableEmitter<Integer> emitter){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(downloadUrl)
                .build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                //下载失败
                breakpoint(downloadUrl,emitter);
            }
    
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.body() == null) {
                    //下载失败
                    breakpoint(downloadUrl,emitter);
                    return;
                }
                InputStream is = null;
                FileOutputStream fos = null;
                byte[] buff = new byte[2048];
                int len;
                try {
                    is = response.body().byteStream();
                    File file = createFile();
                    fos = new FileOutputStream(file);
                    long total = response.body().contentLength();
                    contentLength=total;
                    long sum = 0;
                    while ((len = is.read(buff)) != -1) {
                        fos.write(buff,0,len);
                        sum+=len;
                        int progress = (int) (sum * 1.0f / total * 100);
                        //下载中,更新下载进度
                        emitter.onNext(progress);
                        downloadLength=sum;
                    }
                    fos.flush();
                    //4.下载完成,安装apk
                    installApk(TestActivity.this,file);
                } catch (Exception e) {
                    e.printStackTrace();
                    breakpoint(downloadUrl,emitter);
                } finally {
                    try {
                        if (is != null)
                            is.close();
                        if (fos != null)
                            fos.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    
    }
    

    //断点续传

    private void breakpoint(String downloadUrl,ObservableEmitter<Integer> emitter){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(downloadUrl)
                .addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength)
                .build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                //下载失败
                breakpoint(downloadUrl,emitter);
            }
    
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.body() == null) {
                    //下载失败
                    breakpoint(downloadUrl,emitter);
                    return;
                }
                InputStream is = null;
                RandomAccessFile randomFile = null;
                byte[] buff = new byte[2048];
                int len;
                try {
                    is = response.body().byteStream();
                    String root = Environment.getExternalStorageDirectory().getPath();
                    File file = new File(root,"updateDemo.apk");
                    randomFile = new RandomAccessFile(file, "rwd");
                    randomFile.seek(downloadLength);
                    long total = contentLength;
                    long sum = downloadLength;
                    while ((len = is.read(buff)) != -1) {
                        randomFile.write(buff,0,len);
                        sum+=len;
                        int progress = (int) (sum * 1.0f / total * 100);
                        //下载中,更新下载进度
                        emitter.onNext(progress);
                        downloadLength=sum;
                    }
                    //4.下载完成,安装apk
                    installApk(TestActivity.this,file);
                } catch (Exception e) {
                    e.printStackTrace();
                    breakpoint(downloadUrl,emitter);
                } finally {
                    try {
                        if (is != null)
                            is.close();
                        if (randomFile != null)
                            randomFile.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
    

    /**

    • 路径为根目录
    • 创建文件名称为 updateDemo.apk
      */
    private File createFile() {
        String root = Environment.getExternalStorageDirectory().getPath();
        File file = new File(root,"updateDemo.apk");
        if (file.exists())
            file.delete();
        try {
            file.createNewFile();
            return file;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null ;
    }
    
    

    三、安装apk

    3.1项目的src/res新建个xml文件夹再自定义一个file_paths文件

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

    3.2在清单文件中配置

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.mydomain.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
    

    3.3安装apk

    //安装apk,包含7.0

    public void installApk(Context context, File file) {
        if (context == null) {
            return;
        }
        String authority = getApplicationContext().getPackageName() + ".fileProvider";
        Uri apkUri = FileProvider.getUriForFile(context, authority, file);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        //判读版本是否在7.0以上
        if (Build.VERSION.SDK_INT >= 24) {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        }
    
        context.startActivity(intent);
        //弹出安装窗口把原程序关闭。
        //避免安装完毕点击打开时没反应
        Process.killProcess(android.os.Process.myPid());
    }
    

    四、取消订阅

    @Override
    protected void onDestroy() {
        super.onDestroy();
        downDisposable.dispose();//取消订阅
    }
    

    五、自定义Dialog

    5.1UI

    见一、检测是否是最新版本,不是则更新

    5.2布局

    <?xml version="1.0" encoding="utf-8"?>
    

    <android.support.constraint.ConstraintLayout xmlns:android=“http://schemas.android.com/apk/res/android
    xmlns:app=“http://schemas.android.com/apk/res-auto
    xmlns:tools=“http://schemas.android.com/tools
    android:layout_width=“match_parent”
    android:layout_height=“match_parent”>

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/lib_update_app_top_bg"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    
    <View
        android:id="@+id/view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@drawable/lib_update_app_info_bg"
        app:layout_constraintBottom_toTopOf="@+id/line"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView1" />
    
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="是否升级到1.0版本?"
        android:textColor="@android:color/black"
        android:textSize="15sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView1" />
    
    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="新版本大小:"
        android:textColor="#666"
        android:textSize="14sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView1" />
    
    
    <ScrollView
        android:id="@+id/scrollView2"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
            <TextView
                android:id="@+id/textView3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="1,xxxxxxxx\n2,ooooooooo"
                android:textColor="#666"
                android:textSize="14sp"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/textView2" />
        </LinearLayout>
    </ScrollView>
    
    <Button
        android:id="@+id/button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:background="@drawable/textview_round_red"
        android:gravity="center"
        android:minHeight="40dp"
        android:text="升级"
        android:textColor="@android:color/white"
        android:textSize="15sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/scrollView2" />
    
    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        app:layout_constraintBottom_toBottomOf="@+id/button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
    
    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0%"
        android:textColor="#E94339"
        app:layout_constraintBottom_toTopOf="@+id/progressBar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
    
    <android.support.constraint.Guideline
        android:id="@+id/guideline1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />
    
    <View
        android:id="@+id/line"
        android:layout_width="1dp"
        android:layout_height="50dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="8dp"
        android:background="#d8d8d8"
        android:visibility="visible"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.501"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />
    
    <ImageView
        android:id="@+id/iv_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:src="@mipmap/lib_update_app_close"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/line" />
    
    <android.support.constraint.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.2" />
    

    </android.support.constraint.ConstraintLayout>

    5.3其他文件

    5.3.1textview_round_red.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape  xmlns:android="http://schemas.android.com/apk/res/android">
    
        <!-- view背景色 -->
        <solid android:color="#E94339" />
        <!-- 边框颜色 宽度 -->
        <stroke
            android:width="1dip"
            android:color="#E94339" />
        <!-- 边框圆角 -->
        <corners
            android:bottomRightRadius="5dp"
            android:topRightRadius="5dp"
            android:bottomLeftRadius="5dp"
            android:topLeftRadius="5dp"/>
    </shape >
    

    5.3.2lib_update_app_info_bg.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
           android:shape="rectangle">
        <corners
            android:bottomLeftRadius="5dp"
            android:bottomRightRadius="5dp"/>
        <solid android:color="@android:color/white"/>
    </shape>
    

    5.3.3styles文件

    <style name="Translucent_NoTitle" parent="android:style/Theme.Dialog">
    
        <item name="android:background">#00000000</item> <!-- 设置自定义布局的背景透明 -->
        <item name="android:windowBackground">@android:color/transparent</item>  <!-- 设置window背景透明,也就是去边框 -->
    </style>
    

    5.4图片

    在这里插入图片描述

    在这里插入图片描述

    更多相关内容
  • android app自动更新功能的实现
  • 安卓APP自动更新功能实现

    千次阅读 2022-02-17 09:54:53
    安卓APP自动更新功能实现前言代码实现 前言 安卓App自动更新基本上是每个App都需要具备的功能,接下来介绍一下实现自动更新的步骤。 代码实现 App自动更新主要分为新版本检测、升级弹窗、下载升级包、安装app这4个...

    安卓APP自动更新功能实现

    前言

    安卓App自动更新基本上是每个App都需要具备的功能,接下来介绍一下实现自动更新的步骤。
    在这里插入图片描述

    代码实现

    App自动更新主要分为新版本检测、升级弹窗、下载升级包、安装app这4个步骤,以下为MainActivity的实现代码(注意:目标升级版本和升级包下载地址实际需要向平台拉取):

    package com.example.testupgrade;
    
    import androidx.appcompat.app.AlertDialog;
    import androidx.appcompat.app.AppCompatActivity;
    import androidx.core.content.FileProvider;
    
    import android.content.Context;
    import android.content.DialogInterface;
    import android.content.Intent;
    import android.content.pm.PackageManager;
    import android.net.Uri;
    import android.os.Build;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.TextView;
    
    import java.io.File;
    
    public class MainActivity extends AppCompatActivity {
    
        private final String TAG = "Jason";
        private File file;
    
        private TextView textView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initView();
    
            if(versionCheck()) {
                showTip(this);
            }
        }
    
    	//在主界面显示app版本号,非必要
        private void initView() {
            textView = findViewById(R.id.tv_version);
            textView.setText("version:" + getVersionName());
        }
    
        //新版本检测
        private boolean versionCheck() {
            String targetVersion = "xxx"; //todo 实际目标版本号应向平台查询
    
            Log.d(TAG, "versionCheck version:" + getVersionName());
            Log.d(TAG, "versionCheck targetVersion:" + targetVersion);
            if(getVersionName().contentEquals(targetVersion)) {
                return false;
            } else {
                return true;
            }
        }
    
        //升级弹窗
        private void showTip(Context context) {
            AlertDialog dialog = new AlertDialog.Builder(context).setTitle("升级提示").setMessage("检测到新版本,请升级")
                .setNeutralButton("升级", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        doDownload();
                    }
                }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                }).show();
            dialog.setCanceledOnTouchOutside(false);//可选
            dialog.setCancelable(false);//可选
        }
    
        //下载升级包
        private void doDownload() {
    
            String downloadUrl = "https://xxx"; //todo 实际下载地址应向平台查询
    
            String parentPath = "";
            try {
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                    parentPath = this.getExternalFilesDir(null).getPath();
                } else {
                    parentPath = this.getFilesDir().getPath();
                }
            } catch (Exception e) {
                Log.d(TAG, "doDownload e:" + e.getMessage());
            }
    
            Log.d(TAG, "doDownload parentPath:" + parentPath);
            file = new File(parentPath, "myhouse.apk");
            final String filePath = file.getAbsolutePath();
    
            //如果已有文件,删除
            if (file.exists()) {
                Log.d(TAG, "doDownload delete APK");
                file.delete();
            }
    
            try {
                DownloadUtil.get().download(downloadUrl, filePath, new DownloadUtil.OnDownloadListener() {
                    @Override
                    public void onDownloadSuccess() {
                        //成功
                        Log.d(TAG, "doDownload download success");
                        installApk();
                    }
    
                    @Override
                    public void onDownloading(int progress) {
                        //进度
                        //Log.d(TAG, "doDownload download:" + progress +"%");
                    }
    
                    @Override
                    public void onDownloadFailed() {
                        //失败
                        Log.d(TAG, "doDownload download fail");
                    }
                });
            } catch (Exception e) {
                Log.d(TAG, "doDownload e2:" + e.getMessage());
            }
        }
    
        //安装app
        private void installApk() {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            Uri data;
    
    		//7.0以上安卓系统安装app需要通过fileProvider(需要在AndroidManifest.xml声明)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                data = FileProvider.getUriForFile(this, "com.example.testupgrade.provider", file);
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                Log.d(TAG,"installApk 7.0data:" + data);
            } else {
                data = Uri.fromFile(file);
                Log.d(TAG,"installApk data:" + data);
            }
    
            intent.setDataAndType(data, "application/vnd.android.package-archive");
            this.startActivity(intent);
        }
    
        //获取软件版本号
        private String getVersionName() {
            String versionName = "";
            try {
                versionName = getApplicationContext().getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), 0).versionName;
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
            return versionName;
        }
    }
    

    上述下载功能基于okhttp实现的DownloadUtil工具类,具体代码如下:

    package com.example.testupgrade;
    
    import android.os.Environment;
    import android.util.Log;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    
    import okhttp3.Call;
    import okhttp3.Callback;
    import okhttp3.OkHttpClient;
    import okhttp3.Request;
    import okhttp3.Response;
    
    public class DownloadUtil {
        private static DownloadUtil downloadUtil;
        private final OkHttpClient okHttpClient;
    
        public static DownloadUtil get() {
            if (downloadUtil == null) {
                Log.d("Jason", "DownloadUtil get new DownloadUtil");
                downloadUtil = new DownloadUtil();
            }
            return downloadUtil;
        }
    
        private DownloadUtil() {
            okHttpClient = new OkHttpClient();
        }
    
        /**
         * @param url 下载连接
         * @param filePath 储存下载文件的SDCard目录
         * @param listener 下载监听
         */
        public void download(final String url, final String filePath, final OnDownloadListener listener) {
            Log.d("Jason", "DownloadUtil download start");
            Request request = new Request.Builder().url(url).build();
            Log.d("Jason", "DownloadUtil request:" + request.toString());
            okHttpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Log.d("Jason", "DownloadUtil onFailure e:" + e.getMessage());
                    listener.onDownloadFailed();
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    Log.d("Jason", "DownloadUtil onResponse start");
                    InputStream is = null;
                    byte[] buf = new byte[2048];
                    int len = 0;
                    FileOutputStream fos = null;
                    // 储存下载文件的目录
                    //String savePath = isExistDir(saveDir);
                    Log.d("Jason", "DownloadUtil filePath:" + filePath);
                    try {
                        is = response.body().byteStream();
                        long total = response.body().contentLength();
                        //File file = new File(savePath, getNameFromUrl(url));
                        File file = new File(filePath);
                        fos = new FileOutputStream(file);
                        long sum = 0;
                        while ((len = is.read(buf)) != -1) {
                            fos.write(buf, 0, len);
                            sum += len;
                            int progress = (int) (sum * 1.0f / total * 100);
                            // 下载中
                            listener.onDownloading(progress);
                        }
                        fos.flush();
                        // 下载完成
                        listener.onDownloadSuccess();
                    } catch (Exception e) {
                        Log.d("Jason", "DownloadUtil onResponse e1:" + e.getMessage());
                        listener.onDownloadFailed();
                    } finally {
                        try {
                            if (is != null) {
                                is.close();
                            }
                        } catch (IOException e) {
                            Log.d("Jason", "DownloadUtil onResponse e2:" + e.getMessage());
                        }
                        try {
                            if (fos != null) {
                                fos.close();
                            }
                        } catch (IOException e) {
                            Log.d("Jason", "DownloadUtil onResponse e3:" + e.getMessage());
                        }
                    }
                }
    
            });
        }
    
        /**
         * @param saveDir
         * @return
         * @throws IOException
         * 判断下载目录是否存在
         */
        private String isExistDir(String saveDir) throws IOException {
            // 下载位置
            File downloadFile = new File(Environment.getExternalStorageDirectory(), saveDir);
            if (!downloadFile.mkdirs()) {
                downloadFile.createNewFile();
            }
            String savePath = downloadFile.getAbsolutePath();
            return savePath;
        }
    
        /**
         * @param url
         * @return
         * 从下载连接中解析出文件名
         */
        public static String getNameFromUrl(String url) {
            return url.substring(url.lastIndexOf("/") + 1);
        }
    
        public interface OnDownloadListener {
            /**
             * 下载成功
             */
            void onDownloadSuccess();
    
            /**
             * @param progress
             * 下载进度
             */
            void onDownloading(int progress);
    
            /**
             * 下载失败
             */
            void onDownloadFailed();
        }
    }
    
    

    注:为了使用okhttp,需要在build.gradle添加如下配置:

    implementation 'com.squareup.okhttp3:okhttp:4.3.1'
    

    为了实现下载和安装功能,需要在AndroidManifest.xml声明网络、SD卡读写和安装包权限;另外,为了实现7.0以上安卓系统的app安装,还需要声明fileProvider,具体代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.testupgrade">
    
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
        <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:networkSecurityConfig="@xml/network_security_config"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="${applicationId}.provider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_path" />
            </provider>
        </application>
    
    </manifest>
    

    添加fileProvider对应的file_path.xml
    在这里插入图片描述

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

    添加network_security_config.xml,配置可访问http地址:
    在这里插入图片描述

    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <base-config cleartextTrafficPermitted="true" />
    </network-security-config>
    
    展开全文
  • flutter APP自动更新

    千次阅读 2020-08-14 14:03:58
    flutter APP自动更新前言在pubspec.yaml中安装依赖在main.dart文件中,初始化FlutterDownLoader配置网络在AndroidManifest.xml新增如下配置在项目入口dart文件中,新增自动更新逻辑代码效果图 前言 近期做flutter ...

    前言

    近期做flutter APP框架的搭建封装,在APP自动更新这块,参考了很多网址,但都不全面;故自己动手封装了一套,主要采用flutter_downloader及progress_dialog等;在APP启动成功后,若有最新版本,便会自动弹框提示是否更新,若更新,将会下载最新APP,并显示下载进度,下载完成后,将自动提示是否安装最新包。
    此功能主要针对android APP自动更新

    在pubspec.yaml中安装依赖

         permission_handler: 5.0.0+hotfix.4
         package_info: 0.4.1
         path_provider: 1.6.11
         open_file: 3.0.1
         flutter_downloader: 1.5.0
         progress_dialog: 1.2.0
    

    在控制台输入 flutter packages get 命令,下载依赖包

    在main.dart文件中,初始化FlutterDownLoader

      ...
      import 'package:flutter_downloader/flutter_downloader.dart';
      
      ...
    
      
      void main() async {
        WidgetsFlutterBinding.ensureInitialized();
        await FlutterDownloader.initialize(
          debug: true
        );
        runApp(MyApp());
      }
    
      ....
    
    

    配置网络

    在android/app/src/main/res目录下,新建文件夹xml,在xml文件夹下,新增network_security_config.xml
    在network_security_config.xml中写入以下代码

     <?xml version="1.0" encoding="utf-8"?>
        <network-security-config>
            <base-config cleartextTrafficPermitted="true">
                <trust-anchors>
                    <certificates src="system" />
                </trust-anchors>
            </base-config>
        </network-security-config>
    

    如下图:
    在这里插入图片描述

    在AndroidManifest.xml新增如下配置

    1. 新增权限
      <uses-permission android:name="android.permission.INTERNET"/>
      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
      <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
      <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
      <uses-permission android:name="android.permission.RECORD_AUDIO" />
    
    1. 在<application …>中新增network_security_config配置
      <application
            android:name="io.flutter.app.FlutterApplication"
            android:label="flutter助手"
            android:icon="@mipmap/ic_launcher"
            android:networkSecurityConfig="@xml/network_security_config"
        >
    
    

    如下图:
    在这里插入图片描述
    3. 在<application …>标签内加入以下代码

          <provider
                android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider"
                android:authorities="${applicationId}.flutter_downloader.provider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/provider_paths"/>
            </provider>
    		
    		 <provider
                android:name="androidx.work.impl.WorkManagerInitializer"
                android:authorities="${applicationId}.workmanager-init"
                android:enabled="false"
                android:exported="false" />
    
            <provider
                android:name="vn.hunghd.flutterdownloader.FlutterDownloaderInitializer"
                android:authorities="${applicationId}.flutter-downloader-init"
                android:exported="false">
                <!-- changes this number to configure the maximum number of concurrent tasks -->
                <meta-data
                    android:name="vn.hunghd.flutterdownloader.MAX_CONCURRENT_TASKS"
                    android:value="5" />
            </provider>
    
    
            <provider
                    android:name="androidx.core.content.FileProvider"
                    android:authorities="${applicationId}.fileProvider"
                    android:exported="false"
                    android:grantUriPermissions="true"
                    tools:replace="android:authorities">
                <meta-data
                        android:name="android.support.FILE_PROVIDER_PATHS"
                        android:resource="@xml/filepaths"
                        tools:replace="android:resource" />
            </provider>
    

    如下图:
    在这里插入图片描述

    在项目入口dart文件中,新增自动更新逻辑代码

    1. 导入相关包
      import 'dart:isolate';
      import 'dart:ui';
      import 'dart:async';
      import 'dart:io';
    
      import 'package:package_info/package_info.dart';
      import 'package:path_provider/path_provider.dart';
      import 'package:open_file/open_file.dart';
      import 'package:flutter_downloader/flutter_downloader.dart';
      import 'package:progress_dialog/progress_dialog.dart';
    
    
    1. 声明变量
     String serviceVersionCode = '';
      String appId = '';
      ProgressDialog pr;
      String apkName ='app-release.apk';
      String appPath = '';
      ReceivePort _port = ReceivePort();
    
    1. 在initState中初始化

       IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port');
       _port.listen(_updateDownLoadInfo);
       FlutterDownloader.registerCallback(_downLoadCallback);
      
    2. 判断,自动更新

     @override
      void afterFirstLayout(BuildContext context) {
        // 如果是android,则执行热更新
        if(Platform.isAndroid){
           _getNewVersionAPP(context);
        }
       
      }
    
    1. 自动更新代码
     /// 执行版本更新的网络请求
      _getNewVersionAPP(context) async {
         HttpUtils.send(
          context,
          'http://update.rwworks.com:8088/appManager/monitor/app/version/check/flutterTempldate',
        ).then((res) {
           serviceVersionCode = res.data["versionNo"];
           appId = res.data['id'];
          _checkVersionCode();
        });
      }
      
      /// 检查当前版本是否为最新,若不是,则更新
      void _checkVersionCode() {
        PackageInfo.fromPlatform().then((PackageInfo packageInfo) {
          var currentVersionCode = packageInfo.version;
          if (double.parse(serviceVersionCode.substring(0,3))> double.parse(currentVersionCode.substring(0,3))) {
            _showNewVersionAppDialog();
          }
       });
      }
      
      /// 版本更新提示对话框
      Future<void> _showNewVersionAppDialog() async {
        return showDialog<void>(
            context: context,
            barrierDismissible: false,
            builder: (BuildContext context) {
              return AlertDialog(
                title: new Row(
                  children: <Widget>[
                    new Padding(
                        padding: const EdgeInsets.fromLTRB(30.0, 0.0, 10.0, 0.0),
                        child: new Text("发现新版本"))
                  ],
                ),
                content: new Text(
                    serviceVersionCode
                ),
                actions: <Widget>[
                  new FlatButton(
                    child: new Text('下次再说'),
                    onPressed: () {
                      Navigator.of(context).pop();              
                    },
                  ),
                  new FlatButton(
                    child: new Text('立即更新'),
                    onPressed: () {
                      _doUpdate(context);           
                    },
                  )
                ],
              );
            });
    
      }
    
    
     /// 执行更新操作
      _doUpdate(BuildContext context) async {
        Navigator.pop(context);
         _executeDownload(context);
      }
      
      /// 下载最新apk包
      Future<void> _executeDownload(BuildContext context) async {
           pr = new ProgressDialog(
            context,
            type: ProgressDialogType.Download, 
            isDismissible: true, 
            showLogs: true,
          );
          pr.style(message: '准备下载...');
          if (!pr.isShowing()) {
            pr.show();
          }
    
          final path = await _apkLocalPath;
          await FlutterDownloader.enqueue(
            url: 'http://update.rwworks.com:8088/appManager/monitor/app/appload/' + appId + '',
            savedDir: path,
            fileName: apkName,
            showNotification: true,
            openFileFromNotification: true
          );
        }
        
       /// 下载进度回调函数
        static void _downLoadCallback(String id, DownloadTaskStatus status, int progress) {
          final SendPort send = IsolateNameServer.lookupPortByName('downloader_send_port');
          send.send([id, status, progress]);
        }
        
        /// 更新下载进度框
        _updateDownLoadInfo(dynamic data) {
          DownloadTaskStatus status = data[1];
          int progress = data[2];
          if (status == DownloadTaskStatus.running) {
            pr.update(progress: double.parse(progress.toString()), message: "下载中,请稍后…");
          }
          if (status == DownloadTaskStatus.failed) {
            if (pr.isShowing()) {
              pr.hide();
            }
          }
    
          if (status == DownloadTaskStatus.complete) {
            if (pr.isShowing()) {
              pr.hide();
            }
            _installApk();
          }
        }
       
        /// 安装apk
        Future<Null> _installApk() async {
           await OpenFile.open(appPath + '/' + apkName);
        }
        
        /// 获取apk存储位置
        Future<String> get _apkLocalPath async {
          final directory = await getExternalStorageDirectory();
          String path = directory.path  + Platform.pathSeparator + 'Download';;
          final savedDir = Directory(path);
          bool hasExisted = await savedDir.exists();
          if (!hasExisted) {
            await savedDir.create();
          }
          this.setState((){
              appPath = path;
          });
          return path;
        }
    
    

    效果图

    在这里插入图片描述

    展开全文
  • Android App自动更新基本上是每个App都需具备的功能,参考网上各种资料,自己整理了下,先来看看大致的界面: 一、实现思路: 1.发布Android App时,都会生成output-metadata.json文件和对应的apk文件。(不...

    Android App自动更新基本上是每个App都需具备的功能,参考网上各种资料,自己整理了下,先来看看大致的界面:

    一、实现思路:

    1.发布Android App时,都会生成output-metadata.json文件和对应的apk文件。(不知道如何打包发布apk,可以网上搜一下)

    2.output-metadata.json文件里面就记录了发布的程序版本,通过读取此文件来判断是否需要进行更新。

    3.更新过程包括:

    ①下载Apk文件。

    ②安装Apk文件。

    二、实现步骤:

    1.申明权限:由于自动更新需要访问网络,下载更新包,执行安装操作,所以需要申明以下权限:

        <!-- 网络权限 -->
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
        <!-- 在SDCard中创建与删除文件权限 -->
        <uses-permission
            android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
            tools:ignore="ProtectedPermissions" />
        <!-- 存储权限 -->
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        <!-- 安装APK权限 -->
        <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

    另外,配置AndroidManifest.xml文件时,还有2个细节需注意下:

    (1)由于App更新包放在非https的网站下,需要配置app允许访问非http的网站。

    (2)安装App时,Android7.0以上版本需要通过FileProvider方式进行安装,详情可以参考 通过代码安装APK的方法详解

    文件:file_paths.xml

    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <!--安装包文件存储路径-->
        <external-files-path
            name="my_download"
            path="Download" />
        <external-path
            name="."
            path="." />
    </paths>

    文件:network_security_config.xml

    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <base-config cleartextTrafficPermitted="true" />
    </network-security-config>

    2.权限配置完后,现在就开始制作更新程序了。添加更新进度布局。

    文件:progress.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <LinearLayout
            android:id="@+id/titleBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
    
            <TextView
                android:id="@+id/txtStatus"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="状态"
                android:textSize="10sp"
                android:textStyle="normal" />
    
            <ProgressBar
                android:id="@+id/progress"
                style="?android:attr/progressBarStyleHorizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_toLeftOf="@id/txtStatus" />
        </LinearLayout>
    </LinearLayout>

    里面就一个显示百分比的文本框,和一个进度条。

    3.现在进入更新过程的核心操作阶段了,把检查更新,下载apk,安装apk等操作封装成了一个类。

    package com.qingshan.blog;
    
    import android.app.AlertDialog;
    import android.content.Context;
    import android.content.DialogInterface;
    import android.content.Intent;
    import android.content.pm.PackageManager;
    import android.net.Uri;
    import android.os.Build;
    import android.os.Environment;
    import android.os.Handler;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.widget.ProgressBar;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import androidx.core.content.FileProvider;
    
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.net.HttpURLConnection;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class AutoUpdater {
        // 下载安装包的网络路径
        private String apkUrl = "http://qingshanboke.com/uploadfiles/***/rc.***.blog/";
        protected String checkUrl = apkUrl + "output-metadata.json";
    
        // 保存APK的文件名
        private static final String saveFileName = "my.apk";
        private static File apkFile;
    
        // 下载线程
        private Thread downLoadThread;
        private int progress;// 当前进度
        // 应用程序Context
        private Context mContext;
        // 是否是最新的应用,默认为false
        private boolean isNew = false;
        private boolean intercept = false;
        // 进度条与通知UI刷新的handler和msg常量
        private ProgressBar mProgress;
        private TextView txtStatus;
    
        private static final int DOWN_UPDATE = 1;
        private static final int DOWN_OVER = 2;
        private static final int SHOWDOWN = 3;
    
        public AutoUpdater(Context context) {
            mContext = context;
            apkFile = new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), saveFileName);
        }
    
        public void ShowUpdateDialog() {
            AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
            builder.setTitle("软件版本更新");
            builder.setMessage("有最新的软件包,请下载并安装!");
            builder.setPositiveButton("立即下载", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ShowDownloadDialog();
                }
            });
            builder.setNegativeButton("以后再说", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            });
    
            builder.create().show();
        }
    
        private void ShowDownloadDialog() {
            AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
            dialog.setTitle("软件版本更新");
            LayoutInflater inflater = LayoutInflater.from(mContext);
            View v = inflater.inflate(R.layout.progress, null);
            mProgress = (ProgressBar) v.findViewById(R.id.progress);
            txtStatus = v.findViewById(R.id.txtStatus);
            dialog.setView(v);
            dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    intercept = true;
                }
            });
            dialog.show();
            DownloadApk();
        }
    
        /**
         * 检查是否更新的内容
         */
        public void CheckUpdate() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String localVersion = "1";
                    try {
                        localVersion = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName;
                    } catch (PackageManager.NameNotFoundException e) {
                        e.printStackTrace();
                    }
                    String versionName = "1";
                    String outputFile = "";
                    String config = doGet(checkUrl);
                    if (config != null && config.length() > 0) {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                            Matcher m = Pattern.compile("\"outputFile\":\\s*\"(?<m>[^\"]*?)\"").matcher(config);
    
                            if (m.find()) {
                                outputFile = m.group("m");
                            }
                            m = Pattern.compile("\"versionName\":\\s*\"(?<m>[^\"]*?)\"").matcher(config);
                            if (m.find()) {
                                String v = m.group("m");
                                versionName = m.group("m").replace("v1.0.", "");
                            }
                        }
                    }
                    if (Long.parseLong(localVersion) < Long.parseLong(versionName)) {
                        apkUrl = apkUrl + outputFile;
                        mHandler.sendEmptyMessage(SHOWDOWN);
                    } else {
                        return;
                    }
                }
            }).start();
        }
    
        /**
         * 从服务器下载APK安装包
         */
        public void DownloadApk() {
            downLoadThread = new Thread(DownApkWork);
            downLoadThread.start();
        }
    
        private Runnable DownApkWork = new Runnable() {
            @Override
            public void run() {
                URL url;
                try {
                    url = new URL(apkUrl);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.connect();
                    int length = conn.getContentLength();
                    InputStream ins = conn.getInputStream();
                    FileOutputStream fos = new FileOutputStream(apkFile);
                    int count = 0;
                    byte[] buf = new byte[1024];
                    while (!intercept) {
                        int numread = ins.read(buf);
                        count += numread;
                        progress = (int) (((float) count / length) * 100);
                        // 下载进度
                        mHandler.sendEmptyMessage(DOWN_UPDATE);
                        if (numread <= 0) {
                            // 下载完成通知安装
                            mHandler.sendEmptyMessage(DOWN_OVER);
                            break;
                        }
                        fos.write(buf, 0, numread);
                    }
                    fos.close();
                    ins.close();
    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
    
        /**
         * 安装APK内容
         */
        public void installAPK() {
            try {
                if (!apkFile.exists()) {
                    return;
                }
    
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//安装完成后打开新版本
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 给目标应用一个临时授权
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//判断版本大于等于7.0
                    //如果SDK版本>=24,即:Build.VERSION.SDK_INT >= 24,使用FileProvider兼容安装apk
                    String packageName = mContext.getApplicationContext().getPackageName();
                    String authority = new StringBuilder(packageName).append(".fileprovider").toString();
                    Uri apkUri = FileProvider.getUriForFile(mContext, authority, apkFile);
                    intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
                } else {
                    intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
                }
                mContext.startActivity(intent);
                android.os.Process.killProcess(android.os.Process.myPid());//安装完之后会提示”完成” “打开”。
    
    
            } catch (Exception e) {
            }
        }
    
        private Handler mHandler = new Handler() {
            public void handleMessage(android.os.Message msg) {
                switch (msg.what) {
                    case SHOWDOWN:
                        ShowUpdateDialog();
                        break;
                    case DOWN_UPDATE:
                        txtStatus.setText(progress + "%");
                        mProgress.setProgress(progress);
                        break;
                    case DOWN_OVER:
                        Toast.makeText(mContext, "下载完毕", Toast.LENGTH_SHORT).show();
                        installAPK();
                        break;
                    default:
                        break;
                }
            }
    
        };
    
        public static String doGet(String httpurl) {
            HttpURLConnection connection = null;
            InputStream is = null;
            BufferedReader br = null;
            String result = null;
            try {
                URL url = new URL(httpurl);
                connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(15000);
                connection.setReadTimeout(60000);
                connection.connect();
                if (connection.getResponseCode() == 200) {
                    is = connection.getInputStream();
                    br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                    StringBuffer sbf = new StringBuffer();
                    String temp = null;
                    while ((temp = br.readLine()) != null) {
                        sbf.append(temp);
                        sbf.append("\r\n");
                    }
                    result = sbf.toString();
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (null != br) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != is) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
    
                connection.disconnect();
            }
            return result;
        }
    }
    

    注意:上面的 apkUrl即是发布更新包存放的网络路径。其他操作可以参考代码注释,就不再赘述了。

    这里有一个小技巧,可以设置每次打包时,程序按当前时间进行版本号设置。需修改build.gradle文件,像下面这样:

    plugins {
        id 'com.android.application'
    }
    android {
        compileSdkVersion 28
        buildToolsVersion "30.0.3"
        defaultConfig {
            applicationId "com.qingshan.blog"
            minSdkVersion 23
            targetSdkVersion 30
            versionCode 1
            versionName "${releaseTime()}"
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
                android.applicationVariants.all { variant ->
                    variant.outputs.all {
                        outputFileName = "my_${releaseTime()}.apk"
                    }
                }
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    }
    dependencies {
        implementation 'androidx.appcompat:appcompat:1.1.0'
        implementation 'com.google.android.material:material:1.1.0'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.1'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
        implementation 'cn.bingoogolapple:bga-qrcode-zbar:1.3.7'
    }
    def releaseTime() {
        return new Date().format("yyyyMMddHHmmss", TimeZone.getTimeZone("UTC"))
    }
    

    打包后,就可以得到类似的文件结构:

    直接将这2个文件复制到发布服务器上进行发布即可。

    4.在MainActivity.java中进行检查配置。在onCreate方法中加入代码:

            //检查更新
            try {
                //6.0才用动态权限
                if (Build.VERSION.SDK_INT >= 23) {
                    String[] permissions = {
                            Manifest.permission.READ_EXTERNAL_STORAGE,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE,
                            Manifest.permission.ACCESS_WIFI_STATE,
                            Manifest.permission.INTERNET};
                    List<String> permissionList = new ArrayList<>();
                    for (int i = 0; i < permissions.length; i++) {
                        if (ActivityCompat.checkSelfPermission(this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
                            permissionList.add(permissions[i]);
                        }
                    }
                    if (permissionList.size() <= 0) {
                        //说明权限都已经通过,可以做你想做的事情去
                        //自动更新
                        AutoUpdater manager = new AutoUpdater(MainActivity.this);
                        manager.CheckUpdate();
                    } else {
                        //存在未允许的权限
                        ActivityCompat.requestPermissions(this, permissions, 100);
                    }
                }
            } catch (Exception ex) {
                Toast.makeText(MainActivity.this, "自动更新异常:" + ex.getMessage(), Toast.LENGTH_SHORT).show();
            }

    处理权限申请

    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            boolean haspermission = false;
            if (100 == requestCode) {
                for (int i = 0; i < grantResults.length; i++) {
                    if (grantResults[i] == -1) {
                        haspermission = true;
                    }
                }
                if (haspermission) {
                    //跳转到系统设置权限页面,或者直接关闭页面,不让他继续访问
                    permissionDialog();
                } else {
                    //全部权限通过,可以进行下一步操作
                    AutoUpdater manager = new AutoUpdater(MainActivity.this);
                    manager.CheckUpdate();
                }
            }
        }
    
        AlertDialog alertDialog;
    
        //打开手动设置应用权限
        private void permissionDialog() {
            if (alertDialog == null) {
                alertDialog = new AlertDialog.Builder(this)
                        .setTitle("提示信息")
                        .setMessage("当前应用缺少必要权限,该功能暂时无法使用。如若需要,请单击【确定】按钮前往设置中心进行权限授权。")
                        .setPositiveButton("设置", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                cancelPermissionDialog();
                                Uri packageURI = Uri.parse("package:" + getPackageName());
                                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
                                startActivity(intent);
                            }
                        })
                        .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                cancelPermissionDialog();
                            }
                        })
                        .create();
            }
            alertDialog.show();
        }
    
        private void cancelPermissionDialog() {
            alertDialog.cancel();
        }

    至此,就完成了apk自动更新功能。

    需要注意的几个地方:

    1.权限申请一定要对,包括网络权限,存储权限,安装APK权限。

    2.代码安装Apk时,需通过FileProvider方式进行安装。

    3.程序配置了按时间生成版本号,直接对比版本号来进行判断是否需要更新。

    展开全文
  • 关于mui开发的APP自动更新的问题

    千次阅读 2018-09-15 14:56:02
    关于APP自动更新的问题,可是有点坑啊,以下就是我遇到的问题 话不多说,直接上代码 1.先进行获取手机APP当前的信息 const wgtVer = null; //获取当前版本号 plus.runtime.getProperty(plus....
  • vue项目+hbuilder打包实现app自动检测更新并下载vue项目+hbuilder打包实现app自动检测更新并下载
  • uni-app自动更新软件功能实现

    千次阅读 2020-03-03 11:52:32
    <script> let that; export default { data(){ return{ version:"1.0.0",//版本号 platform:"android"//系统平台 ... onLaunch: function() { ... // #ifdef APP-PLUS un...
  • Android如何实现APP自动更新

    万次阅读 2016-11-17 11:02:04
    对于安卓用户来说,手机应用市场说满天飞可是一点都不夸张,比如小米,魅族,百度,360,机锋,应用宝等等,当我们想上线一款新版本APP时,先不说渠道打包的麻烦,单纯指上传APP到各大应用市场的工作量就已经很大了...
  • #Android开发杂记--如何让App自动更新并跳转至安装界面(完美兼容Android10)引言自动更新App的步骤定义FileProviderFileProvider常见问题解析: 引言        一款发行的app不...
  • 3月18日研究-App自动更新通知通知栏下载博客的源代码实例demo,app自动更新判断下载最新版本app的实现
  • 高逼格,超简单,实现App自动更新,一个方法搞定

    万次阅读 多人点赞 2016-07-11 16:31:17
    前言前段时间写了一个篇APP自动更新下载的文章自动更新,一个方法搞定,使用系统的DownloadManager 方法超简洁的实现了apk的下载,不过有好多网友反映有一些机型上面这个方法无法实现下载,经过小编的实验在部分机型...
  • 今天,就来聊一下,如何使用蒲公英第三方平台和DownLoadManager来实现app自动更新的功能。从以下三个方面来说明:** app自动更新流程阐述 如何集成第三方平台(蒲公英) 如何使用系统自带类Downloa
  • Android app应用的自动升级更新

    热门讨论 2013-11-05 17:40:10
    Android APP应用的自动升级更新,可以在本地或连接HTTP进行自动检测,提示是否升级,自动安装。
  • 简单实现安卓app自动更新功能

    万次阅读 多人点赞 2016-03-01 13:25:18
    实现简单安卓app自动更新: 1.服务端提供接口 2.客户端获取接口数据,解析,获取最新版本信息 3.安装最新版本
  • Android实现APP自动更新功能

    千次阅读 2015-01-28 17:03:44
    现在一般的android软件都是需要不断更新的,当你打开某个app的时候,如果有新的版本,它会提示你有新版本需要更新。该小程序实现的就是这个功能。 该小程序的特点是,当有更新时,会弹出一个提示框,点击确定,则在...
  • 原文github地址效果图Android一句话实现APP自动更新带通知栏效果图AndroidStudio使用使用1. AndroidStudio使用allprojects { repositories { maven { url "https://www.jitpack.io" } } } ...
  • [Android]一句话实现APP自动更新(带通知栏)

    千次阅读 热门讨论 2016-07-27 09:55:36
    [Android]一句话实现APP自动更新(带通知栏)@Author GQ 2016年07月27日 基本上所有的APP都会有自动更新功能,一般情况都是根据需求写一个service下载, 还要通知栏同步显示等等... 网上找到这个项目完全省去了自己...
  • uni-app实现APP自动更新并安装

    千次阅读 2020-08-07 09:42:14
    =3){ uni.showToast({ title: '有新的版本发布,检测到您目前非Wifi连接,为节约您的流量,程序已停止自动更新,将在您连接WIFI之后重新检测更新。', mask: false, duration: 5000, icon:"none" }); return; } uni....
  • 使用android 自带的DownloadManager实现文件下载,兼容性强,代码简单,注释详细
  • 先列出需要注意的地方,避免新手朋友们出错 如果uniapp运行app报错:... 如果APPID相同,软件会自动覆盖(所以需要让更新的appid和原来的appid相同,否则就是两个程序了) // 检查版本更新 let _this = this
  • Android APP在线自动更新安装

    千次阅读 2020-05-19 16:37:49
    一、Android App的在线更新,本次先提供两种方式,主要是针对Android6为界,系统对内存文件的读取进行了限制,
  • iOS APP版本自动更新

    千次阅读 2018-07-12 17:20:13
    1.版本自动更新一般采用API对应的方式 获取当前App Store上版本号 于本地存储的版本号对比 2.由服务端返回版本控制升级(容易审核不通过) 参考地址(很详细) ...   想省事的小伙伴们可以直接下载下面文件 ...
  • APP版本自动更新

    千次阅读 2018-09-21 10:34:22
    APP版本自动更新 前端接口调用获取服务器项目版本号 $.ajax({ type: 'POST', url: url + 'api/user/get_version', data: '', dataType: 'json', timeout: 3000, async: false, ...
  • HBuilderX开发app实现自动更新版本

    万次阅读 2019-10-09 15:20:06
      需求说明:使用MUI+Vue等技术并且通过HBuilderX打包开发移动app,在有版本更新时需要自动提示用户有新版本,并且可以点击下载自动安装。 思路说明: 应用打开时(使用Vue的生命周期mounted),获取自己的版本...
  • web打包app(h5+app)版本自动更新的实现

    千次阅读 2020-03-18 09:25:59
    原生app自动更新实现 android自动更新实现 ios自动更新实现 h5+app的特点说明 h5+app自动更新实现 背景说明 web打包的app(也称为h5+app),是指将基于html5等移动端web技术,开发的web应用打包成的app。...
  • uniapp app内实现自动更新

    万次阅读 2019-04-15 18:19:25
    //注意:安卓9.0以上的手机需要在manifest 》app模块权限配置里面勾选PACKAGES 权限不然会无法安装 onLoad(){ this.plusReady(); this.isandroid(); }, methods: { // 获取当前版本号 ...
  • Android实现App版本自动更新

    千次阅读 2018-04-20 16:57:00
    Android实现App版本自动更新现在很多的App中都会有一个检查版本的功能。例如斗鱼TV App的设置界面下: 当我们点击检查更新的时候,就会向服务器发起版本检测的请求。一般的处理方式是:服务器返回的App版本与当前...
  • 需求: app打开时自动检测是否是最新版本,如果不是出现弹框,点击升级
  • 怎样关闭电脑软件上的自动更新?不同的软件,关闭自动更新的方法不同,但是大同小异。以腾讯电脑管家为例,关闭自动更新的方法如下:1,双击运行腾讯电脑管家;2,点击左上角的三个横线(设置图标);3,选择“设置...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 989,476
精华内容 395,790
关键字:

app自动更新

友情链接: spiffs-master.zip