Android:以编程方式安装.apk [重复]

Posted

技术标签:

【中文标题】Android:以编程方式安装.apk [重复]【英文标题】:Android: install .apk programmatically [duplicate] 【发布时间】:2011-06-25 10:58:40 【问题描述】:

我在以下人员的帮助下完成了这个 android download binary file problems 和Install Application programmatically on Android。

我想同时进行自动更新和自动安装。它是本地的,所以它是非市场应用。

这是我的代码:

public void Update(String apkurl)
    try 
        URL url = new URL(apkurl);
        HttpURLConnection c = (HttpURLConnection) url.openConnection();
        c.setRequestMethod("GET");
        c.setDoOutput(true);
        c.connect();

        String PATH = Environment.getExternalStorageDirectory() + "/download/";
        File file = new File(PATH);
        file.mkdirs();
        File outputFile = new File(file, "app.apk");
        FileOutputStream fos = new FileOutputStream(outputFile);

        InputStream is = c.getInputStream();

        byte[] buffer = new byte[1024];
        int len1 = 0;
        while ((len1 = is.read(buffer)) != -1) 
            fos.write(buffer, 0, len1);
        
        fos.close();
        is.close();//till here, it works fine - .apk is download to my sdcard in download file

        Intent promptInstall = new Intent(Intent.ACTION_VIEW)
            .setData(Uri.parse(PATH+"app.apk"))
            .setType("application/android.com.app");
        startActivity(promptInstall);//installation is not working

     catch (IOException e) 
        Toast.makeText(getApplicationContext(), "Update error!", Toast.LENGTH_LONG).show();
    
  

我的权限是 INTERNETWRITE_EXTERNAL_STORAGEINSTALL_PACKAGESDELETE_PACKAGES

当 Intent promptInstall 加载时,应用程序崩溃 =/

那么,我是缺少权限还是我的代码不正确,还是有更好的方法来做到这一点?

【问题讨论】:

它甚至没有抓住。它在捕获之前崩溃。在调试日志中显示 ActivityThread.performLaunchActivity(ActivityThread$ActivityRecord, Intent) line: 2496 您应该删除 INSTALL_PACKAGES 和 DELETE_PACKAGES 权限,因为它们实际上不会授予您的应用程序,因此与它的工作方式无关,但是当它们被拒绝时可能会在日志中产生令人困惑的警告。 我假设安装后,apk 仍然在那个下载目录中。如何检测到安装成功并删除 apk 以免浪费空间? 我在我的应用程序中使用了这种方法,更改如下。它在 2.3.x 设备和我测试过的 3.2 设备上运行良好。但是,在 4.x 设备上,我得到“java.io.FileNotFoundException: ”就行了:InputStream is = c.getInputStream();。我拥有 INTERNET 和 WRITE_EXTERNAL_STORAGE 权限。我错过了什么? 只有当我删除 c.setDoOutput(true); 时它才对我有用。见***.com/questions/12496789/… 【参考方案1】:

我解决了这个问题。我在setData(Uri)setType(String) 中犯了错误。

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk")), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

现在是正确的,我的自动更新正在工作。感谢帮助。 =)

编辑 20.7.2016:

过了很久,我不得不在另一个项目中再次使用这种更新方式。我遇到了一些旧解决方案的问题。那段时间很多事情都发生了变化,所以我不得不用不同的方法来做这件事。代码如下:

    //get destination to update file and set Uri
    //TODO: First I wanted to store my update .apk file on internal storage for my app but apparently android does not allow you to open and install
    //aplication with existing package from there. So for me, alternative solution is Download directory in external storage. If there is better
    //solution, please inform us in comment
    String destination = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/";
    String fileName = "AppName.apk";
    destination += fileName;
    final Uri uri = Uri.parse("file://" + destination);

    //Delete update file if exists
    File file = new File(destination);
    if (file.exists())
    //file.delete() - test this, I think sometimes it doesnt work
        file.delete();

    //get url of app on server
    String url = Main.this.getString(R.string.update_app_url);

    //set downloadmanager
    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
    request.setDescription(Main.this.getString(R.string.notification_description));
    request.setTitle(Main.this.getString(R.string.app_name));

    //set destination
    request.setDestinationUri(uri);

    // get download service and enqueue file
    final DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
    final long downloadId = manager.enqueue(request);

    //set BroadcastReceiver to install app when .apk is downloaded
    BroadcastReceiver onComplete = new BroadcastReceiver() 
        public void onReceive(Context ctxt, Intent intent) 
            Intent install = new Intent(Intent.ACTION_VIEW);
            install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            install.setDataAndType(uri,
                    manager.getMimeTypeForDownloadedFile(downloadId));
            startActivity(install);

            unregisterReceiver(this);
            finish();
        
    ;
    //register receiver for when .apk download is compete
    registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

【讨论】:

有什么办法可以避免向用户提示?我正在尝试以自动方式更新应用程序作为测试套件的一部分,并且需要避免依赖用户来接受下载。 如果没有外部存储怎么下载内部存储&下载后会删除apk文件? @TomBennett 不,出于安全原因,无法避免提示。只有当您的应用使用与 ROM 相同的签名 - ***.com/a/15660063/832776 时,才能安装包 下载对我有用,但开始安装的意图却没有。我使用了这个答案,然后使用了以下来触发安装***.com/a/40131196/2276198 下载工作正常,但我收到一个错误,提示没有找到处理意图的活动。将 mime 类型硬编码为“application/vnd.android.package-archive”为我解决了这个问题。【参考方案2】:

对于 ICS,我已经实现了您的代码并创建了一个扩展 AsyncTask 的类。我希望你能欣赏它!感谢您的代码和解决方案。

