Instal Aplikasi secara terprogram di Android

212

Saya tertarik untuk mengetahui apakah mungkin untuk menginstal secara sistematis APK yang diunduh secara dinamis dari aplikasi Android khusus.

Alkersan
sumber
Saya tidak tahu apa artinya "loader dinamis, tergantung pada lingkungan pengguna saat ini". Jawaban yang diberikan oleh @Lie Ryan menunjukkan bagaimana Anda dapat menginstal APK yang diunduh dengan cara apa pun yang Anda pilih.
CommonsWare

Jawaban:

241

Anda dapat dengan mudah meluncurkan tautan play store atau prompt instal:

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

atau

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

Namun, Anda tidak dapat menginstal .apks tanpa izin eksplisit pengguna ; tidak kecuali perangkat dan program Anda di-root.

Lie Ryan
sumber
35
Jawaban yang bagus, tetapi jangan hardcode /sdcard, karena itu salah pada Android 2.2+ dan perangkat lain. Gunakan Environment.getExternalStorageDirectory()sebagai gantinya.
CommonsWare
3
Direktori / aset / hanya ada di mesin pengembangan Anda, ketika aplikasi dikompilasi ke APK, direktori / aset / tidak lagi ada karena semua aset zip di dalam APK. Jika Anda ingin menginstal dari / aset / direktori Anda, Anda harus mengekstraknya ke folder lain terlebih dahulu.
Lie Ryan
2
@LyanRyan. Senang melihat jawaban Anda. Saya memiliki perangkat yang telah di-rooting dengan custom ROM. Saya ingin menginstal tema secara dinamis tanpa meminta pengguna untuk menekan tombol install. Bisakah saya melakukan itu.
Sharanabasu Angadi
1
Ini tampaknya tidak berfungsi lagi ketika targetSdk adalah 25. ini memberikan pengecualian: "android.os.FileUriExposedException: ... apk terbuka di luar aplikasi melalui Intent.getData () ...". Bagaimana bisa?
pengembang android
4
@android developer: Saya tidak bisa menguji ini, saat ini, tetapi pada targetSdkVersion> = 24, yang berikut ini berlaku: stackoverflow.com/questions/38200282/... jadi Anda harus menggunakan 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);

Saya memiliki masalah yang sama dan setelah beberapa upaya, berhasil bagi saya dengan cara ini. Saya tidak tahu mengapa, tetapi mengatur data dan mengetik secara terpisah mengacaukan niat saya.

Horaceman
sumber
Saya tidak bisa cukup menjawab jawaban ini. Untuk beberapa alasan, menetapkan data maksud dan tipe MIME secara terpisah menyebabkan ActivityNotFoundException di tingkat API 17.
Brent M. Spell
Saya baru saja memilih ini juga. Bentuk ini adalah satu-satunya yang bisa aku dapatkan untuk bekerja. Masih bertahun-tahun kemudian .. apa bug freakin ini? Jam terbuang sia-sia. Saya menggunakan Eclipse (Helios), BTW.
Tam
10
@ BrentM.Spell dan lain-lain: lihat ke dalam dokumentasi, Anda akan melihat bahwa setiap kali Anda menetapkan hanya data ATAU tipe, yang lain secara otomatis dibatalkan, misalnya: setData()akan menyebabkan parameter tipe dihapus. Anda HARUS menggunakan setDataAndType()jika Anda ingin memberikan nilai untuk keduanya. Di sini: developer.android.com/reference/android/content/…
Bogdan Alexandru
41

Solusi yang diberikan untuk pertanyaan ini berlaku untuk targetSdkVersions 23 dan di bawah. Untuk Android N, yaitu API level 24, dan di atasnya, bagaimanapun, mereka tidak bekerja dan macet dengan Pengecualian berikut:

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

Ini disebabkan oleh fakta bahwa mulai dari Android 24, Uriuntuk menangani file yang diunduh telah berubah. Misalnya, file instalasi yang bernama appName.apkdisimpan pada sistem file eksternal utama dari aplikasi dengan nama paket com.example.testakan sama

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

untuk API 23dan di bawah, sedangkan sesuatu seperti

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

untuk API 24 dan di atas.

Rincian lebih lanjut tentang ini dapat ditemukan di sini dan saya tidak akan membahasnya.

