Uygulamayı Android'e programlı olarak yükle


212

Özel bir Android uygulamasından dinamik olarak indirilen bir apk'yi programlı olarak kurmanın mümkün olup olmadığını bilmekle ilgileniyorum.


"Mevcut kullanıcı ortamına bağlı dinamik yükleyici" nin ne anlama geldiğini bilmiyorum. @Lie Ryan tarafından verilen cevap, seçtiğiniz herhangi bir yolla indirilen bir APK'yı nasıl yükleyebileceğinizi gösterir.
CommonsWare

Yanıtlar:


241

Bir play store bağlantısını veya bir yükleme istemini kolayca başlatabilirsiniz:

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

veya

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

Ancak, .apks'i kullanıcının açık izni olmadan yükleyemezsiniz ; cihaz ve program köklü değilse.


35
İyi cevap, ancak sabit kod kullanmayın /sdcard, çünkü bu Android 2.2 ve diğer cihazlarda yanlıştır. Environment.getExternalStorageDirectory()Bunun yerine kullanın .
CommonsWare

3
/ Asset / dizini yalnızca geliştirme makinenizde bulunur, uygulama bir APK'ya derlendiğinde, / asset / dizini artık var olmayan tüm öğeler APK içinde sıkıştırılmış olarak bulunur. / Asset / dizininizden kurmak istiyorsanız, önce bunu başka bir klasöre çıkarmanız gerekir.
Lie Ryan

2
@LieRyan. Cevabınızı görmek güzel. Özel ROM'lu köklü cihazım var. Kullanıcılardan yükleme düğmesine basmalarını istemeden temaları dinamik olarak yüklemek istiyorum. Bunu yapabilir miyim.
Sharanabasu Angadi

1
Bu, targetSdk 25 olduğunda artık işe yaramıyor gibi gözüküyor: "android.os.FileUriExposedException: ... apk Intent.getData () ... aracılığıyla uygulamanın ötesine maruz kalan apk. Nasıl olur?
android geliştirici

4
@android geliştirici: Bunu şu anda test edemiyorum, ancak targetSdkVersion> = 24 için aşağıdakiler geçerlidir: stackoverflow.com/questions/38200282/… bu yüzden FileProvider'ı kullanmanız gerekir.
Yalan 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);

Aynı sorunu yaşadım ve birkaç denemeden sonra benim için bu şekilde çalıştı. Nedenini bilmiyorum, ancak veri ve türü ayrı ayrı ayarlamak niyetimi bozdu.


Bu cevabı yeterince değerlendiremiyorum. Nedense, amaç verileri ve MIME türünün ayrı ayrı ayarlanması, API düzey 17'de ActivityNotFoundException özelliğine neden olur.
Brent M. Spell

Ben de buna karşı oy verdim. Bu form, işe alabileceğim tek aylaktı. Hala yıllar sonra .. bu garip böcek nedir? Saatler boşa. Eclipse (Helios), BTW kullanıyorum.
Tam

10
@ BrentM.Spell ve diğerleri: belgelere bakın, yalnızca veri VEYA türünü her ayarladığınızda, diğerinin otomatik olarak geçersiz kılındığını göreceksiniz, örneğin: setData()type parametresinin kaldırılmasına neden olur. Her setDataAndType()ikisi için de değer vermek istiyorsanız KULLANMALISINIZ . Burada: developer.android.com/reference/android/content/…
Bogdan Alexandru

41

Bu soruya sunulan çözümlerin tümü targetSdkVersion23 ve altı için geçerlidir. Bununla birlikte, Android N, yani API düzey 24 ve üstü için, aşağıdaki İstisna ile çalışmaz ve çökmezler:

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

Bu, Android 24'ten başlayarak Uri, indirilen dosyanın adreslenmesi için değişmiş olmasından kaynaklanmaktadır . Örneğin appName.apk, uygulamanın paket adıyla birincil harici dosya sisteminde depolanan bir yükleme dosyası com.example.testşu şekilde olur:

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

için API 23ve aşağıda gibi bir şey oysa

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

için API 24 ve yukarıda.

Bununla ilgili daha fazla ayrıntıyı burada bulabilirsinizBununla ve bunun üzerinden geçmeyeceğim.

