【问题标题】:Android okhttp image upload causes java.lang.OutofMemoryErrorAndroid okhttp 图片上传导致 java.lang.OutofMemoryError
【发布时间】:2019-03-24 02:21:56
【问题描述】:

我正在开发一个 Android 应用程序,用户可以在其中报告问题(上传为 json 数据)并将图像文件(连接到问题的那些文件)上传到服务器。如果应用程序具有在线连接,则它可以正常工作。不幸的是,这对我来说还不够,它必须具有保存这些图像并稍后在手机恢复在线时上传它们的能力。

所以,我创建了一个不断尝试上传数据的服务 (我知道这不是让它全部工作的最佳结构,但这是我能想出的唯一解决方案,我是整个编程内容的初学者) 当第一个请求失败时。这就是问题所在,它适用于数据,但不适用于图像,在我保存图像几秒钟后应用程序崩溃,给我留下了java.lang.OutOfMemoryError

我使用 okhttp 2.5 进行连接,并使用 sqlite 空间来存储数据。

这里是感兴趣的代码:

ErrorActivity 类:

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

               }
           });
       }

    }

上传服务

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.");
                       }
                   }
               });

}

有什么建议可以避免来自 okhttp 调度程序的 outofmemoryerror 吗?任何帮助表示赞赏。

【问题讨论】:

    标签: android android-sqlite out-of-memory image-uploading okhttp


    【解决方案1】:

    您无需每次都尝试发送数据。但是您可以创建一个广播接收器来收听手机上所需的事件并采取相应的行动。例如,跟踪手机的连接性并仅在手机连接时发送数据。

    以下链接可以帮助您实施解决方案:

    Receiver element

    BroadcastReceiver

    StackOverFlow 的这篇文章谈到了聆听 NETWORK CHANGE

    Broadcast receiver for checking internet connection in android app

    我认为这将对您的项目有所帮助。不要犹豫,在 cmets 中要求澄清,但请尝试。好编码

    【讨论】:

    • 您好,感谢您的回答。我会试试看,你的解决方案很简单。
    • @Newbie1001 考虑接受答案以帮助未来的读者
    • 还有一个问题:我应该从接收方调用服务吗?将其集成到广播接收器中被认为是一个坏主意,因为此操作通常是一个长时间运行的任务,并且系统可以在 OnReceive() 之后终止线程以回收内存。 (我必须访问数据库、获取数据并上传)如果没有,我还有哪些其他选择?
    猜你喜欢
    • 2016-06-07
    • 1970-01-01
    • 2018-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-29
    • 1970-01-01
    相关资源
    最近更新 更多