Instale la aplicación mediante programación en Android

212

Estoy interesado en saber si es posible instalar mediante programación un apk descargado dinámicamente desde una aplicación personalizada de Android.

Alkersan
fuente
No sé qué significa "cargador dinámico, dependiente del entorno del usuario actual". La respuesta proporcionada por @Lie Ryan muestra cómo puede instalar un APK descargado por cualquier medio que elija.
CommonsWare

Respuestas:

241

Puede iniciar fácilmente un enlace de Play Store o un mensaje de instalación:

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

o

Intent goToMarket = new Intent(Intent.ACTION_VIEW)
    .setData(Uri.parse("https://play.google.com/store/apps/details?id=com.package.name"));
startActivity(goToMarket);

Sin embargo, no puede instalar .apks sin el permiso explícito del usuario ; no a menos que el dispositivo y su programa estén rooteados.

Lie Ryan
fuente
35
Buena respuesta, pero no codifique /sdcard, ya que está mal en Android 2.2+ y otros dispositivos. Usar en su Environment.getExternalStorageDirectory()lugar.
CommonsWare
3
El directorio / asset / solo existe en su máquina de desarrollo, cuando la aplicación se compila en un APK, el directorio / asset / ya no existe ya que todos los activos están comprimidos dentro del APK. Si desea instalar desde su directorio / asset /, primero deberá extraerlo en otra carpeta.
Lie Ryan
2
@LieRyan. Es bueno ver tu respuesta. Tengo el dispositivo rooteado con ROM personalizada. Quiero instalar temas dinámicamente sin pedirle al usuario que presione el botón de instalación. Puedo hacer eso.
Sharanabasu Angadi
1
Esto ya no parece funcionar cuando targetSdk es 25. da una excepción: "android.os.FileUriExposedException: ... apk expuesto más allá de la aplicación a través de Intent.getData () ...". ¿Cómo?
Desarrollador de Android el
44
Desarrollador de Android: no puedo probar esto, por el momento, pero en targetSdkVersion> = 24, se aplica lo siguiente: stackoverflow.com/questions/38200282/…, por lo que debe usar FileProvider.
Lie Ryan
56
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);

Tuve el mismo problema y después de varios intentos, funcionó para mí de esta manera. No sé por qué, pero configurar los datos y el tipo por separado arruinó mi intención.

Horaceman
fuente
No puedo votar esta respuesta lo suficiente. Por alguna razón, establecer los datos de intención y el tipo MIME por separado provoca ActivityNotFoundException en el nivel 17 de API.
Brent M. Spell
Acabo de votar esto también. Este formulario fue el único que pude poner a trabajar. Aún años después ... ¿qué es este bicho raro? Horas desperdiciadas. Estoy usando Eclipse (Helios), por cierto.
Tam
10
@ BrentM.Spell y otros: mire la documentación, verá que cada vez que configura solo datos O tipo, el otro se anula automáticamente, por ejemplo: setData()causaría la eliminación del parámetro tipo. DEBE usar setDataAndType()si desea dar valores para ambos. Aquí: developer.android.com/reference/android/content/…
Bogdan Alexandru
41

Las soluciones proporcionadas a esta pregunta son aplicables a los targetSdkVersions de 23 y menos. Sin embargo, para Android N, es decir, el nivel de API 24 y superior, no funcionan y se bloquean con la siguiente excepción:

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

Esto se debe al hecho de que a partir de Android 24, la Uridirección del archivo descargado ha cambiado. Por ejemplo, un archivo de instalación denominado appName.apkalmacenado en el sistema de archivos externo primario de la aplicación con el nombre del paquete com.example.testsería

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

para API 23y debajo, mientras que algo como

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

para API 24 y encima.

Más detalles sobre esto se pueden encontrar aquí y no voy a pasar por ello.

