【问题标题】:Install Application programmatically on Android在 Android 上以编程方式安装应用程序
【发布时间】:2011-06-04 00:07:07
【问题描述】:

可以从自定义 Android 应用程序以编程方式安装动态下载的 apk。

【问题讨论】:

  • 我不知道“动态加载器,依赖于当前用户环境”是什么意思。 @Lie Ryan 提供的答案显示了如何安装通过您选择的任何方式下载的 APK。
  • 你能看到这个答案对我有帮助吗:stackoverflow.com/a/69689612/12536231

标签: android apk


【解决方案1】:

您可以轻松启动市场链接或安装提示:

Intent promptInstall = new Intent(Intent.ACTION_VIEW)
    .setDataAndType(Uri.parse("file:///path/to/your.apk"), 
                    "application/vnd.android.package-archive");
startActivity(promptInstall); 

source

Intent goToMarket = new Intent(Intent.ACTION_VIEW)
    .setData(Uri.parse("market://details?id=com.package.name"));
startActivity(goToMarket);

source

但是,没有用户的明确许可,您不能安装 .apks;除非设备和您的程序已root。

【讨论】:

  • 好答案,但不要硬编码/sdcard,因为这在 Android 2.2+ 和其他设备上是错误的。请改用Environment.getExternalStorageDirectory()
  • /asset/ 目录只存在于您的开发机器中,当应用程序编译为 APK 时,/asset/ 目录不再存在,因为所有资产都压缩在 APK 中。如果您希望从 /asset/ 目录安装,您需要先将其解压到另一个文件夹中。
  • @LieRyan。很高兴看到你的回答。我有带有自定义 ROM 的根设备。我想动态安装主题而不要求用户按下安装按钮。我可以这样做吗?
  • 当 targetSdk 为 25 时,这似乎不再起作用。它给出了一个异常:“android.os.FileUriExposedException: ...apk 通过 Intent.getData() 暴露在应用之外 ...” .怎么会?
  • @android 开发者:目前我无法对此进行测试,但在 targetSdkVersion >= 24 上,以下适用:stackoverflow.com/questions/38200282/… 所以你必须使用 FileProvider。