İçin soruya cevap vermek için targetSdkVersionbir 24ve üzeri, tek adımları takip etmelidir: AndroidManifest.xml için aşağıdaki ekleyin:

<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. src, main paths.xmliçindeki xmlklasöre aşağıdaki dosyayı ekleyin res:

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

pathNameYukarıdaki örnek içerik uri örnekte gösterildiği olduğu vepathValue sistem üzerinde gerçek yol. Bir "." Koymak iyi bir fikir olabilir. (tırnak işaretleri olmadan) yukarıdaki herhangi bir ek alt dizin eklemek istemiyorsanız pathValue için.

  1. appName.apkBirincil harici dosya sistemine apk ile adı yüklemek için aşağıdaki kodu yazın :

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

Harici dosya sistemindeki kendi uygulamanızın özel dizinine yazarken de izin gerekmez.

Burada yukarıda kullandığım bir AutoUpdate kütüphanesi yazdım .


2
Bu yöntemi izledim. Ancak, kurulum düğmesine bastığımda dosya bozuk diyor. Bluetooth üzerinden aktarım yaparak aynı dosyayı yükleyebiliyorum. neden öyle?

HI Dosya bozuk hatası aldığınızda, APK'yı getirmek için uygulamanıza verdiğiniz ulaşım modu nedir, sunucudan indiriyorsanız, kopyalama dosyasındaki akışları sunucudan yıkamak için kontrol edin? Bluetooth çalışma ile aktarılan APK'yı yüklediğimden beri sorun olduğunu düşünüyorum.
Sridhar S

2
java.lang.NullPointerException: Boş bir nesne başvurusunda 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData (android.content.pm.PackageManager, java.lang.String)' sanal yöntemini çağırmayı deneyin
Makvin

Kütüphaneniz, kullanımla ilgili basit talimatlar ile birlikte basit bir
beniokuya

2
Çok teşekkür ederim, bu cevapla bir hafta süren mücadeleden sonra sorunumu çözdüm! @SridharS, gördüğüm gibi uzun zaman önce olduğunu biliyorum, ama eğer ilgileniyorsanız, 5. satırda, o zaman işe yaramalı .authorityStrsonra eklemelisiniz context.getPackageName().
sehrob

30

Daha derinlere inip Android Source'dan PackageInstaller uygulaması kaynakları buldum.

https://github.com/android/platform_packages_apps_packageinstaller

Manifest'ten izin gerektirdiğini gördüm:

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

Ve gerçek kurulum işlemi onaylandıktan sonra gerçekleşir

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

33
android.permission.INSTALL_PACKAGES yalnızca sistem imzalı uygulamalar için kullanılabilir. Yani bu pek yardımcı olmaz
Hubert Liberacki

21

Ben sadece benim apk dosya benim app "Veri" dizinine kaydedildi ve bu şekilde yüklenmesini sağlamak için apk dosyasında dünya okunabilir olması için izinleri değiştirmek gerektiğini gerçeği paylaşmak istiyorum, aksi takdirde sistem "Ayrıştırma hatası: Paketi Ayrıştırmada Bir Sorun Var"; yani @Horaceman'ın çözümünü kullanarak:

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

Aynı ayrıştırıcı hatası alıyorum! Nasıl çözeceğimi bilmiyorum? File.setReadable (true, false) ayarladım ama benim için çalışmıyor
Jai

15

Bu başkalarına çok yardımcı olabilir!

İlk:

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

İkinci: android 7 ve üstü için aşağıdaki gibi manifest bir sağlayıcı tanımlamanız gerekir!

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

Üçüncü olarak: res / xml klasöründe path.xml dosyasını aşağıdaki gibi tanımlayın! İç depolama için bu yolu başka bir şeye değiştirmek istiyorsanız birkaç yol var! Bu bağlantıya gidebilirsiniz: 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>

Dördüncü: Bu izni bildirimde eklemelisiniz:

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

Uygulamaya, paketlerin yüklenmesini isteme izni verir. 25'ten büyük hedefleme API'ları, Intent.ACTION_INSTALL_PACKAGE kullanmak için bu izne sahip olmalıdır.

Lütfen sağlayıcı yetkililerin aynı olduğundan emin olun!



3
Teşekkürler, dördüncü adım hepsinde eksik olan şeydi.
Amin Keshavarzian

