学更好的别人,
做更好的自己。
——《微卡智享》
本文长度为2499字,预计阅读7分钟
仿应用宝下载安装App
前面几章我们学习了检测App是否安装,能过AsyncTask下载App并调用安装,还有实现进度条按钮的功能,这一篇我们把这几章连着RecyclerView进行一个综合实战练习,在实战过程中又顺便学到了几个新的知识点。
实现效果
根据上面的视频我们可以看到,做了一个检测App是否已经安装,如果未安装直接下载并安装的小Demo效果,接下来我们说说实现方式。
实现思路
定义一个AppInfo的类,里面有App的名称,包名、下载地址,存放的路径及当前的状态标志(未安装,已下载,已安装),再加一个判断当前标志的函数,
利用我们《学习|Android检测并自动下载安装包(Kotlin)》文章中的那个已经封装了的类进行程序下载,这个类在这里又有改动,主要是因为原来的类中AsyncTask都是串行方式,只能一个一个等的下载,这里我重新加了一下并行运行的方式的调用方法
利用RecyclerView的布局,显示程序中文名及包名,右边为进度条的按钮,这样可以实时看到下载进度。
代码实现
微卡智享
我们新建一个AcTest的项目,整体完成的目录如下
activity_main的布局中直接添加一个RecyclerView的控件
CAppInfo
新建一个CAppInfo的类,用于记录我们的App的相关信息
ackage dem.vac.actest
import android.content.Context
import java.io.File
class CAppInfo{
//程序名称
lateinit var AppName: String
//程序包名
lateinit var PackageName: String
//下载文件名称
lateinit var FileName: String
//下载地址
lateinit var DownloadUrl: String
//状态 0-下载 1-已下载 2-已安装
var Status: Int = 0
fun CheckfileStaus(context: Context) {
Status = 0
if (DownloadUrl == "" || FileName == "") {
return
}
if (CheckAppInstall.isAppInstalled(context, PackageName)) {
Status = 2
} else {
var file = File(FileName)
//存在的话说明已下载
if (file.exists()) {
Status = 1
} else {
Status = 0
}
}
}
}
CheckAppInstall
这个就是检测App是否已经安装的类,前面《学习|Android检测并自动下载安装包(Kotlin)》介绍过
package dem.vac.actest
import android.content.Context
import android.content.pm.PackageManager
import android.text.TextUtils
import android.util.Log
import java.lang.Exception
class CheckAppInstall {
companion object StaticFun {
fun isAppInstalled(context: Context, uri: String): Boolean {
var pm: PackageManager = context.packageManager
var installed = false
if(TextUtils.isEmpty(uri)) return installed
try {
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES)
installed = true
} catch (ex: Exception) {
Log.i("install", ex.message)
installed = false
}
return installed
}
}
}
DownloadHelper(划重点、新的知识点)
DownloadHelper前面文章也列出来过,这里一开始直接套用了,在测试过程中发现几个APP同时下载时点击是无反应的,后台网上找了找资料发现用AsyncTask默认的串行的,想要并行实现需要通过ThreadPoolExecutor配合着executeOnExecutor来实现,所以这个类里我又加了一下方法,保留原来的串行方法,然后加了一下并行的方法。
package dem.vac.actest
import android.os.AsyncTask
import java.io.File
import java.io.FileOutputStream
import java.lang.Exception
import java.net.URL
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.Executor
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
class DownloadHelper {
companion object StaticFun {
//定义多线程异步执行
private var Thread_Pool_executor: Executor = ThreadPoolExecutor(
3, 3, 10, TimeUnit.SECONDS,
ArrayBlockingQueue<Runnable>(2)
)
fun download(url: String, localPath: String, listener: OnDownloadListener) {
var task = DownloadAsyncTask(url, localPath, listener)
task.execute()
}
fun downloadasync(url: String, localPath: String, listener: OnDownloadListener) {
var task = DownloadAsyncTask(url, localPath, listener)
task.executeOnExecutor(Thread_Pool_executor)
}
class DownloadAsyncTask(mUrl: String, mFilepath: String, Listener: OnDownloadListener) : AsyncTask<String, Int, Boolean>() {
lateinit var mFailInfo: String
private var mUrl: String = mUrl
private var mFilePath: String = mFilepath
private var mListener: OnDownloadListener = Listener
override fun onPreExecute() {
super.onPreExecute()
this.mListener.onStart()
}
override fun onProgressUpdate(vararg values: Int?) {
super.onProgressUpdate(*values)
if (values.isNotEmpty()) {
values[0]?.let { mListener.onProgress(it) }
}
}
override fun doInBackground(vararg p0: String?): Boolean {
var pdfurl: String = mUrl
try {
var url = URL(pdfurl)
var urlConnection = url.openConnection()
var inputStream = urlConnection.getInputStream()
var contentlen = urlConnection.contentLength
var pdffile = File(mFilePath)
//如果存在直接提示安装
if (pdffile.exists()) {
var result = pdffile.delete()
if (!result) {
mFailInfo = "存储路径下的同名文件删除失败!"
return false
}
}
var downloadSize = 0
var bytes = ByteArray(1024)
var length : Int
var outputStream = FileOutputStream(mFilePath)
do {
length = inputStream.read(bytes)
if (length == -1) break
outputStream.write(bytes, 0, length)
downloadSize += length
publishProgress(downloadSize * 100 / contentlen)
} while (true)
inputStream.close()
outputStream.close()
} catch (ex: Exception) {
ex.printStackTrace()
mFailInfo = ex.message.toString()
return false
}
return true
}
override fun onPostExecute(result: Boolean?) {
super.onPostExecute(result)
if (result!!) {
mListener.onSuccess(File(mFilePath))
} else {
mListener.onFail(File(mFilePath), mFailInfo)
}
}
}
interface OnDownloadListener {
fun onStart()
fun onSuccess(file: File)
fun onFail(file: File, failInfo: String)
fun onProgress(progress: Int)
}
}
}
AppViewAdapter(RecyclerView的操作)
AppViewAdapter中写了相关RecyclerView的UI逻辑操作,算是把我们的RecyclerView重新复习了一下,并且全都改为Kotlin写的
package dem.vac.actest
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat.startActivity
import androidx.core.content.FileProvider
import androidx.recyclerview.widget.RecyclerView
import dem.vac.actest.DownloadHelper.StaticFun.OnDownloadListener
import java.io.File
class AppViewAdapter(context: Context, appinfos: List<CAppInfo>, statusChange: OnItemStatusChange) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mAppinfos: List<CAppInfo> = appinfos
private var mContext: Context = context
private var mStatusLister: OnItemStatusChange = statusChange
class AppViewHolder(context: Context, view: View) : RecyclerView.ViewHolder(view) {
var tvAppName: TextView = view.findViewById(R.id.tvAppName)
var tvPackageName: TextView = view.findViewById(R.id.tvPackageName)
var progressButton: ProgressButton = view.findViewById(R.id.progressbtn)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
var v: View = LayoutInflater.from(mContext).inflate(R.layout.rcl_item, parent, false)
return AppViewHolder(mContext, v)
}
override fun getItemCount(): Int {
return mAppinfos.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
var viewHolder = holder as AppViewHolder
var item = mAppinfos[position]
//检测当前程序状态
item.CheckfileStaus(mContext)
viewHolder.tvAppName.text = item.AppName
viewHolder.tvPackageName.text = item.PackageName
when (item.Status) {
0 -> {
viewHolder.progressButton.setText("下载")
viewHolder.progressButton.setTextColor(Color.WHITE)
}
1 -> {
viewHolder.progressButton.setText("安装")
viewHolder.progressButton.setTextColor(Color.WHITE)
}
2 -> {
viewHolder.progressButton.setText("已安装")
viewHolder.progressButton.setTextColor(Color.WHITE)
}
else -> {
viewHolder.progressButton.setText("重试")
viewHolder.progressButton.setTextColor(Color.WHITE)
}
}
viewHolder.progressButton.setOnProgressButtonClickListener(object :
ProgressButton.OnProgressButtonClickListener {
override fun onClickListener() {
when (item.Status) {
0 -> {
DownloadHelper.downloadasync(
item.DownloadUrl,
item.FileName,
object :
OnDownloadListener {
override fun onStart() {
viewHolder.progressButton.setProgress(0)
mStatusLister.onRefreshAll()
}
override fun onSuccess(file: File) {
viewHolder.progressButton.setText("安装")
item.Status = 1
InstallApp(item)
mStatusLister.onRefreshAll()
}
override fun onFail(file: File, failInfo: String) {
viewHolder.progressButton.setText("重试")
item.Status = 0
mStatusLister.onRefreshAll()
}
override fun onProgress(progress: Int) {
viewHolder.progressButton.setProgress(progress)
viewHolder.progressButton.setText("下载中 $progress%")
}
})
}
1 -> {
InstallApp(item)
}
}
}
})
}
//安装
private fun InstallApp(item: CAppInfo) {
val intent = Intent(Intent.ACTION_VIEW)
val uri: Uri
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
uri = FileProvider.getUriForFile(
mContext,
mContext.packageName + ".provider",
File(item.FileName)
)
} else {
uri = Uri.fromFile(File(item.FileName))
}
intent.setDataAndType(
uri,
"application/vnd.android.package-archive"
)
startActivity(mContext, intent, null)
}
}
interface OnItemStatusChange {
fun onRefreshAll()
}
上在最后我加了一个了OnItemStatusChange的接口,在下载完或安装是周用重新刷新一下界面显示的,不过效果只能说挺一般,有小伙伴如果有更好的思路也可以留言告诉我。
我们看一下动图的展示效果
源码地址
https://github.com/Vaccae/AcTest.git
完