【解决方案2】:
File file = new File(dir, "App.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);

我遇到了同样的问题,经过几次尝试后,这对我来说很有效。我不知道为什么,但是分别设置数据和类型搞砸了我的意图。

【讨论】:

  • 我不能对这个答案给予足够的支持。出于某种原因,分别设置 Intent 数据和 MIME 类型会导致 API 级别 17 中的 ActivityNotFoundException。
  • 我也刚刚投了赞成票。这张表格是我唯一可以上班的表格。多年以后..这个该死的错误是什么?浪费了几个小时。我正在使用 Eclipse(Helios),顺便说一句。
  • @BrentM.Spell 和其他人:查看文档,您会看到,每当您只设置数据或类型时,另一个自动无效,例如:setData() 会导致类型参数被移除。如果你想为两者都赋值,你必须使用setDataAndType()。这里:developer.android.com/reference/android/content/…
【解决方案3】:

本题提供的解决方案均适用于23岁及以下的targetSdkVersions。但是,对于 Android N,即 API 级别 24 及更高版本,它们不起作用并崩溃,并出现以下异常:

android.os.FileUriExposedException: file:///storage/emulated/0/... exposed beyond app through Intent.getData()

这是因为从 Android 24 开始,用于寻址下载文件的 Uri 已更改。例如,一个名为appName.apk 的安装文件存储在应用程序的主要外部文件系统中,包名称为com.example.test 将是

file:///storage/emulated/0/Android/data/com.example.test/files/appName.apk

API 23 及以下,而类似

content://com.example.test.authorityStr/pathName/Android/data/com.example.test/files/appName.apk

API 24 及以上。

可以在here 找到更多详细信息,我不再赘述。

要回答targetSdkVersion24 及以上的问题,必须遵循以下步骤: 将以下内容添加到 AndroidManifest.xml:

<application
        android:allowBackup="true"
        android:label="@string/app_name">
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.authorityStr"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/paths"/>
        </provider>
</application>

2。将以下paths.xml文件添加到src,main中res上的xml文件夹中:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="pathName"
        path="pathValue"/>
</paths>

pathName 是上面示例性内容 uri 示例中所示的内容,pathValue 是系统上的实际路径。 放一个“。”是个好主意。如果您不想添加任何额外的子目录,则上面的 pathValue (不带引号)。

  1. 编写以下代码以在主外部文件系统上安装名称为 appName.apk 的 apk:

    File directory = context.getExternalFilesDir(null);
    File file = new File(directory, fileName);
    Uri fileUri = Uri.fromFile(file);
    if (Build.VERSION.SDK_INT >= 24) {
        fileUri = FileProvider.getUriForFile(context, context.getPackageName(),
                file);
    }
    Intent intent = new Intent(Intent.ACTION_VIEW, fileUri);
    intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
    intent.setDataAndType(fileUri, "application/vnd.android" + ".package-archive");
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    context.startActivity(intent);
    activity.finish();
    

在外部文件系统上写入您自己的应用程序的私有目录时也不需要权限。

我已经编写了一个自动更新库here,我在其中使用了上述内容。

【讨论】:

  • 我按照这个方法。但是,当我按下安装按钮时,它说文件已损坏。我可以通过蓝牙传输来安装相同的文件。为什么会这样?
  • HI 当您收到文件损坏错误时,您给应用程序带来 APK 的传输方式是什么,如果您是从服务器下载,请检查从复制文件中刷新流服务器?由于安装通过蓝牙工作传输的 APK 意味着这就是我猜的问题。
  • java.lang.NullPointerException: 尝试调用虚拟方法 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String )' 在空对象引用上
  • 您的图书馆终于可以拥有简单的自述文件,其中包含有关使用的简单说明... ;)
  • 非常感谢,经过一周的努力,我解决了我的问题! @SridharS,我知道这已经是很久以前的事了,但如果你有兴趣,在第 5 行,你应该在 context.getPackageName() 之后添加 .authorityStr 那么它应该可以工作。
【解决方案4】:

好吧,我挖得更深了,从 Android Source 中找到了 PackageInstaller 应用程序的源代码。

https://github.com/android/platform_packages_apps_packageinstaller

从清单中我发现它需要许可:

    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />

确认后才是真正的安装过程

Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallAppProgress.class);
String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (installerPackageName != null) {
   newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
}
startActivity(newIntent);

【讨论】:

  • android.permission.INSTALL_PACKAGES 仅适用于系统签名的应用程序。所以这无济于事
  • @alkersan 你能解释一下你是如何导入 PackatUtil 的吗?还有你如何把这个库放在应用程序类路径中?
【解决方案5】:

我只想分享一个事实,即我的 apk 文件已保存到我的应用程序“数据”目录中,并且我需要将 apk 文件的权限更改为全球可读,以便以这种方式安装它,否则系统会抛出“解析错误:解析包时出现问题”;所以使用@Horaceman 的解决方案:

File file = new File(dir, "App.apk");
file.setReadable(true, false);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);

【讨论】:

  • 我遇到了同样的解析器错误!不知道怎么解决?我设置了 file.setReadable(true, false) 但它不适合我
【解决方案6】:

这可以帮助别人很多!

第一:

private static final String APP_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/MyAppFolderInStorage/";

private void install() {
    File file = new File(APP_DIR + fileName);

    if (file.exists()) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        String type = "application/vnd.android.package-archive";

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Uri downloadedApk = FileProvider.getUriForFile(getContext(), "ir.greencode", file);
            intent.setDataAndType(downloadedApk, type);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        } else {
            intent.setDataAndType(Uri.fromFile(file), type);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }

        getContext().startActivity(intent);
    } else {
        Toast.makeText(getContext(), "ّFile not found!", Toast.LENGTH_SHORT).show();
    }
}

第二:对于 android 7 及更高版本,您应该在清单中定义一个提供程序,如下所示!

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="ir.greencode"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/paths" />
    </provider>

