¿Podemos instalar un APK desde un ContentProvider?

Estoy trabajando en una biblioteca para permitir que las aplicaciones se actualicen automáticamente, para aquellos que se distribuyen fuera de Android Market.

Mi plan original era incluir un código que descargaría el archivo APK al almacenamiento interno y luego lo instalaría desde allí a través de un ContentProvider y un content:// Uri. Sin embargo, cuando lo intenté, el sistema de instalación descargó una advertencia de "Omitir directorio:" a LogCat y no pudo instalarlo. Una vez que cambié a descargar el APK a un almacenamiento externo y usar un file:// Uri con ACTION_VIEW instalador Intent, funcionó.

El mensaje "Omitir directorio:" parece haber sido registrado por parsePackage() in PackageParser, que parece asumir que está trabajando con un File. Eso sugeriría que no podemos usar content:// Uri valores.

¿Alguien ha utilizado con éxito ACTION_VIEW en un application/vnd.android.package-archive Intent con un content:// Uri? Si es así, ¿hubo algún truco específico para configurar el ContentProvider eso hizo que funcionara?

¡Gracias!

preguntado el 09 de marzo de 12 a las 16:03

y qué tan exactamente se ve este ContentProvider (especialmente la implementación de openFile)? -

@Selvin: Un ContentProvider con una openFile() la implementación es lo que se necesita para permitir que se sirvan los archivos, y usé una implementación trivial existente que sé que funciona en general como base Mi pregunta es si alguien ha logrado que esto funcione con éxito, ya que mi lectura del código fuente sugiere que no es compatible, independientemente de cuál sea el ContentProvider parece. -

No lo he probado, pero ¿has visto el método descrito aquí? stackoverflow.com/a/4605040/377260 -

@iPaulPro: Eso solo funcionaría si estuviera creando firmware. -

4 Respuestas

La documentación para ACTION_INSTALL_PACKAGE es incorrecta. También solo aceptará archivos.

Por lo tanto, mi única sugerencia sería crear una copia del archivo en el área de archivos de aplicaciones, hacerlo legible para todo el mundo y limpiar los archivos sobrantes en una fecha posterior.

Respuesta incorrecta anterior: En 4.0 y superior, hay un ACTION_INSTALL_PACKAGE que aceptará un content:// URI (Javoc), pero, antes de eso, está limitado a instalar a través de ACTION_VIEW, lo que supone que el URI pasado es un archivo:// URI.

respondido 14 mar '12, 14:03

¿Has probado esto? Obtengo los mismos resultados que con ACTION_VIEW cuando uso un content:// esquema: un mensaje de LogCat "Omitir directorio" y el instalador se retira. - CommonsWare

Tenía fe en la documentación, pero parece que la documentación es incorrecta. El código que da servicio a ACTION_INSTALL_PACKAGE solo aceptará archivos también. Es hora de registrar un error. Lo siento. - al sutton

Bueno, al menos no estoy completamente loco, entonces. Incompletamente loco, sin duda. :-) ¡Gracias por tu ayuda! - CommonsWare

Nota la content El esquema funciona en Android 7.0, aunque no en versiones anteriores de Android. - CommonsWare

IMPORTANTE Context.MODE_WORLD_READABLE está en desuso y (de la documentación :) "A partir de [Android] N intentar usar este modo generará una SecurityException". solía Context.MODE_WORLD_READABLE para permitir que el sistema acceda al archivo APK descargado en el directorio de almacenamiento privado de la aplicación, por lo que no necesitaba el permiso para el almacenamiento externo, ¡pero a partir de Android 7 esto ya no es posible! - micha f

Supongo que esto no es posible, ya que la API de Java no parece permitirlo. Proveedores de contenido openFile() devuelve un ParcelFileDescriptor, de donde se puede obtener una java.io.FileDescriptor. A continuación, puede utilizar este FileDescriptor para abrir un FileInputStream o con una FileOutputStream. Desafortunadamente, no puede usarlo para abrir un RandomAccessFile (a pesar de que RandomAccessFile funciona en los descriptores de la misma manera que los demás, el constructor que necesitaría simplemente falta en la API).

Como los archivos APK son archivos ZIP, que deben leerse desordenados (hay que buscar hasta el final para encontrar el directorio de archivos), supongo que la implementación de la instalación requerirá un RandomAccessFile, por lo que no habría sido posible respaldar el caso que intenta implementar.

Respondido 11 Feb 13, 13:02

Estoy de acuerdo con su evaluación, pero si bien su análisis tomó un camino diferente al mío, todavía estoy más o menos donde estoy: "bueno, no vea posible". Espero algo un poco más definitivo. :-) Sin embargo, aceptaré tu respuesta si no consigo algo mejor. ¡Gracias! - CommonsWare

