本文长度为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 {}写入才可以
代码语言:javascript复制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,一个进度条,和一个按钮,如下
代码语言:javascript复制<?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事件
代码语言:javascript复制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中也要加入
代码语言:javascript复制 <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的类,然后写了一个静态函数用于检测想要的安装包是否已经安装
代码语言:javascript复制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中调用
代码语言:javascript复制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