-
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图片
更多相关内容 -
安卓APP自动更新功能实现
2022-02-17 09:54:53安卓APP自动更新功能实现前言代码实现 前言 安卓App自动更新基本上是每个App都需要具备的功能,接下来介绍一下实现自动更新的步骤。 代码实现 App自动更新主要分为新版本检测、升级弹窗、下载升级包、安装app这4个...前言
安卓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:58flutter APP自动更新前言在pubspec.yaml中安装依赖在main.dart文件中,初始化FlutterDownLoader配置网络在AndroidManifest.xml新增如下配置在项目入口dart文件中,新增自动更新逻辑代码效果图 前言 近期做flutter ...flutter APP自动更新
前言
近期做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新增如下配置
- 新增权限
<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" />
- 在<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文件中,新增自动更新逻辑代码
- 导入相关包
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';
- 声明变量
String serviceVersionCode = ''; String appId = ''; ProgressDialog pr; String apkName ='app-release.apk'; String appPath = ''; ReceivePort _port = ReceivePort();
-
在initState中初始化
IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port'); _port.listen(_updateDownLoadInfo); FlutterDownloader.registerCallback(_downLoadCallback);
-
判断,自动更新
@override void afterFirstLayout(BuildContext context) { // 如果是android,则执行热更新 if(Platform.isAndroid){ _getNewVersionAPP(context); } }
- 自动更新代码
/// 执行版本更新的网络请求 _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自动更新功能的实现
2015-11-10 10:14:50android app自动更新功能的实现 -
Android APP 自动更新实现(适用Android9.0)
2021-02-20 21:14:10Android 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.程序配置了按时间生成版本号,直接对比版本号来进行判断是否需要更新。
-
web打包app(h5+app)版本自动更新的实现
2019-09-11 17:19:56文章目录背景说明原生app自动更新实现android自动更新实现ios自动更新实现h5+app的特点说明h5+app自动更新实现 背景说明 web打包的app(也称为h5+app),是指将基于html5等移动端web技术,开发的web应用打包成的app。... -
uniapp打包安卓APP实现自动更新(更新app)
2021-02-18 09:26:09先列出需要注意的地方,避免新手朋友们出错 如果uniapp运行app报错:... 如果APPID相同,软件会自动覆盖(所以需要让更新的appid和原来的appid相同,否则就是两个程序了) // 检查版本更新 let _this = this -
关于mui开发的APP自动更新的问题
2018-09-15 14:56:02关于APP自动更新的问题,可是有点坑啊,以下就是我遇到的问题 话不多说,直接上代码 1.先进行获取手机APP当前的信息 const wgtVer = null; //获取当前版本号 plus.runtime.getProperty(plus.... -
iOS APP版本自动更新
2018-07-12 17:20:131.版本自动更新一般采用API对应的方式 获取当前App Store上版本号 于本地存储的版本号对比 2.由服务端返回版本控制升级(容易审核不通过) 参考地址(很详细) ... 想省事的小伙伴们可以直接下载下面文件 ... -
HBuilderX开发app实现自动更新版本
2019-10-09 15:20:06需求说明:使用MUI+Vue等技术并且通过HBuilderX打包开发移动app,在有版本更新时需要自动提示用户有新版本,并且可以点击下载自动安装。 思路说明: 应用打开时(使用Vue的生命周期mounted),获取自己的版本... -
app自动化打开设置并退出
2022-03-23 20:26:18app自动化打开设置并退出 -
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... -
软件自动更新怎么关闭 安卓关闭软件自动更新
2021-05-26 06:09:463,选择“设置中心”,在版本升级中,设置为不自动更新后,保存即可。怎么取消手机软件自动更新方法:1.选择操作系统桌面上的“安全中心”.2.在安全中心中选择“网络助手”.3.在“网络助手”中选择“联网控制”.4.在... -
h5+实现APP自动下载更新(hbuilder)
2017-07-04 15:21:57这里的自动更新并非是热更新,而只是单纯检测服务器上是否有新的版本,如果有则下载安装。思路:在服务器中配置一个版本文件:xxx.json{ update:’yes’,//是否自动更新 version:’1.0.8’,//最新的版本号 url:’... -
uniapp app内实现自动更新
2019-04-15 18:19:25//注意:安卓9.0以上的手机需要在manifest 》app模块权限配置里面勾选PACKAGES 权限不然会无法安装 onLoad(){ this.plusReady(); this.isandroid(); }, methods: { // 获取当前版本号 ... -
如何去做App自动化以及注意事项
2022-01-07 13:54:59个人总结一下自己做app自动化所遇到的坑,还有一些个人的心得,下面是个人的总结,希望能够给大家带来一些帮助哦,首先我们不管是在app自动化,还是web自动化,元素的定位尤其重要:个人经常使用的定位如下: ... -
#Android开发杂记--如何让App自动更新并跳转至安装界面(完美兼容Android10)
2021-03-20 15:58:02#Android开发杂记--如何让App自动更新并跳转至安装界面(完美兼容Android10)引言自动更新App的步骤定义FileProviderFileProvider常见问题解析: 引言 一款发行的app不... -
Google浏览器设置不自动更新:关闭谷歌浏览器自动更新方法(总是自动更新提示失败)
2019-09-23 17:29:36在windows操作系统,我是windows8,在使用Google浏览器的时候,经常弹窗会报出无法更新,版本太旧,有点繁琐; 下面在网上找到了一些方法: 1、找到路径的Update文件夹:C:\Users\Administrator\AppData\... -
iOS企业版本app下载安装以及自动更新
2020-09-29 23:35:11iOS企业版本app下载安装以及自动更新 https://www.jianshu.com/p/48ad437bf669 iOS企业版版本应用的更新下载不同于个人版本,下面以我做过的项目为例来说明企业版应用更新流程 用户下载安装 一、准备好ipa包。包... -
轻松实现APP自动检测更新
2016-08-24 02:07:47概述:为了让以快速并且节约的方式让APP更新新版本,通常需要在APP内增加自动检测更新新版本的功能。 运行截图: 实现:4个步骤 1.在服务端放置存储版本信息的文件 一般以json格式保存必要的信息... -
App自动化测试怎么做?实战分享App自动化测试全流程
2022-04-12 17:06:31什么是app自动化测试? 概念:所谓app测试也称之为移动测试,通俗易懂的理解就是测试我们平时手机使用的程序。那什么是app自动化测试呢? 通常情况下是随app产品不断迭代更新,给测试⼈员也增加了测试⼯作量,特别是... -
高逼格,超简单,实现App自动更新,一个方法搞定
2016-07-11 16:31:17前言前段时间写了一个篇APP自动更新下载的文章自动更新,一个方法搞定,使用系统的DownloadManager 方法超简洁的实现了apk的下载,不过有好多网友反映有一些机型上面这个方法无法实现下载,经过小编的实验在部分机型... -
Android实现App版本自动更新
2018-04-20 16:57:00Android实现App版本自动更新现在很多的App中都会有一个检查版本的功能。例如斗鱼TV App的设置界面下: 当我们点击检查更新的时候,就会向服务器发起版本检测的请求。一般的处理方式是:服务器返回的App版本与当前... -
android app自动升级demo
2014-06-26 14:24:13该资源是app自动升级代码,主要是获取当前版本和从服务器上获取到新版本,然后对比看是否要更新,从而达到自动更新的功能! -
简单实现安卓app自动更新功能
2016-03-01 13:25:18实现简单安卓app自动更新: 1.服务端提供接口 2.客户端获取接口数据,解析,获取最新版本信息 3.安装最新版本 -
利用蒲公英自动更新APP及其更新机制
2018-03-15 19:07:04App Key:唯一标识一个应用的 Key,在蒲公英上的每一个 App 都有一个唯一的 App Key,开发者可以在应用管理页面首页查看。 2.导入SDK 1> 在project下的build.gradle文件中: allprojects { ... -
vue 实现app项目版本迭代自动更新 热更新
2020-04-14 09:37:47需求: app打开时自动检测是否是最新版本,如果不是出现弹框,点击升级 -
APP 自动化 无法运行——Python
2022-03-21 14:58:22APP自动化 : Python 使用 Appium 实现APP自动化时; 问题描述 BUG 情况说明:可以使用 abd 命令查到手机而无法控制的时候 原因分析: 造成这个问题的原因,可能是 sdk 的 adb 与 夜神 的 adb 版本出现了 不一致;... -
[Android]一句话实现APP自动更新(带通知栏)
2016-07-27 09:55:36[Android]一句话实现APP自动更新(带通知栏)@Author GQ 2016年07月27日 基本上所有的APP都会有自动更新功能,一般情况都是根据需求写一个service下载, 还要通知栏同步显示等等... 网上找到这个项目完全省去了自己...