El PackageManagerService que maneja la instalación crea una copia del archivo APK y, por lo tanto, no requiere el URI original para proporcionar una transmisión con capacidad de búsqueda. - al sutton

He leído el interior de los zips a través de un proveedor de contenido (descargo complementos para uno de mis proyectos y consisten en un zip proporcionado por un proveedor de contenido). - HammeReD

Tenga en cuenta que Android 7.0+ admite content Uri valores ahora. - CommonsWare

Estoy de acuerdo con el análisis de Jules, y agregaría precisiones concretas:

In PackageInstallerActivity, que es llamado por ACTION_VIEW en un apk, hay esto en el onCreate() método:

315 String apkPath = mPackageURI.getPath();
316 File apkFile = new File(apkPath);

Y antes de eso, este método de PackageUtil se llama:

73 public static  PackageParser.Package getPackageInfo(Uri packageURI) {
74     final String archiveFilePath = packageURI.getPath();
75     PackageParser packageParser = new PackageParser(archiveFilePath);
76     File sourceFile = new File(archiveFilePath);
77     DisplayMetrics metrics = new DisplayMetrics();
78     metrics.setToDefaults();
79     return packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0);
80 }

Todo esto tiende a confirmar que PackageManager espera solo File Uris.

El registro que tienes, Skipping dir: se encuentra en packageParser.parsePackage, que prueba si la ruta proporcionada en el Uri es un archivo o no.

respondido 12 mar '12, 15:03

Tengo esto en una de mis aplicaciones que me permite acceder al almacenamiento local (preferencia del usuario seleccionable antes de que me trates;))

import java.io.*;
import android.content.*;
import android.database.*;
import android.net.*;
import android.os.*;
import android.preference.PreferenceManager;
import android.util.Log;

public class LocalFileContentProvider extends ContentProvider {
   private static final String URI_PREFIX = "content://your.content.provider.as.per.manifest";

   public static String constructUri(String url) {
       Uri uri = Uri.parse(url);
       return uri.isAbsolute() ? url : URI_PREFIX + url;
   }

   @Override
   public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {

       SharedPreferences app_preferences = PreferenceManager.getDefaultSharedPreferences(getContext());

       boolean allowLocal = app_preferences.getBoolean("allowLocalFiles", false);

        if (allowLocal) {    
            try {
                File file = new File(uri.getPath());

                if (file.isDirectory())
                    return null;

                ParcelFileDescriptor parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
                return parcel;
            } catch (Exception e) {
                return null;
            }
        } else {
            return null;
        }

   }

   @Override
   public boolean onCreate() {
       return true;
   }

   @Override
   public int delete(Uri uri, String s, String[] as) {
       throw new UnsupportedOperationException("Not supported by this provider");
   }

   @Override
   public String getType(Uri uri) {
       throw new UnsupportedOperationException("Not supported by this provider");
   }

   @Override
   public Uri insert(Uri uri, ContentValues contentvalues) {
       throw new UnsupportedOperationException("Not supported by this provider");
   }

   @Override
   public Cursor query(Uri uri, String[] as, String s, String[] as1, String s1) {
       throw new UnsupportedOperationException("Not supported by this provider");
   }

   @Override
   public int update(Uri uri, ContentValues contentvalues, String s, String[] as) {
       throw new UnsupportedOperationException("Not supported by this provider");
   }

}

Mi manifiesto contiene

<provider android:name="it.automated.android.kiosk.se.LocalFileContentProvider"
      android:authorities="it.automated" />

y luego para iniciar la instalación (o acción)

Intent viewIntent = new Intent(Intent.ACTION_VIEW);
viewIntent.setDataAndType(Uri.parse(url), mimeType);

respondido 12 mar '12, 15:03

Lo intentaré de nuevo. Su código es casi idéntico al que comencé, que falló con los resultados descritos en la pregunta misma. ¡Gracias! - CommonsWare

El problema no está en el lado del proveedor, por lo que incluso si usó el código de demostración API (desarrollador.android.com/resources/samples/ApiDemos/src/com/…) el problema aún persistiría. - al sutton

Puede usar un WebView como intermediario que funciona perfectamente bien. - borroso

Hola, @CommonsWare, soy nuevo aquí y principiante en Android también. Solo necesito un poco de ayuda con respecto a su pregunta. ¿Puede informarme con una respuesta detallada? ¿Cómo puedo instalar otra aplicación con la ayuda de una aplicación, como almacenar? el .apk en una aplicación, etc. - Haseeb Warriach

en resumen, quiero vincular un apk con otro apk para instalar, ¿es posible? si es posible con las últimas versiones, por favor hágamelo saber cómo es - Haseeb Warriach

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.