学习|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