0

I have an android app that shows the rss of my website and shows a notification when there is new content.

The problem: My app crashes sometimes, and this is what i can see in Google play Console:

java.lang.RuntimeException: 
  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.NullPointerException: 
  at com.mydomain.rss.notification.RssNotificationService$SearchForUpdates.doInBackground (RssNotificationService.java:100)
  at com.mydomain.rss.notification.RssNotificationService$SearchForUpdates.doInBackground (RssNotificationService.java:92)
  at android.os.AsyncTask$2.call (AsyncTask.java:304)
  at java.util.concurrent.FutureTask.run (FutureTask.java:237)

In android studio I see the following message:

This AsyncTask class should be static or leaks might occur (com.mydomain.rss.notification.RssNotificationService.SearchForUpdates)

The following is the java file I need to modify to fix the problem, but I'm not sure what I have to change to fix the problem:

package com.mydomain.rss.notification;

import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import com.mydomain.rss.FeedReaderActivity;
import com.mydomain.rss.R;
import com.mydomain.rss.SettingsActivity;
import com.mydomain.rss.cache.MainActivityContext;
import com.mydomain.rss.cache.RefreshFeed;
import com.mydomain.rss.parser.RSSItem;
import com.mydomain.rss.utility.RSSutility;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.text.Html;

/**
 * RSS Service Executer
 */

public class RssNotificationService extends Service {

    public static final int NOTIFICATION_ID = 1;
    //public static final String TITLE_ERROR = "Notify Error";

    Handler searchHandler = new Handler();
    Timer searchLoader = new Timer();

     @Override
     public void onCreate() {
         super.onCreate();
         boolean is_notification_on = getPrefrenceBoolean(SettingsActivity.NOTIFICATION, false);
         if (is_notification_on){
             new SearchForUpdates().execute();
         }
     }

     @Override
     public IBinder onBind(Intent intent) {
         return null;
     }

     @Override
     public void onRebind(Intent intent) {
     super.onRebind(intent);
     }

     @Override
     public boolean onUnbind(Intent intent) {
     return true;
     }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
       return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    public boolean getPrefrenceBoolean(String preference, boolean default_value){
        SharedPreferences sp = getSharedPreferences(this.getPackageName(), Context.MODE_PRIVATE);
        return sp.getBoolean (preference, default_value);
    }

    public void savePrefrenceString(String preference, String value){
        SharedPreferences.Editor editor = getSharedPreferences(this.getPackageName(), Context.MODE_PRIVATE).edit();
        editor.putString(preference, value);
        editor.apply();
    }
    public String getPrefrenceString(String preference, String default_value){
        SharedPreferences sp = getSharedPreferences(this.getPackageName(), Context.MODE_PRIVATE);
        return sp.getString(preference, default_value);
    }

    private class SearchForUpdates extends AsyncTask<String, Void, String> {

        @Override
        protected String doInBackground(String... params) {

                String lastest_title = getPrefrenceString ("lastTitle", "No feed");

                List<RSSItem> feeds = RSSutility.getRSSFeed(getString(R.string.rss_url));
                RSSItem feed = feeds.get(0);
                String title = feed.getTitle(); 

                if (lastest_title.equalsIgnoreCase(title)){
                    //no update
                }else{
                    showNotification(feed);
                    //store last title so next time will not notify for same rss
                    savePrefrenceString("lastTitle", title);
                }

            return "Executed";
        }

        @Override
        protected void onPostExecute(String result) { 

             boolean is_notification_on = getPrefrenceBoolean(SettingsActivity.NOTIFICATION, false);
             if (is_notification_on){
                //Reload Menu
                long SEARCH_FREQUENCY = Integer.valueOf(getString(R.string.frequency)) * 60 * 1000; 

                searchLoader = new Timer();
                searchLoader.schedule(new TimerTask(){
                    public void run() {
                            searchHandler.post(new Runnable(){
                                public void run(){
                                    new SearchForUpdates().execute();
                                    searchLoader.cancel();
                                }
                            });
                }}, SEARCH_FREQUENCY, SEARCH_FREQUENCY);
             }
        }

        @Override
        protected void onPreExecute() {}

        @Override
        protected void onProgressUpdate(Void... values) {}
    }

    private void showNotification(RSSItem feed) {

        Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_notification);  