Para responder a la pregunta targetSdkVersionde 24y más arriba, uno tiene que seguir estos pasos: Agregue lo siguiente al 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. Agregue el siguiente paths.xmlarchivo a la xmlcarpeta resen src, main:

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

El pathNamees el que se muestra en el ejemplo de contenido de uri anterior y pathValuees la ruta real en el sistema. Sería una buena idea poner un "." (sin comillas) para pathValue en lo anterior si no desea agregar ningún subdirectorio adicional.

  1. Escriba el siguiente código para instalar el apk con el nombre appName.apken el sistema de archivos externo primario:

    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();

Tampoco se necesita permiso al escribir en el directorio privado de su propia aplicación en el sistema de archivos externo.

He escrito una biblioteca de AutoUpdate aquí en la que he usado lo anterior.

Ali Nem
fuente
2
Seguí este método. Sin embargo, dice que el archivo está dañado cuando presiono el botón de instalación. Soy capaz de instalar el mismo archivo transfiriéndolo a través de bluetooth. ¿porque?
HI Cuando recibes un error de archivo dañado, ¿cuál es el modo de transporte que le diste a tu aplicación para traer el APK? Supongo que desde la instalación del APK que fue transferido por Bluetooth funcionando, ese es el problema.
Sridhar S
2
java.lang.NullPointerException: intento de invocar el método virtual 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData (android.content.pm.PackageManager, java.lang.String)' en una referencia de objeto nulo
Makvin
Su biblioteca podría tener al fin un archivo léame simple con instrucciones simples sobre el uso ...;)
Renetik
2
¡Muchas gracias, después de una semana de lucha con esta respuesta, resolví mi problema! @SridharS, sé que hace mucho tiempo, como veo, pero si le interesa, en la línea 5, debe agregar .authorityStrdespués, context.getPackageName()entonces debería funcionar.
sehrob
30

Bueno, profundicé y encontré fuentes de la aplicación PackageInstaller de Android Source.

https://github.com/android/platform_packages_apps_packageinstaller

De manifiesto encontré que requiere permiso:

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

Y el proceso real de instalación ocurre después de la confirmación

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);
Alkersan
fuente
33
android.permission.INSTALL_PACKAGES está disponible solo para las aplicaciones firmadas del sistema. Así que esto no ayudaría mucho
Hubert Liberacki
21

Solo quiero compartir el hecho de que mi archivo apk se guardó en el directorio "Datos" de mi aplicación y que necesitaba cambiar los permisos en el archivo apk para que fuera legible en todo el mundo para permitir que se instale de esa manera, de lo contrario, el sistema estaba lanzando "Error de análisis: hay un problema al analizar el paquete"; entonces usando la solución de @Horaceman que hace:

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);
usuario1604240
fuente
¡Recibo el mismo error de analizador! ¿No sé cómo resolverlo? Configuré file.setReadable (verdadero, falso) pero no me funciona
Jai
15

¡Esto puede ayudar mucho a otros!

Primero:

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();
    }
}

Segundo: para Android 7 y superior, debe definir un proveedor en manifiesto como a continuación.

    <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>

Tercero: ¡ defina path.xml en la carpeta res / xml como se muestra a continuación! Estoy usando esta ruta para el almacenamiento interno si desea cambiarla a otra cosa, ¡hay algunas maneras! Puedes ir a este enlace: 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>

Adelante: debe agregar este permiso en manifiesto:

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

Permite que una aplicación solicite paquetes de instalación. Las aplicaciones dirigidas a API mayores de 25 deben tener este permiso para usar Intent.ACTION_INSTALL_PACKAGE.

¡Asegúrese de que las autoridades del proveedor sean las mismas!


