Жизненный цикл активности и AsyncRequest: попытка кэшировать данные лямбда-ответа AWS - PullRequest
0 голосов
/ 23 января 2019

Я создаю свое первое приложение для Android и столкнулся с проблемой.Надеемся, что вы можете помочь.

Приложение извлекает данные из внешних API REST, используя лямбда-функции AWS.Он использует FragmentPagerAdapter для отображения данных на нескольких фрагментах с помощью горизонтального пролистывания.Каждый запрос API увеличивает стоимость сервиса, поэтому необходим дисковый кеш.Подкласс AsyncRequest используется для выполнения лямбда-функций AWS, которые выполняют запросы к соответствующему API REST и записывают возвращенные данные JSON в файл кэша в getCacheDir ().

Метод onStart () выполняет вызов AsyncTask.OnPostExecute () AsyncTask записывает данные в соответствующий файл кэша после завершения doInBackground ().Метод onResume () инициализирует FragmentPagerAdapter, который открывает файлы кэша для чтения данных и заполнения представления.Метод onStop () удаляет все кэшированные файлы.

Похоже, FragmentPagerAdapter пытается прочитать файл кэша до того, как doInBackground () завершит работу, и вызовет onPostExecute (), поэтому файл еще не существует;однако журналы указывают, что это происходит только с одной - первой - лямбда-функцией.Всего имеется 4 лямбда-запроса AWS, в результате которых на диск записывается 4 файла кэша.Первоначально у меня были вызовы AsyncTask в onCreate () и логика для создания экземпляра FragmentPagerAdapter в onCreate (), но после вызовов AsyncTask.Я надеялся, что, переместив вызовы в AsyncTask на более раннюю точку жизненного цикла активности, это устранит проблему, но это не так, и я подозреваю, что это связано с тем, что FragmentPagerAdapter пытается загрузить файл кэша в пользовательском интерфейсе.поток, в то время как вызовы AsyncTask все еще выполняются в фоновом потоке ...

Я хотел бы сделать HTTP-запросы непосредственно к лямбда-функциям AWS, заставить пользователя ждать, пока они все не будут завершены и записаны вкеш (покажите «загрузка ...»), затем загрузите представления, но я не могу понять, как реализовать лямбду AWS со стандартной библиотекой HTTP, такой как Volley.

Независимо от того, вынужден ли я использовать AsyncTask или нет, мне нужен способ гарантировать, что я не читаю из файлов кэша, пока они не будут успешно записаны.

Я хочу приложениеиспользовать файлы кэша исключительно для загрузки данных в представления.Первоначально он должен обращаться к внешним API только при открытии приложения, а затем периодически через некоторый интервал (30 минут?), Чтобы гарантировать актуальность данных, если пользователь сохраняет их открытыми достаточно долго.

