1

I am developing an Android application, where users can report problems (upload as json data) and upload image files(those files connected to a problem) to a server. The application works fine if it has online connection. Unfortunately, that is not enough for me, It must have the capability of saving those images and upload them later when the mobile is back online.

So, I created a service which continuously tries to upload datas (I know this is not the best construction to make it all work, but this is the only solution I could came up with, I am quite a beginner in this whole programming stuff) when the first request fails. And here is the problem, it works when it comes to datas, but not in case of images, a few seconds after I save the image the app crashes, leaving me a java.lang.OutOfMemoryError.

I am using okhttp 2.5 for connection and room over sqlite to store data.

Here is the code of interest:

ErrorActivity class:

    public class ErrorActivity extends AppCompatActivity {

        private static final int REQUEST_PICK_IMAGE = 3;
        UploadService mUploadservice;
        boolean mBound = false;
        //... Giving permissions, other variables...

        @Override
        protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_error);
           Button btnReport = findViewById(R.id.btnReport);

           btnReport.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View v) {
               try {
                   reportdefiency();                
               } catch (JSONException e) {
                  e.printStackTrace();
               }
              }
           });

           //...
         }

         @Override
         protected void onStart() {
             super.onStart();
             Intent intent = new Intent(ErrorActivity.this, 
                UploadService.class);
             bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
         }

         @Override
         protected void onStop() {
            super.onStop();
            unbindService(mConnection);
            mBound = false;
         }

          private ServiceConnection mConnection = new ServiceConnection() {
             @Override
             public void onServiceConnected(ComponentName name, IBinder 
               service) {
                UploadService.LocalBinder binder = 
                     (UploadService.LocalBinder) service;
                mUploadservice = binder.getService();
                mBound = true;
             }

             @Override
             public void onServiceDisconnected(ComponentName name) {
                mBound=false;
             }
         };

       public void reportdefiency(){
            //call static inner class AsyncTask, to avoid memory leak
            //and avoid UI unresponsiveness
            new ImgTask(this).execute();
            //...
       }

       private static class ImgTask extends AsyncTask<Void, Void, Void>{

           private WeakReference<ErrorActivity> activityWeakReference;

           ImgTask(ErrorActivity context){
                activityWeakReference = new WeakReference<>(context);
           }

           @Override
           protected Void doInBackground(Void...voids) {

               ErrorActivity activity = activityWeakReference.get();
               if(activity == null || activity.isFinishing()) return null;

               activity.uploadmultipleimages();

               return null;
           }
       }

       public void uploadmultipleimages(){

            // check if any images are picked to upload and so on...
            // create multipart body part ...
            // ..................... 

            OkHttpClient okHttpClient = new OkHttpClient();
            okHttpClient.setConnectTimeout(30, TimeUnit.SECONDS);
            okHttpClient.setReadTimeout(30, TimeUnit.SECONDS);
            //create the requestbody
            RequestBody rb =multipartBuilder
            .type(MultipartBuilder.FORM)
            .build();

            Request request = new Request.Builder()
            .url("http://blabla/bla/api"
                    +suburlblabla).post(rb).build();
            }


           //new variables, because inner
           // class needs final fields
           final List<byte[]> byteslist = fileinbytes;

           //if fails -> save it and start service
           okHttpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Request request, IOException e) {
                     //a list contains the image to save
                     final List<OffImage> offImageList = new 
                         ArrayList<>();
                     File[] files = new File[pictureUriArray.size()];
                     for(int i=0; i<pictureUriArray.size();i++){
                         //convert and store in Offimage class
                         //which is also an @Entity in SQLite
                         //adding files to list, checked->works...
                     }
                     // DB = SQLite database, singleton pattern, insert 
                     // images to table    
                     DB.getInstance(getApplicationContext())
                       .daoAccess().insertoffimages(offImageList);

                     //if activity bounded to service, upload it...
                     if(mBound){
                        mUploadservice.uploaddefimagedata();
                     }

               }
           });
       }

    }

UploadService

public class UploadService extends Service {

     // Users can save images  multiple times on Erroractivity
     // this int will register the number of calls from activity.
     // Each call will be a request in an infinite loop to upload 
     // the images. But a change in this counter will indicate
     // okhttp call to end the loop and start another, because more
     // images added to the queue list, hence must query sqlite
     // again and create another request with the refreshed list
     // images in byte[] format 
     private static int defimagejobcounter = 0;

     public void uploaddefimagedata(){
              List<OffImage> offImageList = 
                        DB.getInstance(getApplicationContext())
                       .daoAccess()
                       .selectalloffimages();

              if(offImageList!=null){
                  if(offImageList.size()>0){
                      // increase the counter to indicate to a previous call
                      // to abandon its operation (See later)
                      defimagejobcounter++;
                      createmultipartrequests(offImageList);
                  }
              }
     }

     private void createmultipartrequests(List<Offimage> listoflists){
        // for cycle to create each request
        for(int i = 0; listoflists.size(); i++){
             //creating requestbody, multipart, etc....
              uploadeqimage(requestBody, listoflists.get(0).getEqID(), 
                    defimagejobcounter);
        }
     }

     private void uploadeqimage(final RequestBody requestBody, final int 
          eqid, final int oldcounter){

           OkHttpClient okHttpClient = new OkHttpClient();
           okHttpClient.setConnectTimeout(20, TimeUnit.SECONDS);
           okHttpClient.setReadTimeout(20, TimeUnit.SECONDS);

           Request request = new 
            Request.Builder().url("http://blabla/bla/api"
                +suburlblabla).post(requestBody).build();

           okHttpClient.newCall(request).enqueue(new Callback() {
                 @Override
                 public void onFailure(Request request, IOException e) {
                      // retry if request fails, AND no other calls yet
                      // from Activity 
                       if(defiencyimagejobcounter==oldcounter){
                           uploadeqimage(requestBody, eqid, oldcounter);
                       }
                 }

                 @Override
                 public void onResponse(Response response){

                       if(response.isSuccessful()){                
                              DB.getInstance(getApplicationContext())
                              .daoAccess().deletealleqimages();
                              defimagejobcounter = 0;
                             Log.d(TAG, "Image(s) uploaded.");
                       }
                   }
               });

}

Any suggestions how to avoid outofmemoryerror from okhttp dispatcher? Any help appreciated.

Gratien Asimbahwe
  • 1,606
  • 4
  • 19
  • 30
Newbie1001
  • 131
  • 11

1 Answers1

2

You don't need to try sending data every time. But you can create a Broadcast Receiver to listen to desired events on the phone and act accordingly. For example track the connectivity of the phone and send data only if the phone is connected.

Links below can help you on implementing the solution:

Receiver element

BroadcastReceiver

And this post from StackOverFlow talks about listening to NETWORK CHANGE

Broadcast receiver for checking internet connection in android app

I Think this will help you in your project. Do not hesitate to ask for clarifications in comments but try. Good coding

Gratien Asimbahwe
  • 1,606
  • 4
  • 19
  • 30
  • Hi, thanks for your answer. I will try it, your solution is straightforward. – Newbie1001 Oct 19 '18 at 08:54
  • @Newbie1001 consider accepting an answer to help future readers – Gratien Asimbahwe Oct 19 '18 at 08:55
  • One more question : should I call the service from the receiver? Integrate it into the broadcastreceiver considered as a bad idea because this operation is typically a long running task and the system can kill thread after OnReceive() to reclaim memory. (I must access the database, take the data, and upload it) If not, what other options do I have? – Newbie1001 Oct 24 '18 at 14:01