public class UpdateApp extends AsyncTask<String,Void,Void>
    private Context context;
    public void setContext(Context contextf)
        context = contextf;
    

    @Override
    protected Void doInBackground(String... arg0) 
        try 
            URL url = new URL(arg0[0]);
            HttpURLConnection c = (HttpURLConnection) url.openConnection();
            c.setRequestMethod("GET");
            c.setDoOutput(true);
            c.connect();

            String PATH = "/mnt/sdcard/Download/";
            File file = new File(PATH);
            file.mkdirs();
            File outputFile = new File(file, "update.apk");
            if(outputFile.exists())
                outputFile.delete();
            
            FileOutputStream fos = new FileOutputStream(outputFile);

            InputStream is = c.getInputStream();

            byte[] buffer = new byte[1024];
            int len1 = 0;
            while ((len1 = is.read(buffer)) != -1) 
                fos.write(buffer, 0, len1);
            
            fos.close();
            is.close();

            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setDataAndType(Uri.fromFile(new File("/mnt/sdcard/Download/update.apk")), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
            context.startActivity(intent);


         catch (Exception e) 
            Log.e("UpdateAPP", "Update error! " + e.getMessage());
        
        return null;
    
   

要使用它,在你的主要活动调用中通过这种方式:

atualizaApp = new UpdateApp();
atualizaApp.setContext(getApplicationContext());
atualizaApp.execute("http://serverurl/appfile.apk");

【讨论】:

下载的 .apk 文件没问题,但是从代码安装时出现此错误,解析包时出现问题。但是当我从模拟器中提取 .apk 并手动安装时,一切正常。你能告诉我是什么问题吗? @Big.Child 您是否将 apk 下载到可公开访问的文件夹中?我最初将它下载到我的应用程序的文件目录中,但这给了我解析错误。我现在将它下载到Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),这对我有用。 看在上帝的份上,请使用 getExternalCacheDir().getAbsolutePath()。当用户卸载应用程序时,保存到下载文件夹只会在设备上留下垃圾。相信我,没有人会故意在网络上下载 apk 将 apk 保留在下载文件夹中。 谢谢你的回答。真的帮了我。但我面对的是java.io.FileNotFoundException。问题出在这一行:urlConnection.setDoOutput(true);。显然,JAVA 中的这一行强制 http 协议将 GET 更改为 POST,而不管指定 GET。 嗨!很抱歉长时间延迟回答。我认为出于安全原因,上次更新可能会弃用某些功能。不幸的是,我正在使用 ionic 来开发移动应用程序,所以我不知道为什么上面的脚本不再起作用了。【参考方案3】:
/*  
 *  Code Prepared by **Muhammad Mubashir**.
 *  Analyst Software Engineer.
    Email Id : muhammad.mubashir.bscs@gmail.com
    Skype Id : muhammad.mubashir.ansari
    Code: **August, 2011.**

    Description: **Get Updates(means New .Apk File) from IIS Server and Download it on Device SD Card,
                 and Uninstall Previous (means OLD .apk) and Install New One.
                 and also get Installed App Version Code & Version Name.**

    All Rights Reserved.
*/
package com.SelfInstall01;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import com.SelfInstall01.SelfInstall01Activity;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class SelfInstall01Activity extends Activity 

    class PInfo 
        private String appname = "";
        private String pname = "";
        private String versionName = "";
        private int versionCode = 0;
        //private Drawable icon;
        /*private void prettyPrint() 
            //Log.v(appname + "\t" + pname + "\t" + versionName + "\t" + versionCode);
        */
    
    public int VersionCode;
    public String VersionName="";
    public String ApkName ;
    public String AppName ;
    public String BuildVersionPath="";
    public String urlpath ;
    public String PackageName;
    public String InstallAppPackageName;
    public String Text="";

    TextView tvApkStatus;
    Button btnCheckUpdates;
    TextView tvInstallVersion;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //Text= "Old".toString();
        Text= "New".toString();


        ApkName = "SelfInstall01.apk";//"Test1.apk";// //"DownLoadOnSDcard_01.apk"; //      
        AppName = "SelfInstall01";//"Test1"; //

        BuildVersionPath = "http://10.0.2.2:82/Version.txt".toString();
        PackageName = "package:com.SelfInstall01".toString(); //"package:com.Test1".toString();
        urlpath = "http://10.0.2.2:82/"+ Text.toString()+"_Apk/" + ApkName.toString();

        tvApkStatus =(TextView)findViewById(R.id.tvApkStatus);
        tvApkStatus.setText(Text+" Apk Download.".toString());


        tvInstallVersion = (TextView)findViewById(R.id.tvInstallVersion);
        String temp = getInstallPackageVersionInfo(AppName.toString());
        tvInstallVersion.setText("" +temp.toString());

        btnCheckUpdates =(Button)findViewById(R.id.btnCheckUpdates);
        btnCheckUpdates.setOnClickListener(new OnClickListener() 
               
            @Override
            public void onClick(View arg0) 
            
                GetVersionFromServer(BuildVersionPath); 

                if(checkInstalledApp(AppName.toString()) == true)
                   
                    Toast.makeText(getApplicationContext(), "Application Found " + AppName.toString(), Toast.LENGTH_SHORT).show();


                else
                    Toast.makeText(getApplicationContext(), "Application Not Found. "+ AppName.toString(), Toast.LENGTH_SHORT).show();          
                               
            
        );

    // On Create END.

    private Boolean checkInstalledApp(String appName)
        return getPackages(appName);    
    

    // Get Information about Only Specific application which is Install on Device.
    public String getInstallPackageVersionInfo(String appName) 
    
        String InstallVersion = "";     
        ArrayList<PInfo> apps = getInstalledApps(false); /* false = no system packages */
        final int max = apps.size();
        for (int i=0; i<max; i++) 
        
            //apps.get(i).prettyPrint();        
            if(apps.get(i).appname.toString().equals(appName.toString()))
            
                InstallVersion = "Install Version Code: "+ apps.get(i).versionCode+
                    " Version Name: "+ apps.get(i).versionName.toString();
                break;
            
        

        return InstallVersion.toString();
    
    private Boolean getPackages(String appName) 
    
        Boolean isInstalled = false;
        ArrayList<PInfo> apps = getInstalledApps(false); /* false = no system packages */
        final int max = apps.size();
        for (int i=0; i<max; i++) 
        
            //apps.get(i).prettyPrint();

            if(apps.get(i).appname.toString().equals(appName.toString()))
            
                /*if(apps.get(i).versionName.toString().contains(VersionName.toString()) == true &&
                        VersionCode == apps.get(i).versionCode)
                
                    isInstalled = true;
                    Toast.makeText(getApplicationContext(),
                            "Code Match", Toast.LENGTH_SHORT).show(); 
                    openMyDialog();
                */
                if(VersionCode <= apps.get(i).versionCode)
                
                    isInstalled = true;

                    /*Toast.makeText(getApplicationContext(),
                            "Install Code is Less.!", Toast.LENGTH_SHORT).show();*/

                    DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() 
                    
                        @Override
                        public void onClick(DialogInterface dialog, int which) 
                            switch (which)
                            
                            case DialogInterface.BUTTON_POSITIVE:
                                //Yes button clicked
                                //SelfInstall01Activity.this.finish(); Close The App.

                                DownloadOnSDcard();
                                InstallApplication();
                                UnInstallApplication(PackageName.toString());

                                break;

                            case DialogInterface.BUTTON_NEGATIVE:
                                //No button clicked

                                break;
                            
                        
                    ;

                    AlertDialog.Builder builder = new AlertDialog.Builder(this);
                    builder.setMessage("New Apk Available..").setPositiveButton("Yes Proceed", dialogClickListener)
                        .setNegativeButton("No.", dialogClickListener).show();

                    
                if(VersionCode > apps.get(i).versionCode)
                
                    isInstalled = true;
                    /*Toast.makeText(getApplicationContext(),
                            "Install Code is better.!", Toast.LENGTH_SHORT).show();*/

                    DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() 
                    
                        @Override
                        public void onClick(DialogInterface dialog, int which) 
                            switch (which)
                            
                            case DialogInterface.BUTTON_POSITIVE:
                                //Yes button clicked
                                //SelfInstall01Activity.this.finish(); Close The App.

                                DownloadOnSDcard();
                                InstallApplication();
                                UnInstallApplication(PackageName.toString());

                                break;

                            case DialogInterface.BUTTON_NEGATIVE:
                                //No button clicked

                                break;
                            
                        
                    ;

                    AlertDialog.Builder builder = new AlertDialog.Builder(this);
                    builder.setMessage("NO need to Install.").setPositiveButton("Install Forcely", dialogClickListener)
                        .setNegativeButton("Cancel.", dialogClickListener).show();              
                
            
        

        return isInstalled;
    
    private ArrayList<PInfo> getInstalledApps(boolean getSysPackages) 
           
        ArrayList<PInfo> res = new ArrayList<PInfo>();        
        List<PackageInfo> packs = getPackageManager().getInstalledPackages(0);

        for(int i=0;i<packs.size();i++) 
        
            PackageInfo p = packs.get(i);
            if ((!getSysPackages) && (p.versionName == null)) 
                continue ;
            
            PInfo newInfo = new PInfo();
            newInfo.appname = p.applicationInfo.loadLabel(getPackageManager()).toString();
            newInfo.pname = p.packageName;
            newInfo.versionName = p.versionName;
            newInfo.versionCode = p.versionCode;
            //newInfo.icon = p.applicationInfo.loadIcon(getPackageManager());
            res.add(newInfo);
        
        return res; 
    


    public void UnInstallApplication(String packageName)// Specific package Name Uninstall.
    
        //Uri packageURI = Uri.parse("package:com.CheckInstallApp");
        Uri packageURI = Uri.parse(packageName.toString());
        Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
        startActivity(uninstallIntent); 
    
    public void InstallApplication()
       
        Uri packageURI = Uri.parse(PackageName.toString());
        Intent intent = new Intent(android.content.Intent.ACTION_VIEW, packageURI);