第三: 在 res/xml 文件夹中定义 path.xml,如下所示! 如果您想将其更改为其他内容,我正在使用此 path 进行内部存储,有几种方法!你可以去这个链接: FileProvider

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="your_folder_name" path="MyAppFolderInStorage/"/>
</paths>

Forth:您应该在清单中添加此权限:

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

允许应用程序请求安装包。面向大于 25 的 API 的应用必须拥有此权限才能使用 Intent.ACTION_INSTALL_PACKAGE。

请确保提供者权限相同!


【讨论】:

  • 谢谢,第四步是所有这些都缺少的。
  • 确实,第四点是绝对必要的。所有其他答案都完全错过了。
  • android.permission.REQUEST_INSTALL_PACKAGES 是否仅适用于系统应用程序或使用系统密钥库或其他任何方式签名的应用程序?
  • @Pavitra 允许应用程序请求安装包。面向大于 25 的 API 的应用必须持有此权限才能使用 Intent.ACTION_INSTALL_PACKAGE。保护等级:签名
  • 在第三步中,external-path pathname 有点模棱两可。如果我的文件下载到storage/emulated/0/Android/data/com.mycompany.myapp/files,那么pathname 的值究竟应该是多少?
【解决方案7】:

别忘了申请权限:

android.Manifest.permission.WRITE_EXTERNAL_STORAGE 
android.Manifest.permission.READ_EXTERNAL_STORAGE

在 AndroidManifest.xml 中添加提供者和权限:

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
...
<application>
    ...
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>
</application>

创建 XML 文件提供程序 res/xml/provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="external"
        path="." />
    <external-files-path
        name="external_files"
        path="." />
    <cache-path
        name="cache"
        path="." />
    <external-cache-path
        name="external_cache"
        path="." />
    <files-path
        name="files"
        path="." />
</paths>