Activity и FragmentPagerAdapter

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        if(savedInstanceState == null)
            verifyPermissions();

        setContentView(R.layout.activity_main);

        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onStart()
    {
        super.onStart();

        try
        {
            CognitoCachingCredentialsProvider credentials = Weather.this.getCredentialsProvider();
            LambdaInvokerFactory factory = Weather.this.getLambdaInvokerFactory(credentials);

            Location location = this.getLocation();
            Log.d("Successfully created Location object.");

            String lat = Double.toString(location.getLatitude());
            Log.i("Latitude: " + lat + ".");

            String lon = Double.toString(location.getLongitude());
            Log.i("Longitude: " + lon + ".");

            RequestParams params = new RequestParams(Double.toString(location.getLatitude()), Double.toString(location.getLongitude()));

            LambdaFunctions fxns = new LambdaFunctions();

            for(Field field : fxns.getClass().getDeclaredFields())
            {
                String name = field.getName();

                CacheController cache = new CacheController(this.getCacheDir());

                cache.setCacheFile(new File(cache.getCacheDir(), name));

                if(cache.exists())
                {
                    File cacheFile = cache.getCacheFile();

                    Log.i("Cache file exists: " + cacheFile.getAbsolutePath());

                    long lastModified = cacheFile.lastModified();

                    /**
                     * Cache file expired. Query API and rewrite.
                     */
                    if((System.currentTimeMillis() - lastModified) >= CACHE_LIFE_MILLIS)
                    {
                        Log.i("Cache file expired: " + cacheFile.getAbsolutePath());

                        RequestTemplate request = new RequestTemplate(factory);
                        Log.d("Successfully created RequestTemplate object for AWS Lambda function '" + name + ".'");

                        new AsyncRequest(request, field.getName(), getCacheDir()).execute(params);
                        Log.d("Successfully submitted AsyncRequest for AWS Lambda function '" + name + ".'");
                    }
                }
                else
                {
                    Log.i("Cache file does not exist for '" + name + "' data.");

                    RequestTemplate request = new RequestTemplate(factory);
                    Log.d("Successfully created RequestTemplate object for AWS Lambda function '" + name + ".'");

                    new AsyncRequest(request, field.getName(), getCacheDir()).execute(params);
                    Log.d("Successfully submitted AsyncRequest for AWS Lambda function '" + name + ".'");
                }
            }
        }
        catch (RuntimeException e)
        {
            Log.e("Failed to get lcoation from device.");
            Log.e(e.getMessage());
            Toast.makeText(this, e.toString(), Toast.LENGTH_SHORT);
        }
    }

    @Override
    protected void onResume()
    {
        ViewPager pager = findViewById(R.id.viewPager);
        pager.setAdapter(new WeatherPagerAdapter(getSupportFragmentManager(), this));

        super.onResume();
    }

    private void deleteFiles()
    {
        LambdaFunctions fxns = new LambdaFunctions();

        for (Field field : fxns.getClass().getDeclaredFields())
        {
            String name = field.getName();

            Log.i("Current lambda name: " + name);

            CacheController cache = new CacheController(this.getCacheDir(), name);

            try
            {
                cache.delete(name);
            }
            catch (NullPointerException e)
            {
                Log.e("Unable to locate cache file for AWS Lambda function '" + name + ".'");
            }
        }
    }

    @Override
    protected void onStop()
    {
        deleteFiles();

        super.onStop();

        Log.i("WeatherOutdoors paused.");
    }

    private class WeatherPagerAdapter extends FragmentPagerAdapter
    {
        private Activity activity;

        public WeatherPagerAdapter(FragmentManager fm, Activity activity)
        {
            super(fm);
            this.activity = activity;
        }

        @Override
        public Fragment getItem(int pos)
        {
            switch(pos)
            {
                case 0:
                Log.d("Returning Data Fragment...");
                return DataFragment.newInstance();

                case 1:

                    LambdaFunctions fxns = new LambdaFunctions();

                    for(Field field : fxns.getClass().getDeclaredFields())
                    {
                        String name = field.getName();

                        CacheController cache = new CacheController(this.activity.getCacheDir(), name);

                        if (cache.exists())
                        {
                            SummaryData data = new SummaryData();
                            String data = data.getSummaryData(getCacheDir());
                            Log.d("Loaded Summary data successfully.");

                            Log.d("Returning Summary Fragment...");
                            return SummaryFragment.newInstance(data);
                        }
                        else
                        {
                            Log.i("Cache file does not exist for '" + name + "' data.");
                        }
                    }

                default: return DataFragment.newInstance();
            }
        }

        @Override
        public int getCount()
        {
            return 2;
        }
    }

AsyncTask

    @Override
    protected String doInBackground(RequestParams... params)
    {
        WeatherInterface weatherInterface =
            AsyncRequest.this.getRequestTemplate().getLambdaFactory().build(WeatherInterface.class);

        try
        {
            String functionName = AsyncRequest.this.functionName;
            Log.v("Submitting request for " + functionName + ".");

            Gson gson = new GsonBuilder().setPrettyPrinting().create();

            String data = gson.toJson(AsyncRequest.this.getRequestTemplate().getLambdaResponse(weatherInterface, functionName, params));

            Log.v("Successfully obtained data from AWS Lambda '" + functionName + ".'");

            return data;
        }
        catch (LambdaFunctionException lfe)
        {
            Log.v("Failed to invoke AWS Lambda function '" + this.functionName + ".'");
            String exception = lfe.getMessage();
            Log.d(exception);
            Log.v(lfe.getStackTrace().toString());
            return exception;
        }
        catch (Exception e)
        {
            Log.e(e.getMessage());
            return e.getMessage();
        }
    }

    @Override
    protected void onPostExecute(String result)
    {
        /**
         * Save data to disk
         */
        CacheController cache = new CacheController(this.cacheDir);

        cache.write(this.functionName, result);

        Log.i("Wrote weather data for service '" + this.functionName + "' to cache.");
    }