Hadi Note
fuente
3
Gracias, el cuarto paso fue lo que faltaba en todos ellos.
Amin Keshavarzian
1
De hecho, el cuarto punto es absolutamente necesario. Todas las demás respuestas lo perdieron por completo.
whiteagle
¿No está disponible android.permission.REQUEST_INSTALL_PACKAGES solo para aplicaciones del sistema o aplicaciones firmadas con el almacén de claves del sistema o lo que sea?
pavi2410
@Pavitra Permite que una aplicación solicite la instalación de paquetes. Las aplicaciones dirigidas a API mayores de 25 deben tener este permiso para usar Intent.ACTION_INSTALL_PACKAGE. Nivel de protección: firma
Hadi Note
Este código no usa Intent.ACTION_INSTALL_PACKAGE. Entonces, ¿por qué este permiso?
Alexander Dyagilev
5

Otra solución que no requiere codificar la aplicación receptora y que, por lo tanto, es más segura:

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

Si es posible. Pero para eso necesita el teléfono para instalar fuentes no verificadas. Por ejemplo, slideMe hace eso. Creo que lo mejor que puede hacer es verificar si la aplicación está presente y enviar una intención para Android Market. deberías usar algo del esquema de URL para Android Market.

market://details?id=package.name

No sé exactamente cómo iniciar la actividad, pero si comienzas una actividad con ese tipo de URL. Debería abrir el Android Market y darle la opción de instalar las aplicaciones.

Loïc Faure-Lacroix
fuente
Como veo, esta solución es la más cercana a la verdad :). Pero no es apropiado para mi caso. Necesito un cargador dinámico, que dependa del entorno de usuario actual y que vaya al mercado, no es una buena solución. Pero de todos modos, gracias.
Alkersan
4

Vale la pena señalar que si utiliza el DownloadManagerpara iniciar su descarga, asegúrese de guardarlo en una ubicación externa, por ejemplo setDestinationInExternalFilesDir(c, null, "<your name here>).apk";. La intención con un tipo de paquete-archivo no parece gustarle el content:esquema utilizado con las descargas a una ubicación interna, pero sí file:. (Intentar ajustar la ruta interna en un objeto File y luego obtener la ruta tampoco funciona, a pesar de que resulta en unfile: URL, ya que la aplicación no analizará el apk; parece que debe ser externo).

Ejemplo:

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);
qix
fuente
3

No olvides solicitar permisos:

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

Agregue en AndroidManifest.xml el proveedor y el permiso:

<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>

Crear proveedor de archivos 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>

Utilice el siguiente código de ejemplo:

   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();
            }

        }

    }

    ...

}
amiron
fuente
2

Solo una extensión, si alguien necesita una biblioteca, esto podría ayudar. Gracias a Raghav

HierroFlor
fuente
2

prueba esto

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);
Joolah
fuente
1

Primero agregue la siguiente línea a AndroidManifest.xml:

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

Luego use el siguiente código para instalar 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);
Abbas.moosavi
fuente
0

UpdateNode proporciona una API para Android para instalar paquetes APK desde dentro de otra aplicación.

Simplemente puede definir su actualización en línea e integrar la API en su aplicación, eso es todo.
Actualmente, la API está en estado Beta, pero ya puede hacer algunas pruebas usted mismo.

Además de eso, UpdateNode también ofrece mostrar mensajes a través del sistema, bastante útil si desea contar algo importante para sus usuarios.

Soy parte del equipo de desarrollo del cliente y estoy usando al menos la funcionalidad de mensajes para mi propia aplicación de Android.

Vea aquí una descripción de cómo integrar la API

sarahara
fuente
Hay algunos problemas con el sitio web. No puedo obtener la api_key y no puedo continuar con el registro.
infinite_loop_
Hola @infinite_loop_, puedes encontrar la clave API en la sección de tu cuenta de usuario: updatenode.com/profile/view_keys
sarahara
0

Prueba esto: escribe en el manifiesto:

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

Escribe el código:

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);
Phuoc-Loc Truong
fuente
1
No necesita ese permiso exclusivo del sistema para iniciar la actividad del Instalador de paquetes
Clocker el