Is it possible to merge/install split APK files (AKA “app bundle”), on Android device itself, without root?

后端 未结 8 1268
北恋
北恋 2020-12-09 02:50

Background

In the past, I\'ve asked about sharing or backup of app-bundle / split apk files, here .

This seems like an almost impossible task, which I coul

8条回答
  •  我在风中等你
    2020-12-09 03:32

    No root required implementation Check this git hub link: https://github.com/nkalra0123/splitapkinstall

    We have to create a service and pass that handle in session.commit()

     Intent callbackIntent = new Intent(getApplicationContext(), APKInstallService.class);
     PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 0, callbackIntent, 0);
     session.commit(pendingIntent.getIntentSender());
    

    EDIT: Since the solution works, but not really published here, I've decided to write it before marking it as correct solution. Here's the code:

    manifest

    
      
      
      
      
        
          
            
    
            
          
        
    
        
      
    
    

    APKInstallService

    class APKInstallService : Service() {
        override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
            when (if (intent.hasExtra(PackageInstaller.EXTRA_STATUS)) null else intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)) {
                PackageInstaller.STATUS_PENDING_USER_ACTION -> {
                    Log.d("AppLog", "Requesting user confirmation for installation")
                    val confirmationIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT)
                    confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    try {
                        startActivity(confirmationIntent)
                    } catch (e: Exception) {
                    }
                }
                PackageInstaller.STATUS_SUCCESS -> Log.d("AppLog", "Installation succeed")
                else -> Log.d("AppLog", "Installation failed")
            }
            stopSelf()
            return START_NOT_STICKY
        }
    
        override fun onBind(intent: Intent): IBinder? {
            return null
        }
    }
    

    MainActivity

    class MainActivity : AppCompatActivity() {
        private lateinit var packageInstaller: PackageInstaller
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            val toolbar = findViewById(R.id.toolbar)
            setSupportActionBar(toolbar)
            val fab = findViewById(R.id.fab)
            fab.setOnClickListener {
                packageInstaller = packageManager.packageInstaller
                val ret = installApk("/storage/emulated/0/Download/split/")
                Log.d("AppLog", "onClick: return value is $ret")
            }
    
        }
    
        private fun installApk(apkFolderPath: String): Int {
            val nameSizeMap = HashMap()
            var totalSize: Long = 0
            var sessionId = 0
            val folder = File(apkFolderPath)
            val listOfFiles = folder.listFiles()
            try {
                for (listOfFile in listOfFiles) {
                    if (listOfFile.isFile) {
                        Log.d("AppLog", "installApk: " + listOfFile.name)
                        nameSizeMap[listOfFile.name] = listOfFile.length()
                        totalSize += listOfFile.length()
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
                return -1
            }
            val installParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
            installParams.setSize(totalSize)
            try {
                sessionId = packageInstaller.createSession(installParams)
                Log.d("AppLog","Success: created install session [$sessionId]")
                for ((key, value) in nameSizeMap) {
                    doWriteSession(sessionId, apkFolderPath + key, value, key)
                }
                doCommitSession(sessionId)
                Log.d("AppLog","Success")
            } catch (e: IOException) {
                e.printStackTrace()
            }
            return sessionId
        }
    
        private fun doWriteSession(sessionId: Int, inPath: String?, sizeBytes: Long, splitName: String): Int {
            var inPathToUse = inPath
            var sizeBytesToUse = sizeBytes
            if ("-" == inPathToUse) {
                inPathToUse = null
            } else if (inPathToUse != null) {
                val file = File(inPathToUse)
                if (file.isFile)
                    sizeBytesToUse = file.length()
            }
            var session: PackageInstaller.Session? = null
            var inputStream: InputStream? = null
            var out: OutputStream? = null
            try {
                session = packageInstaller.openSession(sessionId)
                if (inPathToUse != null) {
                    inputStream = FileInputStream(inPathToUse)
                }
                out = session!!.openWrite(splitName, 0, sizeBytesToUse)
                var total = 0
                val buffer = ByteArray(65536)
                var c: Int
                while (true) {
                    c = inputStream!!.read(buffer)
                    if (c == -1)
                        break
                    total += c
                    out!!.write(buffer, 0, c)
                }
                session.fsync(out!!)
                Log.d("AppLog", "Success: streamed $total bytes")
                return PackageInstaller.STATUS_SUCCESS
            } catch (e: IOException) {
                Log.e("AppLog", "Error: failed to write; " + e.message)
                return PackageInstaller.STATUS_FAILURE
            } finally {
                try {
                    out?.close()
                    inputStream?.close()
                    session?.close()
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }
    
        private fun doCommitSession(sessionId: Int) {
            var session: PackageInstaller.Session? = null
            try {
                try {
                    session = packageInstaller.openSession(sessionId)
                    val callbackIntent = Intent(applicationContext, APKInstallService::class.java)
                    val pendingIntent = PendingIntent.getService(applicationContext, 0, callbackIntent, 0)
                    session!!.commit(pendingIntent.intentSender)
                    session.close()
                    Log.d("AppLog", "install request sent")
                    Log.d("AppLog", "doCommitSession: " + packageInstaller.mySessions)
                    Log.d("AppLog", "doCommitSession: after session commit ")
                } catch (e: IOException) {
                    e.printStackTrace()
                }
    
            } finally {
                session!!.close()
            }
        }
    }
    

提交回复
热议问题