Giter Site home page Giter Site logo

installapk's Introduction

InstallApk

简介

关于 Apk 普通安装和静默安装的总结。适配 Android 6.0 、Android 7.0 和 Android 9.0。

测试环境

Android 7.1 、Android 9.0

应用场景

对于某些定制的系统而言,是需要做到静默安装某些业务 App 的。比如有两个 App,一个是业务 App A,一个是专门负责安装服务的 App B。当 A 收到后台的升级推送时,会将新版本的安装包下载到一个指定的目录,然后给 B 发送一条广播,让 B 安装 刚刚下载好的 A 的最新版本,并强制拉起 A 应用。这个过程是完全静默的,不需要人工干预。这类场景的升级可以应用在高铁站、机场等公共场合的智能终端等场景,因为这些地方的业务升级一般都会自动完成。

Apk 安装的几种实现方式

一般来说,有几种方式:

  1. 标准的 Intent
  2. 把 apk 地址托管给浏览器,浏览器下载安装
  3. pm install(需要 su 权限)
  4. 使用 PackageManager 进行安装(需要是系统级别的应用,或系统签名)
  5. 把 apk 地址托管给 DownloadManager 下载处理(类似2)

非静默安装 -- 标准的 Intent 安装 Apk

使用 Intent 安装 Apk 没什么好说的,只需要注意一点,就是对 Android 7.0 的兼容处理。这里强调下 Android 7.0 的处理方式。在 Android 7.0 下面,使用 FileProvider 共享文件,步骤如下(参考):

1. 指定 FileProvider (默认写法,不用修改)
  <!-- Android 7.0 文件访问的兼容处理 -->
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_path" />
        </provider>
2. 在 res/xml/ 下面创建 xml 文件 provider_path.xml

以下路径已经包含所有的路径,可以按需保留或者修改。

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path name="files-path" path="/."/>
    <cache-path name="cache-path" path="/."/>
    <external-path name="external-path" path="/."/>
    <external-files-path name="external-files-path" path="/."/>
    <external-cache-path name="external-cache-path" path="/."/>
</paths>
3. 启动代码做兼容处理
  /**
     * 描述: 安装
     */
    fun install(apkPath: String, context: Context): Boolean {
        // 先判断手机是否有root权限
        if (hasRootPermission()) {
            // 有root权限,利用静默安装实现
            return silentInstall(apkPath)
        } else {
            // 没有root权限,利用意图进行安装
            val file = File(apkPath)
            if (!file.exists()) {
                return false
            }
            val intent = Intent(Intent.ACTION_VIEW)
            val uri: Uri
            val type = "application/vnd.android.package-archive"
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                uri = Uri.fromFile(file)
            } else {
                val authority = context.packageName + ".FileProvider"
                uri = FileProvider.getUriForFile(context, authority, file)
                intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
            }
            context.grantUriPermission(
                context.packageName,
                uri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION
            )
            intent.setDataAndType(uri, type)
            //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            context.startActivity(intent)
            return true
        }
    }

非静默安装 -- 使用浏览器下载并安装 Apk

很简单,直接上代码.或者参看例程 filedownload

    /**
     * 使用这种方法下载完全把工作交给了系统应用,自己的应用中不需要申请任何权限,方便简单快捷。但如此我们也不能知道
     * 下载文件的大小,不能监听下载进度和下载结果。
     *
     * @param context 上下文
     * @param url     下载 url
     */
    fun downloadFileByBrowser(context: Context, url: String) {
        val intent = Intent()
        intent.action = Intent.ACTION_VIEW
        intent.addCategory(Intent.CATEGORY_BROWSABLE)
        intent.data = Uri.parse(url)
        context.startActivity(intent)
    }

把 apk 地址托管给 DownloadManager 下载处理(类似使用浏览器下载并安装 Apk)

这个也比较简单,直接看代码。参考例程 filedownload

1. 定义下载器工具类
package com.xzy.installapk


import android.app.DownloadManager
import android.content.Context
import android.content.IntentFilter
import android.net.Uri
import android.os.Environment
import android.util.Log

import java.io.File
import java.util.Objects

/**
 * 调用系统下载器实现下载功能。
 *
 * @author xzy
 */
class SystemDownloadManager {
    private var callback: Callback? = null

    fun setCallback(callback: Callback) {
        this.callback = callback
    }