CacheController (File IO)

    public boolean exists()
    {
        return new File(getCacheFile().getAbsolutePath()).exists();
    }

    public void write(String label, String data)
    {
        try
        {
            File cache = new File(this.cacheDir, label);
            File temp = new File("/sdcard/" + label);
            FileWriter writer = new FileWriter(cache);
            FileWriter tempWriter = new FileWriter(temp);
            writer.write(data);
            tempWriter.write(data);
            writer.close();
            tempWriter.close();

            Log.i("Cache file written: " + cache.getAbsolutePath());
            Log.i("Temp file written: " + cache.getAbsolutePath());
        }
        catch (IOException e)
        {
            Log.e(e.toString());
        }
    }

    public String read(String label)
    {
        File cache = new File(this.cacheDir, label);
        StringBuilder builder = new StringBuilder();

        try
        {
            BufferedReader buffer = new BufferedReader(new FileReader(cache));
            String line = buffer.readLine();

            while (line != null)
            {
                 builder.append(line).append("\n");
                 line = buffer.readLine();
            }

            Log.i("Cache file read: " + cache.getAbsolutePath());
        }
        catch (FileNotFoundException e)
        {
            Log.e(e.toString());
        }
        catch (IOException e)
        {
            Log.e(e.toString());
        }

        return  builder.toString();
    }

    public void delete(String label)
    {
        File cache = new File(this.cacheDir, label);

        String path = cache.getAbsolutePath();

        if(cache.exists())
        {
            if (cache.delete())
            {
                Log.i("Cache file deleted successfully: " + path);
            }
            else
            {
                Log.e("Failed to delete cache file: " + path);
            }
        }
        else
        {
            Log.i("Unable to delete - file does not exist: " + path);
        }
    }

Подробный вывод Logcat

Приложение, в конечном счете, аварийно завершает работу, так как оно возвращает JsonNull вместо JsonElement при попытке прочитать файл кэша и проанализировать JSON из него.Я намеренно не поймаю исключение, пока не выясню это.

