İan Boddison
İan Boddison

Reputation: 367

Updating an APK

We have an internal app that checks for updates when launched. It is not released on Play Store because it is for internal use only.

The update code below was working fine on my test device (API 23). However I have recently released an update which downloads on the test device but reports:

Parse Error - There was a problem while parsing the package

On my own device (API 24) the app downloads the app and then crashes.

public class UpdateApp extends AsyncTask<String,Void,Void> {
    private Context context;
    public void setContext(Context contextf){
        context = contextf;
    }

    @Override
    protected Void doInBackground(String... arg0) {
        try {
            URL url = new URL(arg0[0]);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setDoOutput(true);
            conn.connect();

            File file = context.getCacheDir();
            file.mkdirs();
            outputFile = new File(file, "update.apk");
            if(outputFile.exists()){
                outputFile.delete();
            }
            FileOutputStream fos = new FileOutputStream(outputFile);

            InputStream is = conn.getInputStream();

            byte[] buffer = new byte[1024];
            int len1 = 0;
            while ((len1 = is.read(buffer)) != -1) {
                fos.write(buffer, 0, len1);
            }
            fos.close();
            is.close();
            outputFile.setReadable(true, false);

            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setDataAndType(Uri.fromFile(outputFile), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent);

        } catch (Throwable ex) {
            Toast.makeText(context, ex.toString(), Toast.LENGTH_LONG).show();
        }
        return null;
    }

On both devices the updated APK is downloaded before either throwing an error (API 23) or crashing (API 24). I do not understand how the app can just crash as all the code is in a try block which has a catch for the Throwable class so it should catch all errors and exceptions!

The following is the logcat output from my API 24 device whilst updating after the file is downloaded. Other than pointing to the problem being with the AysncTask class, I cannot work out anything useful from this.

2018-11-04 16:47:27.593 966-1017/? E/PROXIMITY: ProximitySensor: unknown event (type=3, code=0)
2018-11-04 16:47:27.704 23431-23476/uk.co.letsdelight.letsdelight E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
    Process: uk.co.letsdelight.letsdelight, PID: 23431
    java.lang.RuntimeException: An error occurred while executing doInBackground()
        at android.os.AsyncTask$3.done(AsyncTask.java:318)
        at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
        at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
        at java.util.concurrent.FutureTask.run(FutureTask.java:242)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
        at java.lang.Thread.run(Thread.java:761)
     Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:208)
        at android.os.Handler.<init>(Handler.java:122)
        at android.widget.Toast$TN.<init>(Toast.java:351)
        at android.widget.Toast.<init>(Toast.java:106)
        at android.widget.Toast.makeText(Toast.java:265)
        at uk.co.letsdelight.letsdelight.Update$UpdateApp.doInBackground(Update.java:123)
        at uk.co.letsdelight.letsdelight.Update$UpdateApp.doInBackground(Update.java:83)
        at android.os.AsyncTask$2.call(AsyncTask.java:304)
        at java.util.concurrent.FutureTask.run(FutureTask.java:237)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) 
        at java.lang.Thread.run(Thread.java:761) 
2018-11-04 16:47:27.716 966-989/? W/ActivityManager:   Force finishing activity uk.co.letsdelight.letsdelight/.MainActivity
2018-11-04 16:47:27.745 966-23481/? W/AES: Exception Log handling...

What else can I do to try to resolve this problem and perhaps make the code more robust so that we do not have issues with further updates?

Upvotes: 0

Views: 124

Answers (2)

Amin
Amin

Reputation: 3186

Sometimes java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() happens in AsyncTasks because you are calling execute in a background thread. you can attach a debugger and check if you are executing this asyncTask in background or not. you can use Evaluator to get value of Thread.currentThread().getId() if it returns 1 it means you are on the main thread if not, try to call execute in main thread of your app

Upvotes: 0

CommonsWare
CommonsWare

Reputation: 1006564

Step #1: Replace your Toast with something else. You cannot show a Toast on a background thread, which is what the error message is telling you (Can't create handler inside thread that has not called Looper.prepare()). A typical solution, given that you are still using AsyncTask, is to hold onto the Exception in a field in the AsyncTask, then do something with it in onPostExecute(), as that is executed on the main application thread.

Step #2: Always log exceptions. At minimum, you should be logging to LogCat. If you have integrated ACRA, Crashlytics, or similar solutions, you should be taking steps to ensure that your exceptions get reported there as well. Right now, your catch block is only attempting to show a Toast, which means you have no way of knowing what the specific problem is (e.g., lack of connectivity).

Step #3: One certain problem on Android 7.0+ is that you cannot use Uri.fromFile() very well, as file Uri values are banned. On those devices, use FileProvider to make the APK available to the installer, and use FileProvider.getUriForFile() to get the Uri to put in your Intent. Unfortunately, on Android 6.0 and older devices, you cannot use FileProvider, as the installer will not support content Uri values. So, you will need to detect your version (Build.VERSION.SDK_INT) and use the right Uri based on version.

Upvotes: 1

Related Questions