1
Gerçekten, 4. nokta kesinlikle gereklidir. Diğer tüm cevaplar tamamen kaçırdı.
whiteagle

Android.permission.REQUEST_INSTALL_PACKAGES yalnızca sistem anahtar deposuyla imzalanmış sistem uygulamaları veya uygulamaları için mevcut değil mi?
pavi2410

@Pavitra Bir uygulamanın paketleri yüklemesini istemesine izin verir. 25'ten büyük hedefleme API'ları, Intent.ACTION_INSTALL_PACKAGE kullanmak için bu izne sahip olmalıdır. Koruma seviyesi: imza
Hadi Note

Bu kod Intent.ACTION_INSTALL_PACKAGE kullanmaz. Peki neden bu izin ??
Alexander Dyagilev

5

Alıcı uygulamanın sabit kodlanmasını gerektirmeyen ve bu nedenle daha güvenli olan başka bir çözüm:

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

4

Evet mümkün. Ancak bunun için doğrulanmamış kaynakları kurmak için telefona ihtiyacınız var. Örneğin, slideMe bunu yapar. Yapabileceğiniz en iyi şey, uygulamanın mevcut olup olmadığını kontrol etmek ve Android Market için bir niyet göndermek. android Market için url şemasını bir şey kullanmalısınız.

market://details?id=package.name

Etkinliğe nasıl başlayacağımı tam olarak bilmiyorum, ancak bu tür bir URL ile bir etkinliğe başlarsanız. Android pazarını açmalı ve uygulamaları yükleme seçeneği sunmalıdır.


Gördüğüm gibi, bu çözüm gerçeğe en yakın :). Ama benim durumum için uygun değil. Geçerli kullanıcı ortamına bağlı olarak dinamik yükleyiciye ihtiyacım var ve pazara gidiyorum - iyi bir çözüm değil. Neyse, teşekkürler.
Alkersan

4

İndirmenizi DownloadManagerbaşlatmak için kullanıyorsanız, harici bir konuma kaydettiğinizden emin olun setDestinationInExternalFilesDir(c, null, "<your name here>).apk";. Paket arşiv türüne sahip niyet, content:dahili bir konuma indirmelerle kullanılan şema gibi görünmüyor , ancak beğeniyor file:. (Dahili yolu bir File nesnesine sarmaya çalıştıktan sonra, bir yolla sonuçlanmasına rağmen yolu almak da işe yaramaz.file: , uygulama apk'yi ayrıştırmayacağı için url ile ; harici olması gerektiği gibi görünüyor.)

Misal:

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

3

İzin istemeyi unutmayın:

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

AndroidManifest.xml sağlayıcısını ve iznini ekleyin:

<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 dosya sağlayıcısı oluşturma res / xml / sağlayıcı_yolları.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>

Aşağıdaki örnek kodu kullanın:

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

        }

    }

    ...

}


2

bunu dene

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

1

Önce AndroidManifest.xml dosyasına aşağıdaki satırı ekleyin:

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

Sonra apk yüklemek için aşağıdaki kodu kullanın:

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

0

UpdateNode , Android için APK paketlerini başka bir Uygulama içinden yüklemek için bir API sağlar.

Güncellemenizi çevrimiçi olarak tanımlayabilir ve API'yı Uygulamanıza entegre edebilirsiniz - hepsi bu kadar.
Şu anda API Beta durumundadır, ancak zaten bazı testleri kendiniz yapabilirsiniz.

Bunun yanı sıra, UpdateNode, sistem üzerinden mesajlar da sunar - kullanıcılarınız için önemli bir şey söylemek istiyorsanız oldukça yararlıdır.

Client dev ekibinin bir parçasıyım ve kendi Android Uygulamam için en azından mesaj işlevselliğini kullanıyorum.

API'yı nasıl entegre edeceğinizin açıklamasına bakın


web sitesinde bazı sorunlar var. Api_key'i alamıyorum ve kayda devam edemiyorum.
infinite_loop_

Merhaba @infinite_loop_ API anahtarını kullanıcı hesabı bölümünde bulabilirsiniz: updatenode.com/profile/view_keys
sarahara

0

Bunu deneyin - Manifest üzerine yazın:

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

Kodu yazın:

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

1
Paket Yükleyici etkinliğini başlatmak için yalnızca sistem iznine ihtiyacınız yoktur
Clocker
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.