本文长度为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
)
}
}
}
注意点
微卡智享
基本上核心代码都已经完成了,再说几个要注意的点:
源码地址
https://github.com/Vaccae/AndroidTTS.git