简体   繁体   中英

How do I programmatically download and install an apk?

I need to be able to push out updates to a private app (doesn't exist on Play Store) by checking for a newer version (via versionCode/versionName) of an apk hosted somewhere*.

I believe I've got all the code I need- connect to Dropbox, download the file, trigger broadcast receiver to check the version and install the app if newer than current is available.

My issue is that getPackageArchiveInfo always returns null on the downloaded apk.

Note: I am able to manually install the app after it has been downloaded (the file is not corrupt), but I need this process to be automated.

*using Dropbox in this example, but this will not be what prod uses.

I've used a few different resources to build and try to debug my issues, see:
Android: install .apk programmatically
Android install apk with Intent.VIEW_ACTION not working with File provider

The compileSdkVersion is 27, minSdkVersion and targetSdkVersion are 23. This configuration is crucial and cannot be changed.

I am primarily testing on an emulator set to the specific configuration that the production device will be using, but I also am using a Galaxy S6 to test.

MainActivity:

public void onCheckClick(View view) {
        try {
            String versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
            txtCurrent.setText("Current: " + versionName);

            String url = apkLocation;
            DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
            request.setDescription("description");
            request.setTitle("app-debug.apk");

            request.allowScanningByMediaScanner();
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);

            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "app-debug.apk");

            DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
            manager.enqueue(request);

        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    public void onUpdateClick(View view) {

        Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);

        installIntent.setDataAndType(Uri.parse("file:///storage/emulated/0/Download/app-debug.apk"), "application/vnd.android.package-archive");
        startActivity(installIntent);
    }

DownloadBroadcastReceiver:

public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)){
            //do stuff
            PackageManager pm = context.getPackageManager();
            String apkName = "app-debug.apk";
            String fullPath = Environment.DIRECTORY_DOWNLOADS + "/" + apkName;
            PackageInfo info = pm.getPackageArchiveInfo(fullPath, 0);

            Toast.makeText(context, info.packageName, Toast.LENGTH_LONG).show();
        }
    }

AndroidManifest:

<uses-permission android:name="android.permission.INTERNET" />
    <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
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver android:name=".DownloadBroadcastReceiver">
            <intent-filter>
                <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
            </intent-filter>
        </receiver>

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

Expected: Grab the version information from the downloaded apk, then be able to install that apk if it has a newer version.
Actual: Version information is null, and therefore cannot continue the process.

I had the same Problem. I just solved it. Here is my solution: (NB: I used FTP server here, Targated SDK 23)

AndroidManifest:

<provider
            android:name="android.support.v4.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>

Download Apk Class:

class DownloadNewVersion extends AsyncTask<String,Integer,Boolean> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            progressdlg = new ProgressDialog(DownloadApk.this);
            progressdlg.setCancelable(false);
            progressdlg.setMessage("Downloading...");
            progressdlg.setProgressStyle(progressdlg.STYLE_SPINNER);
            //progressdlg.setMax(100);
            progressdlg.setIndeterminate(true);
            progressdlg.setCanceledOnTouchOutside(false);
            progressdlg.show();


        }

        @Override
        protected void onPostExecute(Boolean result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            progressdlg.dismiss();
            if(result){
                Toast.makeText(DownloadApk.this,"Done!!", Toast.LENGTH_SHORT).show();
                
                OpenNewVersion(path);


            }else{
                Toast.makeText(DownloadApk.this,"Error: Server Connection Failed!", Toast.LENGTH_SHORT).show();
            }
        }
        @Override
        protected Boolean doInBackground(String... arg0) {
            Boolean flag = false;
            String sdcardRoot = Environment.getExternalStorageDirectory().getAbsolutePath();
            String apkSavePath = sdcardRoot+"/app-debug.apk";
            path=apkSavePath;
            try {
                

                String server = "103.121.78.28"; //Your download Link
                int port = 21;
                String user = "ftp";
                String pass = "";


                FTPClient ftpClient = new FTPClient();

                try {

                    ftpClient.connect(server, port);
                    ftpClient.login(user, pass);
                    ftpClient.enterLocalPassiveMode();
                    ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
                  


                    String remoteFile1 = "app-debug.apk";//apk name
                    File downloadFile1 = new File(apkSavePath);

                    OutputStream outputStream1 = new BufferedOutputStream(new FileOutputStream(downloadFile1));

                    boolean success = ftpClient.retrieveFile(remoteFile1, outputStream1);
                    outputStream1.close();
                    Log.d("Update", "doInBackground: "+"before if"+success);



                    if (success) {
                        Log.d("Update", "doInBackground: "+"Success"+success);
                       


                        

                        flag = true;
                        

                     

                    }






                } catch (IOException ex) {
                    progressdlg.dismiss();
                    System.out.println("Error: " + ex.getMessage());
                    flag = false;
                    ex.printStackTrace();
                } finally {
                    try {
                        if (ftpClient.isConnected()) {
                            ftpClient.logout();
                            ftpClient.disconnect();
                            progressdlg.dismiss();
                        }
                    } catch (IOException ex) {
                        progressdlg.dismiss();
                        ex.printStackTrace();
                        flag = false;
                    }
                }
            } catch (Exception e) {
                progressdlg.dismiss();
                Log.e("TAG", "Update Error: " + e.getMessage());
                e.printStackTrace();
                flag = false;
            }

            return flag;
        }
    }
    void OpenNewVersion(String location) {
        File apkFile = new File(location);
        Intent intent = new Intent(Intent.ACTION_VIEW);
            


        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri uri = FileProvider.getUriForFile(getApplicationContext(), getApplicationContext().getPackageName() + ".provider", apkFile);
            intent.setDataAndType(uri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
        }


        startActivity(intent);;
    }


    // Function to check and request permission.
    public void checkPermission(String permission, int requestCode)
    {
        if (ContextCompat.checkSelfPermission(DownloadApk.this, permission)
                == PackageManager.PERMISSION_DENIED) {

            // Requesting the permission
            ActivityCompat.requestPermissions(DownloadApk.this,
                    new String[] { permission },
                    requestCode);
        }
        else {
            Toast.makeText(DownloadApk.this,
                    "Permission already granted",
                    Toast.LENGTH_SHORT)
                    .show();
        }
    }

    // This function is called when the user accepts or decline the permission.
    // Request Code is used to check which permission called this function.
    // This request code is provided when the user is prompt for permission.

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super
                .onRequestPermissionsResult(requestCode,
                        permissions,
                        grantResults);


            if (requestCode == STORAGE_PERMISSION_CODE) {
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(DownloadApk.this,
                        "Storage Permission Granted",
                        Toast.LENGTH_SHORT)
                        .show();
            }
            else {
                Toast.makeText(DownloadApk.this,
                        "Storage Permission Denied",
                        Toast.LENGTH_SHORT)
                        .show();
            }
        }
    }

Call Download Apk class from any click listener:

new DownloadNewVersion ().execute();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM