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

2019-12-11 15:21:32 浏览数 (1)

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

0 人点赞