学习|Android检测并自动下载安装包(Kotlin)

  • 2019 年 12 月 11 日
  • 笔记

本文长度为2819,预计阅读6分钟

Android检测并自动下载安装包

上一篇文章《学习|Android使用TTS语音合成》我们学习了Android用TTS语音合成播放声音,其中因为要播放中文,所以需要下载讯飞的语音合成包,项目应用中的话如果让用户自己寻找并下载太麻烦,所以为了增加用户体验,这一篇我们就研究一下怎么检测是否需要下载安装包,如果需要并自动下载。

实现效果

实现思路

1. 初始化TTS之前,先检测讯飞语音合成的包是否已经安装

2. 如果安装,直接进行初始化配置,如果未安装检测是否能访问外网

3. 不能访问外网直接提示初始化失败,能访问外网自动下载安装包

4. 下载完成后显示点击安装按钮进行安装,再加入一个调用TTS配置按钮进行语音设置

代码实现

DownloadHelper类

这个类是从网上找的,通过AsyncTask的方式实现安装包的下载,加入了一个onDownloadInferface的接口实现,网上的这个类是JAVA写的,这里我自己用Kotlin重新写了一篇(其实复制过来可以自己转换的),但是这样对自己学习Kotlin没有什么太大帮助,直接贴出代码,其中外部调用时在Java中的静态方法直接前面加上static即可,Kotlin中需要改为companion boject XXXX {}写入才可以

package dem.vac.ttsdemo    import android.os.AsyncTask  import java.io.File  import java.io.FileOutputStream  import java.lang.Exception  import java.net.URL    class DownloadHelper {        companion object StaticFun {            fun download(url: String, localPath: String, listener: OnDownloadListener) {              var task = DownloadAsyncTask(url, localPath, listener)              task.execute()          }            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)          }      }  }

下载时的进度框

我们新建了一个DownloadActivity,布局文件中加入一个textview,一个进度条,和一个按钮,如下

<?xml version="1.0" encoding="utf-8"?>  <RelativeLayout 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="300dp"      android:layout_height="250dp"      android:layout_gravity="center"      android:background="@color/colorDefBlue"      android:padding="30dp"      tools:context=".DownloadActivity">        <TextView          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:id="@+id/tvstatus"          android:textColor="@color/colorWhite"          android:layout_marginBottom="5dp"          android:layout_above="@+id/progressbar"          android:text="正在下载。。。。" />        <ProgressBar          android:layout_centerInParent="true"          android:layout_width="match_parent"          android:layout_height="wrap_content"          android:id="@+id/progressbar"          style="@android:style/Widget.ProgressBar.Horizontal"          android:progress="0" />          <Button          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:background="@color/colorPrimary"          android:textColor="@color/colorWhite"          android:layout_alignParentBottom="true"          android:layout_centerHorizontal="true"          android:id="@+id/btndo"          android:text="当前操作" />    </RelativeLayout>

DownloadActivity文件中我们把布局文件控件加载完后直接调用DownloadHelper,并重写了相关的onStart,onSuccess,onFail和onProgress事件

package dem.vac.ttsdemo    import android.content.Intent  import android.net.Uri  import android.os.Build  import android.os.Bundle  import android.os.Environment  import android.view.View  import android.view.Window  import android.widget.Button  import android.widget.ProgressBar  import android.widget.TextView  import androidx.appcompat.app.ActionBar  import androidx.appcompat.app.AppCompatActivity  import androidx.core.content.FileProvider  import dem.vac.ttsdemo.DownloadHelper.StaticFun.OnDownloadListener  import java.io.File    class DownloadActivity : AppCompatActivity() {        lateinit var btndo: Button      lateinit var progress: ProgressBar      lateinit var tvstatus: TextView      lateinit var actionBar: ActionBar        private val downloadurl: String = "http://www.sumsoft.cn/apk/TTSChina.apk"      private val filename: String = "TTSChina.apk"          override fun onCreate(savedInstanceState: Bundle?) {          super.onCreate(savedInstanceState)          requestWindowFeature(Window.FEATURE_NO_TITLE)          setContentView(R.layout.activity_download)            initControl()          startdownload()      }        private fun initControl() {          tvstatus = findViewById(R.id.tvstatus)          progress = findViewById(R.id.progressbar)          btndo = findViewById(R.id.btndo)      }        private fun startdownload() {          var localpath: String =              Environment.getExternalStorageDirectory().absolutePath + File.separator + "SUM" + File.separator + filename          DownloadHelper.download(              downloadurl, localpath, object : OnDownloadListener {                  override fun onStart() {                      tvstatus.text = "正在下载中....."                      btndo.visibility = View.GONE                      progress.progress = 0                  }                    override fun onSuccess(file: File) {                      tvstatus.text = "下载完成!"                      btndo.visibility = View.VISIBLE                      btndo.text = "点击安装"                      btndo.setOnClickListener {                          var intent = Intent(Intent.ACTION_VIEW)                          var uri: Uri                          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {                              intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)                              uri = FileProvider.getUriForFile(                                  applicationContext,                                  applicationContext.packageName + ".provider",                                  File(localpath)                              )                          } else {                              uri = Uri.fromFile(File(localpath))                          }                            intent.setDataAndType(                              uri,                              "application/vnd.android.package-archive"                          )                            startActivity(intent)                      }                    }                    override fun onFail(file: File, failInfo: String) {                      tvstatus.text = "下载失败!" + failInfo                      btndo.visibility = View.GONE                    }                    override fun onProgress(pro: Int) {                      tvstatus.text = "正在下载中..... $pro%"                      progress.progress = pro                  }                })        }    }

