Android7.0下载Apk自动安装

1. 整体需求

  1. 下载APK文件

    • 使用DownloadManager来下载
    • 在应用界面中展示下载进度
  2. 安装下载后的APK文件
    • root模式: 可以自动安装,不需要用户主动点击
    • 正常模式: 弹出安装应用页面,需要兼容7.0以上版本

2. DownloadManager

DownloadManager是Android提供的用于下载的类,使用起来比较简单,它包含两个静态内部类DownloadManager.Query和DownloadManager.Request;
DownloadManager.Request用来请求一个下载,DownloadManager.Query用来查询下载信息

2.1. 下载

1. 获取DownloadManager对象

DownloadManager对象属于系统服务,通过getSystemService来进行安装

DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);

一般获取完成后会变成全局变量,方便之后使用

2. 开始下载

在使用DownloadManager进行下载的时候,就会用到DownloadManager.Request

//使用DownLoadManager来下载
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));
//将文件下载到自己的Download文件夹下,必须是External的
//这是DownloadManager的限制
File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "test.apk");
request.setDestinationUri(Uri.fromFile(file));
//添加请求 开始下载
long downloadId = mDownloadManager.enqueue(request);

首先会创建出一个DownloadManager.Request对象,在构造方法中接收Uri,其实就是下载地址,
然后是文件的存放路径,这里需要说明,DownloadManager下载的位置是不能放到内置存贮位置的,必须放到Enviroment中,这里建议放到自己应用的文件夹,不要直接放到SD卡中,也就是通过getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)获取到的路径,该位置的文件是属于应用自己的,在应用卸载时也会随着应用一起被删除掉,并且在使用该文件夹的时候,是不需要SD卡读写权限的
然后通过request.setDestinationUri来设置存储位置,最后将请求加入到downloadManager中,会获得一个downloadID,这个downloadID比较重要,之后下载状态,进度的查询都靠这个downloadID

2.2. 进度查询

在查询下载进度的时候,会通过downloadId来指定查询某一任务的具体进度

/*** 获取进度信息* @param downloadId 要获取下载的id* @return 进度信息 max-100*/
public int getProgress(long downloadId) {//查询进度DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);Cursor cursor = null;int progress = 0;try {cursor = mDownloadManager.query(query);//获得游标if (cursor != null && cursor.moveToFirst()) {//当前的下载量int downloadSoFar = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));//文件总大小int totalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));progress = (int) (downloadSoFar * 1.0f / totalBytes * 100);}} finally {if (cursor != null) {cursor.close();}}return progress;
}

在查询进度的时候会使用到DownloadManager.Query这个类,在查询的时候,也是使用的Cursor,跟查询数据库是一样的,进度信息会需要拿到文件的总大小,和当前大小,自己算一下,最后Cursor对象在使用过后不要忘记关闭了

2.3 下载完成

下载完成后,DownloadManager会发送一个广播,并且会包含downloadId的信息

//下载完成的广播
private class DownloadFinishReceiver extends BroadcastReceiver{@Overridepublic void onReceive(Context context, Intent intent) {//下载完成的广播接收者long completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);}
}

注册这个广播接收者

//注册下载完成的广播
mReceiver = new DownloadFinishReceiver();
registerReceiver(mReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

其他

这里需要注意一点,在下载完成后需要提升一下文件的读写权限,否则在安装的时候会出现apk解析失败的页面,就是别人访问不了我们的apk文件

/*** 提升读写权限* @param filePath 文件路径* @return* @throws IOException*/
public static void setPermission(String filePath)  {String command = "chmod " + "777" + " " + filePath;Runtime runtime = Runtime.getRuntime();try {runtime.exec(command);} catch (IOException e) {e.printStackTrace();}
}

chmod 是Linux下设置文件权限的命令,后面的三个数字每一个代表不同的用户组
权限分为三种:读(r=4),写(w=2),执行(x=1)
那么这三种权限就可以组成7种不同的权限,分别用1-7这几个数字代表,例如7 = 4 + 2 + 1,那么就代表该组用户拥有可读,可写,可执行的权限;5 = 4 + 1,就代表可读可执行权限
而三位数字就带包,该登陆用户,它所在的组,以及其他人

安装

1. 普通模式

1. 7.0之前

在7.0之前安装的时候,只需要通过隐式Intent来跳转,并且指定安装的文件Uri即可

Intent intent = new Intent(Intent.ACTION_VIEW);
// 由于没有在Activity环境下启动Activity,设置下面的标签
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(new File(apkPath)),"application/vnd.android.package-archive");context.startActivity(intent);

2. 7.0之后

在Android7.0之后的版本运行上述代码会出现 android.os.FileUriExposedException
“私有目录被限制访问”是指在Android7.0中为了提高私有文件的安全性,面向 Android N 或更高版本的应用私有目录将被限制访问。
而7.0的” StrictMode API 政策” 是指禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常。
之前代码用到的Uri.fromFile就是商城一个file://的Uri
在7.0之后,我们需要使用FileProvider来解决

FileProvider

第一步:
在AndroidManifest.xml清单文件中注册provider

<provider
    android:name="android.support.v4.content.FileProvider"android:authorities="com.example.chenfengyao.installapkdemo"android:grantUriPermissions="true"android:exported="false"><!--元数据--><meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_path" />
</provider>

需要注意一下几点:
1. exported:必须为false
2. grantUriPermissions:true,表示授予 URI 临时访问权限。
3. authorities 组件标识,都以包名开头,避免和其它应用发生冲突。

第二步:
指定共享文件的目录,需要在res文件夹中新建xml目录,并且创建file_paths

<resources xmlns:android="http://schemas.android.com/apk/res/android"><paths><external-path path="" name="download"/></paths>
</resources>

path=”“,是有特殊意义的,它代表根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了。

第三部:
使用FileProvider

Intent intent = new Intent(Intent.ACTION_VIEW);
File file = (new File(apkPath));
// 由于没有在Activity环境下启动Activity,设置下面的标签
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致   参数3  共享的文件
Uri apkUri = FileProvider.getUriForFile(context, "com.example.chenfengyao.installapkdemo", file);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
context.startActivity(intent);

相较于之前的代码,会把Uri改成使用FiliProvider创建的Uri,并且添加intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)来对目标应用临时授权该Uri所代表的文件,而且getUriForFile中的authority参数需要填写清单文件中的authorities的值

3. 混合

兼容7.0的安装代码是不能在7.0之前的版本运行的,这个时候就需要进行版本的判断了

//普通安装
private static void installNormal(Context context,String apkPath) {Intent intent = new Intent(Intent.ACTION_VIEW);//版本在7.0以上是不能直接通过uri访问的if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {File file = (new File(apkPath));// 由于没有在Activity环境下启动Activity,设置下面的标签intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致   参数3  共享的文件Uri apkUri = FileProvider.getUriForFile(context, "com.example.chenfengyao.installapkdemo", file);//添加这一句表示对目标应用临时授权该Uri所代表的文件intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);intent.setDataAndType(apkUri, "application/vnd.android.package-archive");} else {intent.setDataAndType(Uri.fromFile(new File(apkPath)),"application/vnd.android.package-archive");}context.startActivity(intent);
}

2.root模式

如果应用已经获取了root权限了,那么我们可以实现自动安装,即不会出现应用安装的页面,会在后台自己慢慢的安装,这个时候使用的就是用代码去写命令行了

/*** 应用程序运行命令获取 Root权限,设备必须已破解(获得ROOT权限)** @param command 命令:String apkRoot="chmod 777 "+getPackageCodePath(); RootCommand(apkRoot);* @return  0 命令执行成功*/
public static int RootCommand(String command) {Process process = null;DataOutputStream os = null;try {process = Runtime.getRuntime().exec("su");os = new DataOutputStream(process.getOutputStream());os.writeBytes(command + "\n");os.writeBytes("exit\n");os.flush();int i = process.waitFor();Log.d("SystemManager", "i:" + i);return i;} catch (Exception e) {Log.d("SystemManager", e.getMessage());return -1;} finally {try {if (os != null) {os.close();}process.destroy();} catch (Exception e) {}}
}

这个方法就是将命令写入到手机的shell中,su就代表root权限了,而命令执行成功的话,会返回0的,接下来是安装命令

String command = "pm install -r " + mApkPath;

-r 代表强制安装,否则如果手机中已有该应用的话就会安装失败了,值得注意的是,要想等待命令执行的结果这个过程是很漫长的,所以在使用命令的时候是需要放到主线程中的

3. 整体项目

在写完整代码的时候需要把下载的代码写到Service中,否则你的downloadid就得通过别的方式去存储了,而查询下载进度,也是需要一直去查了,那么就需要写一个循环,并且放到子线程中,我们用RxJava做会比较舒服

1. 一些工具代码

1. IOUtils

package com.example.chenfengyao.installapkdemo.utils;import android.content.Context;
import android.os.Environment;import java.io.Closeable;
import java.io.File;
import java.io.IOException;/*** Created by 陈丰尧 on 2017/4/16.*/public class IOUtils {public static void closeIO(Closeable... closeables) {if (closeables != null) {for (Closeable closeable : closeables) {if (closeable != null) {try {closeable.close();} catch (IOException e) {e.printStackTrace();}}}}}/*** 删除之前的apk** @param apkName apk名字* @return*/public static File clearApk(Context context, String apkName) {File apkFile = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), apkName);if (apkFile.exists()) {apkFile.delete();}return apkFile;}
}

这里面主要用到了删除之前apk的代码,下载前如果有历史版本,就把它删掉,下载新的

2. InstallUtil

package com.example.chenfengyao.installapkdemo.utils;import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.support.v4.content.FileProvider;
import android.widget.Toast;import java.io.File;import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;/*** If there is no bug, then it is created by ChenFengYao on 2017/4/19,* otherwise, I do not know who create it either.*/
public class InstallUtil {/**** @param context* @param apkPath 要安装的APK* @param rootMode 是否是Root模式*/public static void install(Context context, String apkPath,boolean rootMode){if (rootMode){installRoot(context,apkPath);}else {installNormal(context,apkPath);}}/*** 通过非Root模式安装* @param context* @param apkPath*/public static void install(Context context,String apkPath){install(context,apkPath,false);}//普通安装private static void installNormal(Context context,String apkPath) {Intent intent = new Intent(Intent.ACTION_VIEW);//版本在7.0以上是不能直接通过uri访问的if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {File file = (new File(apkPath));// 由于没有在Activity环境下启动Activity,设置下面的标签intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致   参数3  共享的文件Uri apkUri = FileProvider.getUriForFile(context, "com.example.chenfengyao.installapkdemo", file);//添加这一句表示对目标应用临时授权该Uri所代表的文件intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);intent.setDataAndType(apkUri, "application/vnd.android.package-archive");} else {intent.setDataAndType(Uri.fromFile(new File(apkPath)),"application/vnd.android.package-archive");}context.startActivity(intent);}//通过Root方式安装private static void installRoot(Context context, String apkPath) {Observable.just(apkPath).map(mApkPath -> "pm install -r " + mApkPath).map(SystemManager::RootCommand).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(integer -> {if (integer == 0) {Toast.makeText(context, "安装成功", Toast.LENGTH_SHORT).show();} else {Toast.makeText(context, "root权限获取失败,尝试普通安装", Toast.LENGTH_SHORT).show();install(context,apkPath);}});}
}

该类只负责安装APK,如果是Root模式的话,会首先进行尝试,如果失败了,还会调用一次普通模式,进行安装的,注意root模式安装的代码,不要忘记放到子线程中去执行了

3. SystemManager

package com.example.chenfengyao.installapkdemo.utils;import android.util.Log;import java.io.DataOutputStream;
import java.io.IOException;/*** Created by 陈丰尧 on 2017/4/16.*/public class SystemManager {/*** 应用程序运行命令获取 Root权限,设备必须已破解(获得ROOT权限)** @param command 命令:String apkRoot="chmod 777 "+getPackageCodePath();* @return  0 命令执行成功*/public static int RootCommand(String command) {Process process = null;DataOutputStream os = null;try {process = Runtime.getRuntime().exec("su");os = new DataOutputStream(process.getOutputStream());os.writeBytes(command + "\n");os.writeBytes("exit\n");os.flush();int i = process.waitFor();Log.d("SystemManager", "i:" + i);return i;} catch (Exception e) {Log.d("SystemManager", e.getMessage());return -1;} finally {try {if (os != null) {os.close();}process.destroy();} catch (Exception e) {}}}/*** 提升读写权限* @param filePath 文件路径* @return* @throws IOException*/public static void setPermission(String filePath)  {String command = "chmod " + "777" + " " + filePath;Runtime runtime = Runtime.getRuntime();try {runtime.exec(command);} catch (IOException e) {e.printStackTrace();}}}

该类主要就是放一些需要写入到shell中的代码

2. DownLoadService

package com.example.chenfengyao.installapkdemo;import android.app.DownloadManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
import android.util.LongSparseArray;import com.example.chenfengyao.installapkdemo.utils.IOUtils;
import com.example.chenfengyao.installapkdemo.utils.InstallUtil;
import com.example.chenfengyao.installapkdemo.utils.SystemManager;import java.io.File;/*** If there is no bug, then it is created by ChenFengYao on 2017/4/20,* otherwise, I do not know who create it either.*/
public class DownloadService extends Service {private DownloadManager mDownloadManager;private DownloadBinder mBinder = new DownloadBinder();private LongSparseArray<String> mApkPaths;private boolean mIsRoot = false;private DownloadFinishReceiver mReceiver;@Overridepublic void onCreate() {super.onCreate();mDownloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);mApkPaths = new LongSparseArray<>();//注册下载完成的广播mReceiver = new DownloadFinishReceiver();registerReceiver(mReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));}@Nullable@Overridepublic IBinder onBind(Intent intent) {return mBinder;}@Overridepublic void onDestroy() {unregisterReceiver(mReceiver);//取消注册广播接收者super.onDestroy();}public class DownloadBinder extends Binder{/*** 下载* @param apkUrl 下载的url*/public long startDownload(String apkUrl){//点击下载//删除原有的APKIOUtils.clearApk(DownloadService.this,"test.apk");//使用DownLoadManager来下载DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));//将文件下载到自己的Download文件夹下,必须是External的//这是DownloadManager的限制File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "test.apk");request.setDestinationUri(Uri.fromFile(file));//添加请求 开始下载long downloadId = mDownloadManager.enqueue(request);Log.d("DownloadBinder", file.getAbsolutePath());mApkPaths.put(downloadId,file.getAbsolutePath());return downloadId;}public void setInstallMode(boolean isRoot){mIsRoot = isRoot;}/*** 获取进度信息* @param downloadId 要获取下载的id* @return 进度信息 max-100*/public int getProgress(long downloadId) {//查询进度DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);Cursor cursor = null;int progress = 0;try {cursor = mDownloadManager.query(query);//获得游标if (cursor != null && cursor.moveToFirst()) {//当前的下载量int downloadSoFar = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));//文件总大小int totalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));progress = (int) (downloadSoFar * 1.0f / totalBytes * 100);}} finally {if (cursor != null) {cursor.close();}}return progress;}}//下载完成的广播private class DownloadFinishReceiver extends BroadcastReceiver{@Overridepublic void onReceive(Context context, Intent intent) {//下载完成的广播接收者long completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);String apkPath = mApkPaths.get(completeDownloadId);Log.d("DownloadFinishReceiver", apkPath);if (!apkPath.isEmpty()){SystemManager.setPermission(apkPath);//提升读写权限,否则可能出现解析异常InstallUtil.install(context,apkPath,mIsRoot);}else {Log.e("DownloadFinishReceiver", "apkPath is null");}}}
}
  • Service和Client通信是使用Binder来做的,提供开始下载,设置安装模式和获取进度的方法
  • DownloadFinishReceiver是用来监听下载完成的广播接收者,当下载完成后就直接调用InstallUtil来去自动安装,广播再使用过后不要忘记取消监听了
  • LongSparseArray 可以理解为key值是long类型的HashMap,但是效率要稍高一点,在Android中都推荐使用各种的SparseArray

3. Activity

1. 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"><ProgressBar
        android:id="@+id/down_progress"android:max="100"style="@style/Widget.AppCompat.ProgressBar.Horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"/><Button
        android:id="@+id/down_btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="开始下载"/><Switch
        android:id="@+id/install_mode_switch"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="普通模式"/></LinearLayout>

布局文件就比较简单了,progressBar来显示进度,switch来切换模式,然后就是一个下载的按钮

2. Activity

package com.example.chenfengyao.installapkdemo;import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Switch;
import android.widget.Toast;import java.util.concurrent.TimeUnit;import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;public class MainActivity extends AppCompatActivity {private static final String APK_URL = "http://101.28.249.94/apk.r1.market.hiapk.com/data/upload/apkres/2017/4_11/15/com.baidu.searchbox_034250.apk";private Switch installModeSwitch;private ProgressBar mProgressBar;private Button mDownBtn;private DownloadService.DownloadBinder mDownloadBinder;private Disposable mDisposable;//可以取消观察者private ServiceConnection mConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {mDownloadBinder = (DownloadService.DownloadBinder) service;}@Overridepublic void onServiceDisconnected(ComponentName name) {mDownloadBinder = null;}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);installModeSwitch = (Switch) findViewById(R.id.install_mode_switch);mProgressBar = (ProgressBar) findViewById(R.id.down_progress);mDownBtn = (Button) findViewById(R.id.down_btn);Intent intent = new Intent(this, DownloadService.class);startService(intent);bindService(intent, mConnection, BIND_AUTO_CREATE);//绑定服务installModeSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {if (isChecked) {buttonView.setText("root模式");} else {buttonView.setText("普通模式");}if (mDownloadBinder != null) {mDownloadBinder.setInstallMode(isChecked);}});mDownBtn.setOnClickListener(v -> {if (mDownloadBinder != null) {long downloadId = mDownloadBinder.startDownload(APK_URL);startCheckProgress(downloadId);}});}@Overrideprotected void onDestroy() {if (mDisposable != null) {//取消监听mDisposable.dispose();}super.onDestroy();}//开始监听进度private void startCheckProgress(long downloadId) {Observable.interval(100, 200, TimeUnit.MILLISECONDS, Schedulers.io())//无限轮询,准备查询进度,在io线程执行.filter(times -> mDownloadBinder != null).map(i -> mDownloadBinder.getProgress(downloadId))//获得下载进度.takeUntil(progress -> progress >= 100)//返回true就停止了,当进度>=100就是下载完成了.distinct()//去重复.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new ProgressObserver());}//观察者private class ProgressObserver implements Observer<Integer> {@Overridepublic void onSubscribe(Disposable d) {mDisposable = d;}@Overridepublic void onNext(Integer progress) {mProgressBar.setProgress(progress);//设置进度}@Overridepublic void onError(Throwable throwable) {throwable.printStackTrace();Toast.makeText(MainActivity.this, "出错", Toast.LENGTH_SHORT).show();}@Overridepublic void onComplete() {mProgressBar.setProgress(100);Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();}}
}
  • 在Activity中需要startService和bindService都使用,因为我们需要和Service建立联系,又需要让Service脱离Activity的运行
  • 主要说一下checkProgress的代码,在该方法中会使用RxJava来达到轮询的功能
    • interval: 该操作符会一直无限的发射事件,从1,2,3,一直这样下去,100代表第一个事件延迟100ms,200代表每个事件之间有200ms的间隔
    • filter: 会过滤掉不符合条件的事件,例如如果binder为空的话,事件就不往下传递了
    • map: 当事件到这里的时候,就通过binder来查询一下进度
    • takeUntil: 事件持续到什么时候为止,因为interval是无限发射的,总需要一个结束的情况,就使用这个takeUntil,一直到进度达到100的时候就不再查询了,相当于跳出循环的条件,会触发观察者的onComplete方法
    • distinct: 去重,因为下载进度会查到很多的重复数据,这些数据没必要都设置到progressBar中,可以利用该操作符去去重
    • 线程切换: 子线程发布事件,主线程观察,要刷新UI嘛
    • 最后订阅一个观察者,这个观察者也是自己的类实现了Observer的接口
  • ProgressObserver:
    • 在RxJava2中,Observer会在订阅的时候传入一个Disposable,该对象可以允许观察者主动的去取消事件,在Activity的onDestroy中会去取消事件
    • onNext中是设置给ProgressBar进度信息
    • onComplete是下载完成

完整代码

下载地址 : http://download.csdn.net/download/cfy137000/9820195

Android7.0下载Apk自动安装相关推荐

  1. php 自动下载apk,Android 下载apk 自动 安装

    Android N 后,由于不能访问私有路径,需要设置成共享文件 /** * android N 执行此安装方法 * * @param context 上下文 * @param file 文件路径 * ...

  2. android 监听安装来源_Flutter插件开发之APK自动安装

    点击上方的终端研发部,右上角选择"设为星标" 每日早9点半,技术文章准时送上 公众号后台回复"学习",获取作者独家秘制精品资料 往期文章 记五月的一个Andro ...

  3. android下载后的app自动安装,Android 7.0 下载APK后自动安装

    随着Android版本越来越高,Android对隐私的保护力度也越来越大.这些隐私权限的更改在为用户带来更加安全的操作系统的同时也为开发者带来了一些新的任务.如何让你的APP能够适应这些改变而不是崩溃 ...

  4. android9 apk自动安装功能,如何在Android7.0、8.0、9.0系统下通过Intent安装apk

    public static void installApk(Context context, String apkPath) { if (context == null || TextUtils.is ...

  5. DownloadManager下载APK并安装(适配7.0,免费下载)

    效果图## DownloadManager(主角)## 作用:下载管理器是一个处理长时间运行的HTTP下载的系统服务.客户端可能要求将URI下载到特定的目标文件.下载管理器将在后台进行下载. 过程:在 ...

  6. android apk自动安装包下载,Android实现应用下载并自动安装apk包

    安装: String str = "/CanavaCancel.apk"; String fileName = Environment.getExternalStorageDire ...

  7. android 7.0不能自动安装失败,解决Android7.0更新后无法安装的问题

    最近在我们的应用中加入更新功能,按照往常一样加入代码 if (!apkfile.exists()) { Toast.makeText(mContext, "下载的安装包不存在", ...

  8. Android 天气APP(二十六)增加自动更新(检查版本、通知栏下载、自动安装)

    上一篇:Android 天气APP(二十五)地图天气(下)嵌套滑动布局渲染天气数据 效果图 开发流程 1.开发前言 2.上传应用到分发平台 3.版本数据请求与存储 4.检查版本更新.自定义更新提示弹窗 ...

  9. Win7下MATLAB 7.0下载地址+详细安装+运行错误解决

    MATLAB 7.0下载地址 http://pan.baidu.com/share/link?shareid=10874&uk=3928989303 ed2k链接下载地址,打开迅雷或者旋风后, ...

最新文章

  1. 【组队学习】【26期】图神经网络
  2. Gradle的安装和在idea的配置
  3. c++实现ftp服务器_第三步,尝试用树莓派搭建你的云计算平台和服务器
  4. 对称加密与非对称加密的区别_https原理及对称加密、非对称加密、数字证书、数字签名的含义...
  5. 微信分享无响应的解决
  6. Linux 命令之 rpm -- RPM 软件包的管理工具
  7. JAVA中日期格式SimpleDateFormat
  8. JBPM4.4总结-嵌入自己的用户体系(集成自定义用户表)
  9. java has a 关系,Java组成(has-a)关系澄清
  10. linux 查看硬盘的uuid_ubuntu16.04 挂载新硬盘
  11. 阿里巴巴面试题含答案
  12. html+css 背景图片铺满并居中
  13. mysql获取汉字首字母拼音,包括复杂字
  14. 网络表示学习简单总结(一)
  15. Linux系统重装时保留重要分区
  16. 光标大小怎么调_cad十字光标和坐标系,你必知的几个小技巧!
  17. java定时器整点报时_单片机 整点报时 定时小闹钟程序
  18. 女人:不爱,请收起你的暧昧
  19. Beacon模式下的设置!
  20. 以太猫合约之基础合约分析(一)

热门文章

  1. 【设计模式C++】工厂模式
  2. python引入同一目录下的py文件
  3. NATIVE SQL的用法
  4. Jupyter Notebook 如何安装 + 使用?【审核5次重磅发布】
  5. 深度强化学习系列(1): 深度强化学习概述
  6. PyTorch安装与环境配置
  7. 自动化生产线实训系统,自动化生产线实训装置QY-JDYT
  8. 金融新科技的一些玩法
  9. 英雄联盟包括云顶无限闪退重连解决方案
  10. 京东健康和药明康德入股卫宁软件,分别持有7.7%股权