前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >学习|Android检测并自动下载安装包(Kotlin)

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

作者头像
Vaccae
发布2019-12-11 15:21:32
1.4K0
发布2019-12-11 15:21:32
举报
文章被收录于专栏:微卡智享

本文长度为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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微卡智享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
语音合成
语音合成(Text To Speech,TTS)满足将文本转化成拟人化语音的需求,打通人机交互闭环。提供多场景、多语言的音色选择,支持 SSML 标记语言,支持自定义音量、语速等参数,让发音更专业、更符合场景需求。语音合成广泛适用于智能客服、有声阅读、新闻播报、人机交互等业务场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档