        Uri alarmSound = null;
        boolean is_notification_sound_on = getPrefrenceBoolean(SettingsActivity.NOTIFICATION_SOUND, false);
        if (is_notification_sound_on){
            alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        }

        String html = feed.getDescription();
        html = html.replaceAll("<(.*?)\\>", ""); //Removes all items in brackets
        html = html.replaceAll("<(.*?)\\\n", ""); //Must be undeneath
        html = html.replaceFirst("(.*?)\\>", ""); //Removes any connected item to the last bracket
        html = html.replaceAll("&nbsp;", "");
        html = html.replaceAll("&amp;", "");
        html = html.replaceAll("%20", " ");
        html = html.replaceAll("%2C", ", ");
        html = html.replaceAll("%0A", "");

        String title = feed.getTitle();
        title = title.replaceAll("<(.*?)\\>", ""); //Removes all items in brackets
        title = title.replaceAll("<(.*?)\\\n", ""); //Must be undeneath
        title = title.replaceFirst("(.*?)\\>", ""); //Removes any connected item to the last bracket
        title = title.replaceAll("&nbsp;", "");
        title = title.replaceAll("&amp;", "");
        title = title.replaceAll("%20", " ");
        title = title.replaceAll("%2C", ", ");
        title = title.replaceAll("%0A", "");

        //String description = feed.getDescription();

        NotificationCompat.Builder builder = new NotificationCompat.Builder(
                this).setSmallIcon(R.drawable.ic_launcher).setLargeIcon(icon)
                .setContentTitle(feed.getTitle()).setContentText(html)
                .setAutoCancel(true)
                .setSound(alarmSound);

        NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle();  
        bigText.bigText(Html.fromHtml(html));  
        bigText.setBigContentTitle(title);  
        bigText.setSummaryText(Html.fromHtml(html));  
        builder.setStyle(bigText);

        Intent intent = new Intent(this, FeedReaderActivity.class);
        intent.putExtra("NOTIFICATION", true);
        intent.putExtra("title", feed.getTitle());
        intent.putExtra("description", feed.getDescription());
        intent.putExtra("link", feed.getLink());
        intent.putExtra("pubDate", feed.getPubdate());
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        builder.setContentIntent(contentIntent);

        // Send the notification to the system.
        NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(NOTIFICATION_ID, builder.build());

        if (MainActivityContext.instance().activity!=null){
            MainActivityContext.instance().activity.runOnUiThread(new Runnable(){
                @Override
                public void run() {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            Intent broadcast = new Intent();
                            broadcast.setAction("com.rss.RELOAD_URL");
                            sendBroadcast(broadcast);
                            RefreshFeed.instance().refresh = true;
                        }
                    }, 1000);
                }
            });
        }

    }

}

So, if you know how to solve my problem, please reply back with the modifications.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
sshcli
  • 11
  • 6
  • Perhaps your asynkTask calls onPostExecute when your activity or fragment already destroyed. You need to cancel your asyncTask when your context doesn't exists anymore.. – Maicon Hellmann Nov 22 '17 at 19:23

4 Answers4

2

in doing in background in your asyncTask List<RSSItem> feeds its valeo is null I so when you are accessing it feeds.get(0) causes a null pointer exception. your AsyncTask class should be static so you will get only one object of it.

beshoy samy
  • 144
  • 2
  • 8
0

To get rid of the "AsyncTask should be static" warning, you can...make your AsyncTask static. See my full answer for how to do that.

As was noted in the comments, you should cancel the updating the UI if the Activity is no longer around when the AsyncTask gets to onPostExecute. This very easily could happen if it takes a long time for your RSS feeds to load. Here is a code snippet showing how to cancel execution if the Activity is null.

@Override
protected void onPostExecute(String result) {

    // get a reference to the activity if it is still there
    MyActivity activity = activityReference.get();

    // if it is gone, then cancel the UI update
    if (activity == null) return;

    // update the UI ...
}
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
-1

AsyncTask's lifecycle depends on Activity's lifecycle. When activity destroyed you will get crash on async task. In your case it can be destroyed activity where you are using runonuithread. Or your service called onDestroy() snd async task leaked. It is same behavior of lifecycle as activity.

LifeStyle
  • 411
  • 2
  • 10
-1

Make your AsyncTask class static.

Vikash Sharma
  • 539
  • 8
  • 13