    fun downloadFileBySysDownloadManager(
        context: Context,
        url: String,
        fileName: String,
        mimeType: String
    ) {
        val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
        val request = DownloadManager.Request(Uri.parse(url))
        // 通知栏的下载通知
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
        request.setTitle(fileName)
        request.setMimeType(mimeType)
        // 保存到DIRECTORY_DOWNLOADS目录,文件名为 fileName
        val file = File(Environment.DIRECTORY_DOWNLOADS, fileName)
        if (file.exists()) {
            val result = file.delete()
            Log.d(TAG, "file.delete():$result")
        }
        request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, fileName)
        val downloadId = Objects.requireNonNull(downloadManager).enqueue(request)
        Log.d(TAG, "downloadId:$downloadId")
        //文件下载完成会发送完成广播,可注册广播进行监听
        val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
        intentFilter.addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED)
        intentFilter.addAction(DownloadManager.ACTION_VIEW_DOWNLOADS)
        val mDownloadBroadcast = DownloadBroadcast(file, mimeType)
        context.registerReceiver(mDownloadBroadcast, intentFilter)
        if (callback != null) {
            callback!!.callback(mDownloadBroadcast)
        }
    }

    interface Callback {
        fun callback(downloadBroadcast: DownloadBroadcast)
    }

    companion object {
        private val TAG = "SystemDownloadManager"

        val instance = SystemDownloadManager()
    }
}
2 . 需要定义一个广播接收器,用于接收数据(依然兼容 Android 7.0 )
package com.xzy.installapk

import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.util.Log

import androidx.core.content.FileProvider

import java.io.File

/**
 * 调用系统下载器对应的回调广播
 * @author xzy
 */
class DownloadBroadcast(private val mFile: File, private val mMimeType: String) :
    BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        val action = intent.action
        if (DownloadManager.ACTION_DOWNLOAD_COMPLETE == action) {
            val intent1 = Intent(Intent.ACTION_VIEW)
            intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
                intent1.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                val uri1 = FileProvider.getUriForFile(
                    context,
                    BuildConfig.APPLICATION_ID + ".fileProvider",
                    mFile
                )
                intent1.setDataAndType(uri1, mMimeType)
            } else {
                intent1.setDataAndType(Uri.fromFile(mFile), mMimeType)
            }
            Log.d("mFile:", mFile.absolutePath)
            try {
                context.startActivity(intent1)
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }
    }
}

静默安装 -- pm install(需要 su 权限) ,利用 Runtime.getRuntime().exec()

本例程中主要是以 pm install 命令来执行静默安装的。具体请看 silentInstall 方法。(需要 Root 权限)

 /**
     * 静默安装
     * @param apkPath
     * @return
     */
    private fun silentInstall(apkPath: String): Boolean {
        var result = false
        var dataOutputStream: DataOutputStream? = null
        var errorStream: BufferedReader? = null
        try {
            // 申请su权限
            val process = Runtime.getRuntime().exec("su")
            dataOutputStream = DataOutputStream(process.outputStream)
            // 执行pm install命令
            val command = "pm install -r $apkPath\n"
            dataOutputStream.write(command.toByteArray(Charset.forName("utf-8")))
            dataOutputStream.writeBytes("exit\n")
            dataOutputStream.flush()
            process.waitFor()
            errorStream = BufferedReader(InputStreamReader(process.errorStream))
            val msg = StringBuilder()
            var line: String?
            // 读取命令的执行结果
            do {
                line = errorStream.readLine()
                if (line != null) {
                    msg.append(line)
                } else {
                    break
                }
            } while (true)
            Log.d("TAG", "install msg is $msg")
            // 如果执行结果中包含 Failure 或者 denied 字样就认为是安装失败,否则就认为安装成功
            if (!msg.toString().contains("Failure") && !msg.toString().contains("denied")) {
                result = true
            }
        } catch (e: Exception) {
            Log.e("TAG", e.message, e)
        } finally {
            try {
                dataOutputStream?.close()
                errorStream?.close()
            } catch (e: IOException) {
                Log.e("TAG", e.message, e)
            }

        }
        Log.d("TAG", "install result is: $result")
        return result
    }

静默安装 --使用 PackageManager 进行安装(需要是系统级别的应用,或系统签名)

参考这个项目 https://github.com/hgncxzy/SysInstaller

参考

  1. Android 实现静默安装的几种方式
  2. Android P使用pm install安装apk报错
  3. Android 开发实现静默安装(需要 Root 权限)
  4. Android 安装应用

联系

  1. ID : hgncxzy
  2. 邮箱:[email protected]
  3. 项目地址:https://github.com/hgncxzy/InstallApk

installapk's People

Contributors

hgncxzy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

installapk's Issues

加急!静默安装效果很棒,如果可以实现静默安装后自启动就完美了

设备已root,使用静默安装效果很棒;

目前需要实现静默安装后自启动应用

os: android 8.0

失败案例:目前已经尝试通过静态注册广播的形式来自启动【静态注册广播Android 8.0 + 无法收到广播】
失败案例:目前已经尝试通过动态注册广播的形式来自启动【动态注册广播的生命周期跟随app;因此在app更新后,动态注册收不到广播消息】

有什么办法可以实现在应用更新后自启动当前应用吗?【设置已root,系统Android 8.0+】

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.