//      Intent intent = new Intent(android.content.Intent.ACTION_VIEW);

        //intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        //intent.setFlags(Intent.ACTION_PACKAGE_REPLACED);

        //intent.setAction(Settings. ACTION_APPLICATION_SETTINGS);

        intent.setDataAndType
        (Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/"  + ApkName.toString())), 
        "application/vnd.android.package-archive");

        // Not open this Below Line Because...
        ////intent.setClass(this, Project02Activity.class); // This Line Call Activity Recursively its dangerous.

        startActivity(intent);  
    
    public void GetVersionFromServer(String BuildVersionPath)
    
        //this is the file you want to download from the remote server          
        //path ="http://10.0.2.2:82/Version.txt";
        //this is the name of the local file you will create
        // version.txt contain Version Code = 2; \n Version name = 2.1;             
        URL u;
        try 
            u = new URL(BuildVersionPath.toString());

            HttpURLConnection c = (HttpURLConnection) u.openConnection();           
            c.setRequestMethod("GET");
            c.setDoOutput(true);
            c.connect();

            //Toast.makeText(getApplicationContext(), "HttpURLConnection Complete.!", Toast.LENGTH_SHORT).show();  

            InputStream in = c.getInputStream();

            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            byte[] buffer = new byte[1024]; //that stops the reading after 1024 chars..
            //in.read(buffer); //  Read from Buffer.
            //baos.write(buffer); // Write Into Buffer.

            int len1 = 0;
            while ( (len1 = in.read(buffer)) != -1 ) 
                           
                baos.write(buffer,0, len1); // Write Into ByteArrayOutputStream Buffer.
            

            String temp = "";     
            String s = baos.toString();// baos.toString(); contain Version Code = 2; \n Version name = 2.1;

            for (int i = 0; i < s.length(); i++)
                           
                i = s.indexOf("=") + 1; 
                while (s.charAt(i) == ' ') // Skip Spaces
                
                    i++; // Move to Next.
                
                while (s.charAt(i) != ';'&& (s.charAt(i) >= '0' && s.charAt(i) <= '9' || s.charAt(i) == '.'))
                
                    temp = temp.toString().concat(Character.toString(s.charAt(i))) ;
                    i++;
                
                //
                s = s.substring(i); // Move to Next to Process.!
                temp = temp + " "; // Separate w.r.t Space Version Code and Version Name.
            
            String[] fields = temp.split(" ");// Make Array for Version Code and Version Name.

            VersionCode = Integer.parseInt(fields[0].toString());// .ToString() Return String Value.
            VersionName = fields[1].toString();

            baos.close();
        
        catch (MalformedURLException e) 
            Toast.makeText(getApplicationContext(), "Error." + e.getMessage(), Toast.LENGTH_SHORT).show();
            e.printStackTrace();
         catch (IOException e)            
            e.printStackTrace();
            Toast.makeText(getApplicationContext(), "Error." + e.getMessage(), Toast.LENGTH_SHORT).show();
        
            //return true;
    // Method End.

    // Download On My Mobile SDCard or Emulator.
    public void DownloadOnSDcard()
    
        try
            URL url = new URL(urlpath.toString()); // Your given URL.

            HttpURLConnection c = (HttpURLConnection) url.openConnection();
            c.setRequestMethod("GET");
            c.setDoOutput(true);
            c.connect(); // Connection Complete here.!

            //Toast.makeText(getApplicationContext(), "HttpURLConnection complete.", Toast.LENGTH_SHORT).show();

            String PATH = Environment.getExternalStorageDirectory() + "/download/";
            File file = new File(PATH); // PATH = /mnt/sdcard/download/
            if (!file.exists()) 
                file.mkdirs();
            
            File outputFile = new File(file, ApkName.toString());           
            FileOutputStream fos = new FileOutputStream(outputFile);

            //      Toast.makeText(getApplicationContext(), "SD Card Path: " + outputFile.toString(), Toast.LENGTH_SHORT).show();

            InputStream is = c.getInputStream(); // Get from Server and Catch In Input Stream Object.

            byte[] buffer = new byte[1024];
            int len1 = 0;
            while ((len1 = is.read(buffer)) != -1) 
                fos.write(buffer, 0, len1); // Write In FileOutputStream.
            
            fos.close();
            is.close();//till here, it works fine - .apk is download to my sdcard in download file.
            // So please Check in DDMS tab and Select your Emulator.

            //Toast.makeText(getApplicationContext(), "Download Complete on SD Card.!", Toast.LENGTH_SHORT).show();
            //download the APK to sdcard then fire the Intent.
         
        catch (IOException e) 
        
            Toast.makeText(getApplicationContext(), "Error! " +
                    e.toString(), Toast.LENGTH_LONG).show();
                   
    