使用下面的示例代码:

   public class InstallManagerApk extends AppCompatActivity {

    static final String NAME_APK_FILE = "some.apk";
    public static final int REQUEST_INSTALL = 0;

     @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // required permission:
        // android.Manifest.permission.WRITE_EXTERNAL_STORAGE 
        // android.Manifest.permission.READ_EXTERNAL_STORAGE

        installApk();

    }

    ...

    /**
     * Install APK File
     */
    private void installApk() {

        try {

            File filePath = Environment.getExternalStorageDirectory();// path to file apk
            File file = new File(filePath, LoadManagerApkFile.NAME_APK_FILE);

            Uri uri = getApkUri( file.getPath() ); // get Uri for  each SDK Android

            Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
            intent.setData( uri );
            intent.setFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK );
            intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
            intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
            intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, getApplicationInfo().packageName);

            if ( getPackageManager().queryIntentActivities(intent, 0 ) != null ) {// checked on start Activity

                startActivityForResult(intent, REQUEST_INSTALL);

            } else {
                throw new Exception("don`t start Activity.");
            }

        } catch ( Exception e ) {

            Log.i(TAG + ":InstallApk", "Failed installl APK file", e);
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG)
                .show();

        }

    }

    /**
     * Returns a Uri pointing to the APK to install.
     */
    private Uri getApkUri(String path) {

        // Before N, a MODE_WORLD_READABLE file could be passed via the ACTION_INSTALL_PACKAGE
        // Intent. Since N, MODE_WORLD_READABLE files are forbidden, and a FileProvider is
        // recommended.
        boolean useFileProvider = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;

        String tempFilename = "tmp.apk";
        byte[] buffer = new byte[16384];
        int fileMode = useFileProvider ? Context.MODE_PRIVATE : Context.MODE_WORLD_READABLE;
        try (InputStream is = new FileInputStream(new File(path));
             FileOutputStream fout = openFileOutput(tempFilename, fileMode)) {

            int n;
            while ((n = is.read(buffer)) >= 0) {
                fout.write(buffer, 0, n);
            }

        } catch (IOException e) {
            Log.i(TAG + ":getApkUri", "Failed to write temporary APK file", e);
        }

        if (useFileProvider) {

            File toInstall = new File(this.getFilesDir(), tempFilename);
            return FileProvider.getUriForFile(this,  BuildConfig.APPLICATION_ID, toInstall);

        } else {

            return Uri.fromFile(getFileStreamPath(tempFilename));

        }

    }

    /**
     * Listener event on installation APK file
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if(requestCode == REQUEST_INSTALL) {

            if (resultCode == Activity.RESULT_OK) {
                Toast.makeText(this,"Install succeeded!", Toast.LENGTH_SHORT).show();
            } else if (resultCode == Activity.RESULT_CANCELED) {
                Toast.makeText(this,"Install canceled!", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this,"Install Failed!", Toast.LENGTH_SHORT).show();
            }

        }

    }

    ...

}

【讨论】:

    【解决方案8】:

    在 Android Oreo 及以上版本中,我们必须采用不同的方法来以编程方式安装 apk。

     private void installApkProgramatically() {
    
    
        try {
            File path = activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
    
            File file = new File(path, filename);
    
            Uri uri;
    
            if (file.exists()) {
    
                Intent unKnownSourceIntent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(Uri.parse(String.format("package:%s", activity.getPackageName())));
    
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    
                    if (!activity.getPackageManager().canRequestPackageInstalls()) {
                        startActivityForResult(unKnownSourceIntent, Constant.UNKNOWN_RESOURCE_INTENT_REQUEST_CODE);
                    } else {
                        Uri fileUri = FileProvider.getUriForFile(activity.getBaseContext(), activity.getApplicationContext().getPackageName() + ".provider", file);
                        Intent intent = new Intent(Intent.ACTION_VIEW, fileUri);
                        intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
                        intent.setDataAndType(fileUri, "application/vnd.android" + ".package-archive");
                        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
                        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                        startActivity(intent);
                        alertDialog.dismiss();
                    }
    
                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    
                    Intent intent1 = new Intent(Intent.ACTION_INSTALL_PACKAGE);
                    uri = FileProvider.getUriForFile(activity.getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", file);
                    activity.grantUriPermission("com.abcd.xyz", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    activity.grantUriPermission("com.abcd.xyz", uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                    intent1.setDataAndType(uri,
                            "application/*");
                    intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    intent1.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    intent1.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                    startActivity(intent1);
    
                } else {
                    Intent intent = new Intent(Intent.ACTION_VIEW);
    
                    uri = Uri.fromFile(file);
    
                    intent.setDataAndType(uri,
                            "application/vnd.android.package-archive");
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(intent);
                }
            } else {
    
                Log.i(TAG, " file " + file.getPath() + " does not exist");
            }
        } catch (Exception e) {
    
            Log.i(TAG, "" + e.getMessage());
    
        }
    }
    

    奥利奥及以上版本需要未知资源安装权限。所以在活动结果中你必须检查权限的结果

        @Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
    
            case Constant.UNKNOWN_RESOURCE_INTENT_REQUEST_CODE:
                switch (resultCode) {
                    case Activity.RESULT_OK:
                        installApkProgramatically();
    
                        break;
                    case Activity.RESULT_CANCELED:
                        //unknown resouce installation cancelled
    
                        break;
                }
                break;
        }
    }
    

    【讨论】:

    • 更新完成后,我收到一个“未安装应用程序”对话框,显然,它实际上并没有安装更新。
    • App Not install 对话框会在某些情况下出现。 1.当前运行的app和安装APK的签名不同。 2.版本应该大于当前运行的应用程序。 3. 两个应用程序都应该签名或未签名。一个签名另一个未签名意味着这个问题会来。
    • 我试图从工作室更新构建,而不是实际的 apk。掌心
    【解决方案9】:

    另一个不需要硬编码接收应用程序的解决方案,因此更安全:

    Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
    intent.setData( Uri.fromFile(new File(pathToApk)) );
    startActivity(intent);
    

    【讨论】:

      【解决方案10】:

      是的,这是可能的。但为此,您需要手机安装未经验证的来源。例如,slideMe 就是这样做的。我认为您能做的最好的事情是检查应用程序是否存在并为 Android Market 发送意图。您应该使用 android Market 的 url 方案。

      market://details?id=package.name
      

      我不确切知道如何启动活动,但如果您使用这种 url 启动活动。它应该会打开 android 市场并让您选择安装应用程序。

      【讨论】:

      • 如我所见,这个解决方案最接近事实:)。但这不适合我的情况。我需要动态加载器,依赖于当前的用户环境,并进入市场——这不是一个好的解决方案。但无论如何,谢谢。
      【解决方案11】:

      值得注意的是,如果您使用DownloadManager 开始下载,请务必将其保存到外部位置,例如setDestinationInExternalFilesDir(c, null, "&lt;your name here&gt;).apk";。 package-archive 类型的意图似乎不喜欢用于下载到内部位置的content: 方案,但喜欢file:。 (尝试将内部路径包装到 File 对象中然后获取路径也不起作用,即使它导致 file: url,因为应用程序不会解析 apk;看起来它必须是外部的。 )

      例子:

      int uriIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
      String downloadedPackageUriString = cursor.getString(uriIndex);
      File mFile = new File(Uri.parse(downloadedPackageUriString).getPath());
      Intent promptInstall = new Intent(Intent.ACTION_VIEW)
              .setDataAndType(Uri.fromFile(mFile), "application/vnd.android.package-archive")
              .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      appContext.startActivity(promptInstall);
      

      【讨论】:

        【解决方案12】:

        只是一个扩展,如果有人需要一个库,那么this 可能会有所帮助。感谢Raghav

        【讨论】:

          【解决方案13】:

          试试这个

          String filePath = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
          String title = filePath.substring( filePath.lastIndexOf('/')+1, filePath.length() );
          Intent intent = new Intent(Intent.ACTION_VIEW);
          intent.setDataAndType(Uri.fromFile(new File(filePath)), "application/vnd.android.package-archive");
          intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
          MainActivity.this.startActivity(intent);
          

          【讨论】:

            【解决方案14】:

            大约两个月前来到这里,据说想通了。今天回来了,不能正面或反面。据我所知,我的设置没有任何改变,所以很明显,我过去想出的任何东西都不足以满足现在的我。我终于设法让某些东西再次发挥作用,所以在这里记录下来,以供将来我和其他任何可能从另一次尝试中受益的人使用。

            此尝试代表原始 Android Java Install APK - Session API 示例的直接 Xamarin C# 翻译。它可能需要一些额外的工作,但这至少是一个开始。我有它在 Android 9 设备上运行,虽然我有针对 Android 11 的项目。

            InstallApkSessionApi.cs

            namespace LauncherDemo.Droid
            {
                using System;
                using System.IO;
            
                using Android.App;
                using Android.Content;
                using Android.Content.PM;
                using Android.OS;
                using Android.Widget;
            
                [Activity(Label = "InstallApkSessionApi", LaunchMode = LaunchMode.SingleTop)]
                public class InstallApkSessionApi : Activity
                {
                    private static readonly string PACKAGE_INSTALLED_ACTION =
                            "com.example.android.apis.content.SESSION_API_PACKAGE_INSTALLED";
            
                    protected override void OnCreate(Bundle savedInstanceState)
                    {
                        base.OnCreate(savedInstanceState);
                        this.SetContentView(Resource.Layout.install_apk_session_api);
            
                        // Watch for button clicks.
                        Button button = this.FindViewById<Button>(Resource.Id.install);
                        button.Click += this.Button_Click;
                    }
            
                    private void Button_Click(object sender, EventArgs e)
                    {
                        PackageInstaller.Session session = null;
                        try
                        {
                            PackageInstaller packageInstaller = this.PackageManager.PackageInstaller;
                            PackageInstaller.SessionParams @params = new PackageInstaller.SessionParams(
                                    PackageInstallMode.FullInstall);
                            int sessionId = packageInstaller.CreateSession(@params);
                            session = packageInstaller.OpenSession(sessionId);
                            this.AddApkToInstallSession("HelloActivity.apk", session);
            
                            // Create an install status receiver.
                            Context context = this;
                            Intent intent = new Intent(context, typeof(InstallApkSessionApi));
                            intent.SetAction(PACKAGE_INSTALLED_ACTION);
                            PendingIntent pendingIntent = PendingIntent.GetActivity(context, 0, intent, 0);
                            IntentSender statusReceiver = pendingIntent.IntentSender;
            
                            // Commit the session (this will start the installation workflow).
                            session.Commit(statusReceiver);
                        }
                        catch (IOException ex)
                        {
                            throw new InvalidOperationException("Couldn't install package", ex);
                        }
                        catch
                        {
                            if (session != null)
                            {
                                session.Abandon();
                            }
            
                            throw;
                        }
                    }
            
                    
                    private void AddApkToInstallSession(string assetName, PackageInstaller.Session session)
                    {
                        // It's recommended to pass the file size to openWrite(). Otherwise installation may fail
                        // if the disk is almost full.
                        using Stream packageInSession = session.OpenWrite("package", 0, -1);
                        using Stream @is = this.Assets.Open(assetName);
                        byte[] buffer = new byte[16384];
                        int n;
                        while ((n = @is.Read(buffer)) > 0)
                        {
                            packageInSession.Write(buffer, 0, n);
                        }
                    }
            
                    // Note: this Activity must run in singleTop launchMode for it to be able to receive the intent
                    // in onNewIntent().
                    protected override void OnNewIntent(Intent intent)
                    {
                        Bundle extras = intent.Extras;
                        if (PACKAGE_INSTALLED_ACTION.Equals(intent.Action))
                        {
                            PackageInstallStatus status = (PackageInstallStatus)extras.GetInt(PackageInstaller.ExtraStatus);
                            string message = extras.GetString(PackageInstaller.ExtraStatusMessage);
                            switch (status)
                            {
                                case PackageInstallStatus.PendingUserAction:
                                    // This test app isn't privileged, so the user has to confirm the install.
                                    Intent confirmIntent = (Intent) extras.Get(Intent.ExtraIntent);
                                    this.StartActivity(confirmIntent);
                                    break;
                                case PackageInstallStatus.Success:
                                    Toast.MakeText(this, "Install succeeded!", ToastLength.Short).Show();
                                    break;
                                case PackageInstallStatus.Failure:
                                case PackageInstallStatus.FailureAborted:
                                case PackageInstallStatus.FailureBlocked:
                                case PackageInstallStatus.FailureConflict:
                                case PackageInstallStatus.FailureIncompatible:
                                case PackageInstallStatus.FailureInvalid:
                                case PackageInstallStatus.FailureStorage:
                                    Toast.MakeText(this, "Install failed! " + status + ", " + message,
                                            ToastLength.Short).Show();
                                    break;
                                default:
                                    Toast.MakeText(this, "Unrecognized status received from installer: " + status,
                                            ToastLength.Short).Show();
                                    break;
                            }
                        }
                    }
                }
            }
            

            AndroidManifest.xml

            <?xml version="1.0" encoding="utf-8"?>
            <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.launcherdemo" android:installLocation="auto">
                <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
                <application android:label="LauncherDemo.Android" android:theme="@style/MainTheme" />
                <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
                <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
            </manifest>
            

            我特别喜欢这种方法的一点是,它不需要在清单中进行任何特殊工作 - 不需要广播接收器、文件提供程序等。当然,这将应用资产中的一些 APK 作为其源,而更有用的系统可能会使用一些给定的 APK 路径。我想这会引入一定程度的额外复杂性。此外,在 Android 完成之前,我从未遇到过 Xamarin GC 关闭流的任何问题(至少据我所知)。我也没有任何关于 APK 没有被解析的问题。我确保使用签名的 APK(在部署到设备时由 Visual Studio 生成的 APK 工作得很好),而且我没有遇到任何文件访问权限问题,仅仅是因为在本示例中使用了来自应用程序资产的 APK .

            这里提供的其他一些答案的一件事是使侧载权限授予更加简化的想法。 answer by Yabaze Cool 提供了这个功能:

            Intent unKnownSourceIntent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(Uri.parse(String.format("package:%s", activity.getPackageName())));
            
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            
               if (!activity.getPackageManager().canRequestPackageInstalls()) {
                   startActivityForResult(unKnownSourceIntent, Constant.UNKNOWN_RESOURCE_INTENT_REQUEST_CODE);
            ...
            

            当我测试我的翻译时,我卸载了启动器演示和它安装的应用程序。没有向canRequestPackageInstalls 提供检查使我不得不手动按下一个额外的设置按钮才能将我带到与上述ACTION_MANAGE_UNKNOWN_APP_SOURCES 意图相同的对话框。因此,添加此逻辑有助于在一定程度上简化用户的安装过程。

            【讨论】:

            • 自然而然地,在发布这篇文章后的几分钟内,我找到了我以前的工作解决方案,与上面的几乎相同......请原谅我,我囤积了我的知识,我受到了惩罚......
            【解决方案15】:

            UpdateNode 为 Android 提供了一个 API,用于从另一个应用程序内部安装 APK 包。

            您可以在线定义您的更新并将 API 集成到您的应用程序中 - 就是这样。
            目前 API 处于 Beta 状态,但您已经可以自己进行一些测试。

            除此之外,UpdateNode 还提供通过系统显示消息 - 如果您想向用户传达重要信息,这非常有用。

            我是客户端开发团队的一员,至少在我自己的 Android 应用程序中使用消息功能。

            See here a description how to integrate the API

            【讨论】:

            • 网站出现了一些问题。获取不到 api_key 也无法继续注册。
            • 嗨 @infinite_loop_ 您可以在您的用户帐户部分找到 API 密钥:updatenode.com/profile/view_keys
            【解决方案16】:

            试试这个 - 写在清单上:

            uses-permission android:name="android.permission.INSTALL_PACKAGES"
                    tools:ignore="ProtectedPermissions"
            

            编写代码:

            File sdCard = Environment.getExternalStorageDirectory();
            String fileStr = sdCard.getAbsolutePath() + "/Download";// + "app-release.apk";
            File file = new File(fileStr, "app-release.apk");
            Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file),
                                    "application/vnd.android.package-archive");
            
            startActivity(promptInstall);
            

            【讨论】:

            • 您不需要系统权限即可启动 Package Installer 活动
            【解决方案17】:

            首先将以下行添加到 AndroidManifest.xml 中:

            <uses-permission android:name="android.permission.INSTALL_PACKAGES"
                tools:ignore="ProtectedPermissions" />
            

            然后使用以下代码安装apk:

            File sdCard = Environment.getExternalStorageDirectory();
                        String fileStr = sdCard.getAbsolutePath() + "/MyApp";// + "app-release.apk";
                        File file = new File(fileStr, "TaghvimShamsi.apk");
                        Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file),
                                "application/vnd.android.package-archive");
                        startActivity(promptInstall);
            

            【讨论】:

              【解决方案18】:

              基于answer@Uroš Podkrižnik。

              通过 APK 安装应用程序可能因不同版本的 android(API 级别 21-30)而异:

              private var uri: Uri? = null
              private var manager: DownloadManager? = null
              private var file: File? = null
              private var request: DownloadManager.Request? = null
              
              private val REQUEST_WRITE_PERMISSION = 786
              private val REQUEST_INSTALL_PACKAGE = 1234
              
              private var receiver: BroadcastReceiver? = null
              private var installIntent: Intent? = null
              
              ...
              override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
                  val externalStorageDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                      context?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
                  } else {
                      @Suppress("DEPRECATION")
                      Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                  }
                  val destination = "$externalStorageDir/Application.apk"
                  uri = Uri.parse("file://$destination")
              
                  file = File(destination)
                  file?.let { if (it.exists()) it.delete() }
                  
                  request = DownloadManager.Request(Uri.parse("https://path_to_file/application.apk"))
                  request?.let {
                      it.setDescription("Update App")
                      it.setTitle("Application")
                      it.setDestinationUri(uri)
                  }
                  manager = context?.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
              
                  // for level android api >= 23 needs permission to write to external storage
                  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                      if (ContextCompat.checkSelfPermission(context!!, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                          // here you can display the loading diagram
                          registerReceiver()
                      } else {
                          // request for permission to write to external storage
                          requestPermissions(
                              arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                              REQUEST_WRITE_PERMISSION
                          )
                      }
                  } else {
                      // here you can display the loading diagram
                      registerReceiver()
                  }
              }
              

              创建并注册接收者:

              private val onDownloadComplete = object : BroadcastReceiver() {
                  // install app when apk is loaded
                  override fun onReceive(ctxt: Context, intent: Intent) {
                      val mimeType = "application/vnd.android.package-archive"
                      receiver = this
                      try {
                          installIntent = Intent(Intent.ACTION_VIEW)
                          installIntent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
              
                          // for android api >= 24 requires FileProvider
                          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                              installIntent?.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
              
                              val fileProviderURI = FileProvider.getUriForFile(
                                  context!!,
                                  context!!.applicationContext.packageName + ".provider",
                                  file!!)
              
                              installIntent?.setDataAndType(fileProviderURI, mimeType)
              
                              // for android api >= 26 requires permission to install from APK in settings
                              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                                  if (context!!.applicationContext.packageManager.canRequestPackageInstalls()) {
                                      installFromAPK()
                                  } else goToSecuritySettings()
                              } else installFromAPK()
                          } else {
                              // for android api < 24 used file:// instead content://
                              // (no need to use FileProvider)
                              installIntent?.setDataAndType(uri, mimeType)
                              installFromAPK()
                          }
                      } catch (e: Exception) {
                          // view error message
                      }
                  }
              }
              
              private fun registerReceiver() {
                  manager!!.enqueue(request)
                  context?.registerReceiver(
                      onDownloadComplete,
                      IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
                  )
              }
              
              private fun installFromAPK() {
                  try {
                      startActivity(installIntent)
                      context?.unregisterReceiver(receiver)
                      activity?.finish()
                  } catch (e: Exception) {
                      // view error message
                  }
              }
              
              // go to settings for get permission install from APK
              private fun goToSecuritySettings() {
                  val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(
                      Uri.parse(String.format(
                          "package:%s",
                          context!!.applicationContext.packageName
                      ))
                  )
                  try {
                      startActivityForResult(intent, REQUEST_INSTALL_PACKAGE)
                  } catch (e: Exception) {
                      // view error message
                  }
              }
              

              截取权限请求WRITE_EXTERNAL_STORAGE的结果:

              override fun onRequestPermissionsResult(
                      requestCode: Int,
                      permissions: Array<String>,
                      grantResults: IntArray
              ) {
                  super.onRequestPermissionsResult(requestCode, permissions, grantResults)
                  if (requestCode == REQUEST_WRITE_PERMISSION
                          && grantResults.isNotEmpty()
                          && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                      try {
                          // here you can display the loading diagram
                          registerReceiver()
                      } catch (e: Exception) {
                          // view error message
                      }
                  }
              }
              

              在安全设置中截取用户选择的结果:

              @RequiresApi(api = Build.VERSION_CODES.O)
              override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
                  super.onActivityResult(requestCode, resultCode, data)
                  if (requestCode == REQUEST_INSTALL_PACKAGE
                          && resultCode == AppCompatActivity.RESULT_OK) {
                      if (context!!.applicationContext.packageManager.canRequestPackageInstalls()) {
                          installFromAPK()
                      }
                  } else {
                      // view error message
                  }
              }
              

              添加到您的清单中:

              <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
              <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
              <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
              <application...>
                  ...
                  <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/provider_paths" />
                  </provider>
                  ...
              </application>
              

              将 provider_paths.xml 文件添加到 res/xml:

              <?xml version="1.0" encoding="utf-8"?>
              <paths>
                  <external-path name="external_files" path="."/>
              </paths>
              

              对于 android API level = 30,从安全设置返回不起作用, 所以用浏览器安装:

              try {
                  val intent = Intent(Intent.ACTION_VIEW)
                  intent.data = Uri.parse("https://path_to_file/application.apk")
                  startActivity(intent)
                  activity?.finish()
              } catch (e: ActivityNotFoundException) { }
              

              【讨论】:

                猜你喜欢
                • 2012-06-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-09-15
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多