Untuk menjawab pertanyaan untuk targetSdkVersiondari 24dan di atas, kita harus ikuti langkah berikut: Tambahkan berikut ke 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. Tambahkan paths.xmlfile berikut ke xmlfolder di resdalam 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>

The pathNameadalah bahwa ditunjukkan pada contoh uri konten teladan di atas dan pathValuemerupakan jalan yang sebenarnya pada sistem. Ini akan menjadi ide yang bagus untuk menempatkan "." (tanpa tanda kutip) untuk pathValue di atas jika Anda tidak ingin menambahkan subdirektori tambahan.

  1. Tulis kode berikut untuk menginstal apk dengan nama appName.apkpada sistem file eksternal utama:

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

Tidak ada izin juga diperlukan saat menulis ke direktori pribadi aplikasi Anda pada sistem file eksternal.

Saya telah menulis perpustakaan AutoUpdate di sini di mana saya telah menggunakan di atas.

Ali Nem
sumber
2
Saya mengikuti metode ini. Namun, dikatakan file rusak ketika saya menekan tombol install. Saya dapat menginstal file yang sama dengan mentransfer melalui bluetooth. kenapa begitu?
HI Ketika Anda mendapatkan kesalahan file yang rusak, apa moda transportasi yang Anda berikan ke aplikasi Anda untuk membawa APK, jika Anda mengunduh dari server berarti, periksa untuk menyiram stream di file yang disalin dari server? Karena menginstal APK yang ditransfer oleh Bluetooth berfungsi berarti itu masalahnya saya kira.
Sridhar S
2
java.lang.NullPointerException: Mencoba untuk memanggil metode virtual 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData (android.content.pm.PackageManager, java.lang.String)' pada referensi objek nol
Makvin
Pustaka Anda akhirnya bisa memiliki readme sederhana dengan instruksi sederhana tentang penggunaan ...;)
Renetik
2
Terima kasih banyak, setelah satu minggu berjuang dengan jawaban ini saya menyelesaikan masalah saya! @RidharS, saya tahu ini sudah lama sekali, seperti yang saya lihat, tetapi jika Anda tertarik, pada baris 5, Anda harus menambahkan .authorityStrsetelah context.getPackageName()itu harus bekerja.
sehrob
30

Baiklah, saya menggali lebih dalam, dan menemukan sumber aplikasi PackageInstaller dari Android Source.

https://github.com/android/platform_packages_apps_packageinstaller

Dari manifes saya menemukan bahwa itu memerlukan izin:

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

Dan proses instalasi yang sebenarnya terjadi setelah konfirmasi

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
sumber
33
android.permission.INSTALL_PACKAGES hanya tersedia untuk aplikasi yang ditandatangani sistem. Jadi ini tidak akan banyak membantu
Hubert Liberacki
21

Saya hanya ingin berbagi fakta bahwa file apk saya disimpan ke direktori "Data" aplikasi saya dan bahwa saya perlu mengubah izin pada file apk agar dapat dibaca dunia agar dapat diinstal dengan cara itu, jika tidak, sistem melemparkan "Parse error: Ada Masalah Parsing Paket"; jadi gunakan solusi dari @Horaceman yang membuat:

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);
pengguna1604240
sumber
Saya mendapatkan kesalahan parser yang sama! Saya tidak tahu bagaimana mengatasinya? Saya mengatur file.setReadable (benar, salah) tetapi tidak berfungsi untuk saya
Jai
15

Ini bisa banyak membantu orang lain!

Pertama:

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

Kedua: Untuk android 7 dan di atas Anda harus menentukan penyedia dalam manifes seperti di bawah ini!

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

Ketiga: Tentukan path.xml di folder res / xml seperti di bawah ini! Saya menggunakan jalur ini untuk penyimpanan internal jika Anda ingin mengubahnya ke sesuatu yang lain ada beberapa cara! Anda dapat membuka tautan ini: 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>

Keempat: Anda harus menambahkan izin ini dalam manifes:

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

Mengizinkan aplikasi meminta paket pemasangan. API penargetan aplikasi yang lebih dari 25 harus memiliki izin ini untuk menggunakan Intent.ACTION_INSTALL_PACKAGE.

Pastikan otoritas penyedia sama!