其中要注意的地方是下图红框中,在Android的SDK23后访问下载路径有变化了,当我们下载完成提示点击安装时要注意下面的情况

对应的AndroidManifest.xml中也要加入

           <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_paths" />          </provider>

上面代码中的resourec="@xml/file_paths"中我们也在要RES下创建相应的xml文件,如下图

检测是否安装了程序包

我们新建了一个CheckAppInstall的类,然后写了一个静态函数用于检测想要的安装包是否已经安装

package dem.vac.ttsdemo    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          }      }  }

MainActivity中调用

package dem.vac.ttsdemo    import android.Manifest  import android.Manifest.permission.WRITE_EXTERNAL_STORAGE  import android.content.Intent  import android.content.pm.PackageManager  import android.os.Bundle  import android.speech.tts.TextToSpeech  import android.widget.Button  import android.widget.EditText  import android.widget.TextView  import androidx.appcompat.app.AppCompatActivity  import androidx.core.app.ActivityCompat  import androidx.core.content.ContextCompat.checkSelfPermission  import java.util.*    class MainActivity : AppCompatActivity() {        lateinit var tvshow: TextView      lateinit var edtinput: EditText      lateinit var btn1: Button      lateinit var btn2: Button      lateinit var mSpeech: TextToSpeech        //检测是否安装了讯飞TTS      fun CheckTTS(): Boolean {          return CheckAppInstall.isAppInstalled(this, "com.iflytek.tts")      }        override fun onCreate(savedInstanceState: Bundle?) {          super.onCreate(savedInstanceState)          setContentView(R.layout.activity_main)            requestPermission()            tvshow = findViewById(R.id.tvshow)            if (!CheckTTS()) {              intent = Intent(this, DownloadActivity::class.java)              startActivity(intent)          }            mSpeech = TextToSpeech(this, TextToSpeech.OnInitListener {              if (it == TextToSpeech.SUCCESS) {                  val i = mSpeech.setLanguage(Locale.CHINESE)                  if (i == TextToSpeech.LANG_MISSING_DATA || i == TextToSpeech.LANG_NOT_SUPPORTED) {                      mSpeech.setSpeechRate(1.0f)                      tvshow.text = "设置中文语音失败"                  } else {                      tvshow.text = "初始化成功"                  }              } else {                  tvshow.text = "初始化失败"              }          })            edtinput = findViewById(R.id.edttext)              btn1 = findViewById(R.id.btn1)          btn1.setOnClickListener { view ->              var str: String = edtinput.text.toString();              if (str != "") {                  mSpeech.speak(str, TextToSpeech.QUEUE_ADD, null)              }          }            btn2 = findViewById(R.id.btn2)          btn2.setOnClickListener { view ->              var intent = Intent("com.android.settings.TTS_SETTINGS")              startActivity(intent)          }      }          fun requestPermission() {          val REQUEST_CODE = 1          if (checkSelfPermission(                  this,                  WRITE_EXTERNAL_STORAGE              ) != PackageManager.PERMISSION_GRANTED          ) {              ActivityCompat.requestPermissions(                  this, arrayOf(                      WRITE_EXTERNAL_STORAGE                  ),                  REQUEST_CODE              )          }        }  }

注意点

微卡智享

基本上核心代码都已经完成了,再说几个要注意的点:

  • android6.0后读取本地文件要动态加载权限,这个mainactivity中有
  • android9.0后安装程序也要加入权限<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

源码地址

https://github.com/Vaccae/AndroidTTS.git