我正在构建我的第一个Android应用程序,但是遇到了问题。希望您能提供帮助。
应用程序使用AWS lambda函数从外部REST API中提取数据。它使用FragmentPagerAdapter通过水平滑动在多个片段上显示数据。每个API请求都会增加服务成本,因此需要磁盘缓存。 AsyncRequest的子类用于执行AWS lambda函数,该函数向相应的REST API发出请求,并将返回的JSON数据写入getCacheDir()中的缓存文件。
onStart()方法调用AsyncTask。 doInBackground()完成后,AsyncTask的onPostExecute()会将数据写入相应的缓存文件。 onResume()方法初始化FragmentPagerAdapter,它将打开缓存文件以读取数据并填充视图。 onStop()方法删除所有缓存的文件。
看起来FragmentPagerAdapter尝试在doInBackground()完成并触发onPostExecute()之前读取缓存文件,因此该文件尚不存在。但是,日志表明这仅发生在第一个lambda函数上。共有4个AWS lambda请求,这些请求导致将4个缓存文件写入磁盘。最初,我在onCreate()中有对AsyncTask的调用,也有在onCreate()中实例化FragmentPagerAdapter的逻辑,但是在AsyncTask调用之后。我希望通过将对AsyncTask的调用移到活动生命周期中的较早位置,可以解决此问题,但事实并非如此,并且我怀疑这是因为FragmentPagerAdapter试图在UI中加载缓存文件线程,而AsyncTask调用仍在后台线程中执行...
我想直接向AWS lambda函数发出HTTP请求,让用户等待它们全部完成并写入缓存(显示“正在加载...”),然后加载视图,但是我无法弄清楚如何使用Volley这样的标准HTTP库来实现AWS Lambda。
是否强制使用AsyncTask,我需要一种方法来确保在成功写入缓存文件之前,不要从中读取它们。
我希望应用程序专门使用缓存文件将数据加载到视图中。它仅应在打开应用程序时首先访问外部API,然后以一定间隔(30分钟?)定期运行,以确保如果用户保持打开时间足够长,则数据是最新的。
活动和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(文件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输出
该应用程序最终崩溃,因为在尝试读取缓存文件并从其中解析JSON时,它返回的是JsonNull而不是JsonElement。在我弄清楚这一点之前,我没有故意抓住异常。
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)
非常感谢您能提供的帮助!谢谢。