Hadi Note
sumber
3
Terima kasih, langkah keempat adalah apa yang hilang pada mereka semua.
Amin Keshavarzian
1
Memang, poin ke-4 mutlak diperlukan. Semua jawaban lain tidak terjawab sepenuhnya.
whiteagle
Tidakkah android.permission.REQUEST_INSTALL_PACKAGES hanya tersedia untuk aplikasi sistem atau aplikasi yang ditandatangani dengan keystore sistem atau apa pun?
pavi2410
@Pavitra Mengizinkan aplikasi meminta paket pemasangan. API penargetan aplikasi yang lebih dari 25 harus memiliki izin ini untuk menggunakan Intent.ACTION_INSTALL_PACKAGE. Tingkat perlindungan: tanda tangan
Hadi Note
Kode ini tidak menggunakan Intent.ACTION_INSTALL_PACKAGE. Jadi mengapa ini izin ??
Alexander Dyagilev
5

Solusi lain yang tidak perlu melakukan hard-code pada aplikasi penerima dan karenanya lebih aman:

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

Iya itu mungkin. Tetapi untuk itu Anda memerlukan ponsel untuk menginstal sumber yang tidak diverifikasi. Sebagai contoh, slideMe melakukan itu. Saya pikir hal terbaik yang dapat Anda lakukan adalah memeriksa apakah aplikasi tersebut ada dan mengirim maksud untuk Android Market. Anda harus menggunakan sesuatu skema url untuk Android Market.

market://details?id=package.name

Saya tidak tahu persis bagaimana memulai aktivitas tetapi jika Anda memulai aktivitas dengan url semacam itu. Itu harus membuka pasar android dan memberi Anda pilihan untuk menginstal aplikasi.

Loïc Faure-Lacroix
sumber
Seperti yang saya lihat, solusi ini paling dekat dengan kebenaran :). Tetapi itu tidak sesuai untuk kasus saya. Saya perlu loader dinamis, tergantung pada lingkungan pengguna saat ini, dan pergi ke pasar - bukan solusi yang baik. Tapi bagaimanapun, terima kasih.
Alkersan
4

Perlu dicatat bahwa jika Anda menggunakan DownloadManageruntuk memulai unduhan Anda, pastikan untuk menyimpannya ke lokasi eksternal misalnya setDestinationInExternalFilesDir(c, null, "<your name here>).apk";. Maksud dengan tipe paket-arsip tampaknya tidak seperti content:skema yang digunakan dengan unduhan ke lokasi internal, tetapi memang suka file:. (Mencoba untuk membungkus jalur internal menjadi objek File dan kemudian mendapatkan jalur tidak berfungsi baik, meskipun itu menghasilkan afile: url, karena aplikasi tidak akan menguraikan apk; sepertinya itu harus eksternal.)

Contoh:

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

Jangan lupa untuk meminta izin:

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

Tambahkan AndroidManifest.xml penyedia dan izin:

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

Buat penyedia file 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>

Gunakan kode contoh di bawah ini:

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

Hanya ekstensi, jika ada yang membutuhkan perpustakaan maka ini mungkin membantu. Terima kasih untuk Raghav

IronBlossom
sumber
2

coba ini

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

Pertama tambahkan baris berikut ke AndroidManifest.xml:

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

Kemudian gunakan kode berikut untuk menginstal 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
sumber
0

UpdateNode menyediakan API untuk Android untuk menginstal paket APK dari dalam Aplikasi lain.

Anda bisa mendefinisikan Pembaruan Anda secara online dan mengintegrasikan API ke dalam Aplikasi Anda - hanya itu.
Saat ini API dalam keadaan Beta, tetapi Anda sudah dapat melakukan beberapa tes sendiri.

Selain itu, UpdateNode menawarkan juga menampilkan pesan melalui sistem - cukup berguna jika Anda ingin memberi tahu sesuatu yang penting bagi pengguna Anda.

Saya adalah bagian dari tim dev klien dan saya menggunakan setidaknya fungsi pesan untuk Aplikasi Android saya sendiri.

Lihat di sini deskripsi cara mengintegrasikan API

sarahara
sumber
ada beberapa masalah dengan situs web. Saya tidak bisa mendapatkan api_key dan saya tidak bisa melanjutkan pendaftaran.
infinite_loop_
Hai @infinite_loop_ Anda dapat menemukan kunci API di dalam bagian akun pengguna Anda: updatenode.com/profile/view_keys
sarahara
0

Coba ini - Tulis di Manifest:

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

Tulis Kode:

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
sumber
1
Anda tidak memerlukan izin sistem saja untuk meluncurkan aktivitas Penginstal Paket
Clocker