2019-01-22 17:04:10.234 12407-12407/com.somesite.someapp D/CognitoCachingCredentialsProvider: Loading credentials from SharedPreferences
2019-01-22 17:04:10.245 12407-12407/com.somesite.someapp V/GPSCoordinates::getLocation(): Successfully instansiated LocationManager from Context.LOCATION_SERVICE.
2019-01-22 17:04:10.246 12407-12407/com.somesite.someapp V/GPSCoordinates::getLocation(): Set Accuraccy: ACCURACY_FINE
2019-01-22 17:04:10.246 12407-12407/com.somesite.someapp V/GPSCoordinates::getLocation(): Set Horizontal Accuraccy: ACCURACY_HIGH
2019-01-22 17:04:10.246 12407-12407/com.somesite.someapp V/GPSCoordinates::getLocation(): Set Vertical Accuraccy: ACCURACY_HIGH
2019-01-22 17:04:10.250 12407-12407/com.somesite.someapp V/GPSCoordinates::getLocation(): Successfully instansiated Location from device.
2019-01-22 17:04:10.250 12407-12407/com.somesite.someapp D/Activity::onStart(): Successfully created Location object.
2019-01-22 17:04:10.250 12407-12407/com.somesite.someapp I/Activity::onStart(): Latitude: 39.********.
2019-01-22 17:04:10.250 12407-12407/com.somesite.someapp I/Activity::onStart(): Longitude: -75.********.
2019-01-22 17:04:10.251 12407-12407/com.somesite.someapp I/Activity::onStart(): Cache file does not exist for 'lambda1' data.
2019-01-22 17:04:10.251 12407-12407/com.somesite.someapp D/Activity::onStart(): Successfully created RequestTemplate object for AWS Lambda function 'lambda1.'
2019-01-22 17:04:10.252 12407-12407/com.somesite.someapp D/Activity::onStart(): Successfully submitted AsyncRequest for AWS Lambda function 'lambda1.'
2019-01-22 17:04:10.253 12407-12407/com.somesite.someapp I/Activity::onStart(): Cache file exists: /data/user/0/com.somesite.someapp/cache/lambda2
2019-01-22 17:04:10.253 12407-12407/com.somesite.someapp I/Activity::onStart(): Cache file expired: /data/user/0/com.somesite.someapp/cache/lambda2
2019-01-22 17:04:10.253 12407-12407/com.somesite.someapp D/Activity::onStart(): Successfully created RequestTemplate object for AWS Lambda function 'lambda2.'
2019-01-22 17:04:10.253 12407-12407/com.somesite.someapp D/Activity::onStart(): Successfully submitted AsyncRequest for AWS Lambda function 'lambda2.'
2019-01-22 17:04:10.253 12407-12407/com.somesite.someapp I/Activity::onStart(): Cache file exists: /data/user/0/com.somesite.someapp/cache/lambda3
2019-01-22 17:04:10.254 12407-12407/com.somesite.someapp I/Activity::onStart(): Cache file expired: /data/user/0/com.somesite.someapp/cache/lambda3
2019-01-22 17:04:10.254 12407-12407/com.somesite.someapp D/Activity::onStart(): Successfully created RequestTemplate object for AWS Lambda function 'lambda3.'
2019-01-22 17:04:10.254 12407-12407/com.somesite.someapp D/Activity::onStart(): Successfully submitted AsyncRequest for AWS Lambda function 'lambda3.'
2019-01-22 17:04:10.254 12407-12407/com.somesite.someapp I/Activity::onStart(): Cache file exists: /data/user/0/com.somesite.someapp/cache/lambda4
2019-01-22 17:04:10.254 12407-12407/com.somesite.someapp I/Activity::onStart(): Cache file expired: /data/user/0/com.somesite.someapp/cache/lambda4
2019-01-22 17:04:10.255 12407-12407/com.somesite.someapp D/Activity::onStart(): Successfully created RequestTemplate object for AWS Lambda function 'lambda4.'
2019-01-22 17:04:10.255 12407-12407/com.somesite.someapp D/Activity::onStart(): Successfully submitted AsyncRequest for AWS Lambda function 'lambda4.'
2019-01-22 17:04:10.256 12407-12455/com.somesite.someapp V/AsyncRequest::doInBackground(): Submitting request for lambda1.
2019-01-22 17:04:10.257 12407-12455/com.somesite.someapp V/RequestTemplate::getLambdaResponse(): Returning lambda response for lambda1.
2019-01-22 17:04:10.284 12407-12407/com.somesite.someapp D/ViewRootImpl@ed43136[Activity]: ThreadedRenderer.create() translucent=false
2019-01-22 17:04:10.288 3698-4331/? D/WindowManager: openInputChannel mInputChannel: 2a6c060 com.somesite.someapp/com.somesite.someapp.Activity (server)
2019-01-22 17:04:10.291 12407-12455/com.somesite.someapp D/NetworkSecurityConfig: No Network Security Config specified, using platform default
2019-01-22 17:04:10.292 12407-12407/com.somesite.someapp D/InputTransport: Input channel constructed: fd=59
2019-01-22 17:04:10.292 12407-12407/com.somesite.someapp D/ViewRootImpl@ed43136[Activity]: setView = DecorView@d2ed16c[Activity] touchMode=true
2019-01-22 17:04:10.295 12407-12455/com.somesite.someapp I/System.out: (HTTPLog)-Static: isSBSettingEnabled false
2019-01-22 17:04:10.295 12407-12455/com.somesite.someapp I/System.out: (HTTPLog)-Static: isSBSettingEnabled false
2019-01-22 17:04:10.304 12407-12407/com.somesite.someapp D/ViewRootImpl@ed43136[Activity]: dispatchAttachedToWindow
2019-01-22 17:04:10.314 12407-12407/com.somesite.someapp D/Activity$ActivityPagerAdapter::getItem(): Returning Data Fragment...
2019-01-22 17:04:10.315 12407-12407/com.somesite.someapp V/CacheController::<init>(): /data/user/0/com.somesite.someapp/cache/lambda1
2019-01-22 17:04:10.316 12407-12407/com.somesite.someapp I/Activity$ActivityPagerAdapter::getItem(): Cache file does not exist for 'lambda1' data.
2019-01-22 17:04:10.316 12407-12407/com.somesite.someapp V/CacheController::<init>(): /data/user/0/com.somesite.someapp/cache/lambda2
2019-01-22 17:04:10.319 12407-12407/com.somesite.someapp E/CacheController::read(): java.io.FileNotFoundException: /data/user/0/com.somesite.someapp/cache/lambda1 (No such file or directory)
2019-01-22 17:04:10.319 12407-12407/com.somesite.someapp I/CacheController::read(): Cache file read: /data/user/0/com.somesite.someapp/cache/lambda3
2019-01-22 17:04:10.320 12407-12407/com.somesite.someapp I/CacheController::read(): Cache file read: /data/user/0/com.somesite.someapp/cache/lambda4
2019-01-22 17:04:10.321 12407-12407/com.somesite.someapp D/AndroidRuntime: Shutting down VM


    --------- beginning of crash
2019-01-22 17:04:10.322 12407-12407/com.somesite.someapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.somesite.someapp, PID: 12407
    java.lang.ClassCastException: com.google.gson.JsonNull cannot be cast to com.google.gson.JsonObject
        at com.somesite.someapp.lambda1.getSummaryData(lambda1.java:46)

Буду признателен за любую помощь, которую вы можете оказать!Спасибо.

...