【讨论】:

我认为要点是:intent.setDataAndType (Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/" + ApkName.toString())), "application/vnd. android.package-archive");开始活动(意图); 如何使用此示例从 WebService 下载 apk 点击按钮时出现很多错误。 是的,错误太多了,请您修复并分享新代码 这段代码如果更短更简洁会更有帮助。一个好的起点是删除所有被注释掉的代码。【参考方案4】:

感谢您分享此内容。我已经实施并工作了。然而:

1) 我安装了我的应用程序的第 1 版(工作没问题) 2)我将版本 2 放在服务器上。应用程序检索 ver2 并保存到 SD 卡并提示用户安装新包 ver2 3) ver2 安装并按预期工作 4) 问题是,每次应用启动时,它都希望用户重新安装第 2 版。

所以我认为解决方案只是删除 sdcard 上的 APK,但他们的异步任务将简单地再次为服务器检索 ver2。

因此,阻止再次尝试安装 v2 apk 的唯一方法是从 sdcard 和远程服务器中删除。

正如你所想象的那样,这实际上是行不通的,因为我永远不会知道所有用户何时都收到了最新版本。

非常感谢任何解决此问题的帮助。

我实现了上面列出的“ldmuniz”方法。

新编辑: 只是想我所有的 APK 的名称都一样。我是否应该命名 myapk_v1.0xx.apk 并在该版本中主动设置远程路径以在 v.2.0 发布时查找它?

我测试了这个理论,它确实解决了这个问题。 您需要为您的 APK 文件文件命名某种版本控制,记住始终在您当前发布的应用程序中设置您的 NEXT 发布版本#。不理想但实用。

【讨论】:

在开始更新过程之前,请与您的服务器确认您是否有新的更新待处理。如果服务器返回 Success,则开始更新过程(下载、保存和安装新更新),否则不做任何事情。也就是说,如果(有任何新的更新可用)“atualizaApp.execute("serverurl/appfile.apk");” else //do nothing 对于以后来这里的人:检查保存的apk的版本,从服务器检查版本并检查安装的版本,如果一切都一样,不用担心。仅当 server_version > 保存在 sd 卡中时才从服务器下载,并且仅在 sd_card > 已安装版本时安装,只是为了帮助

以上是关于Android:以编程方式安装.apk [重复]的主要内容,如果未能解决你的问题,请参考以下文章

在 android 上以编程方式安装 APK

Android:以编程方式将 apk 复制到 /system/app

以编程方式安装 .apk 的回调

如何在 Android 10 中以编程方式安装任何 Android 应用

如何在 Android 中以编程方式反编译 dex 文件?

android:安装APK时的应用名称[重复]