I have a simple Android app. The main activity looks for a file on the internal device storage. What happens after that depends on whether the file exists or not. This sequence works just fine as the whole execution is synchronous.
If I now use Google Drive API, all file access are asynchronous and the main activity goes on without waiting for the result of my search...
I found no way to 'force' a synchronous behavior. Synchronous calls of Google API are not allowed in the UI thread.
Moreover, performing such calls in an AsyncTask leads to the same problem...
Any idea on how to handle such scenarios ?
Regards,
Laurent
------------------------- EDIT ----------------------------------
I tried several options but they all lead to the same result.
Both the main activity and the asynctask used to look for my file get blocked.
Any idea why and what to do ?
Main activity code :
if (!TermsOfUseAgreementHandler.mTermsOfUseAgreed) {
Log.i(mTag, "Terms of use agreement needs to be checked");
mCountDownLatch = new CountDownLatch(1);
TermsOfUseAgreementSearchTask searchAgreementTask = new TermsOfUseAgreementHandler().new TermsOfUseAgreementSearchTask();
// Start the async task to look for agreement file using parallel execution
searchAgreementTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
try {
// Block the main thread so that the task gets a chance to perform its Google Drive access...
mCountDownLatch.await(); // MAIN ACTIVITY HANGS HERE !
} catch (InterruptedException e) {
Log.e(mTag, "Failed to look for user agreement file", e);
}
// The async task should have finished (and decreased mCountDownLatch so that await returns)
// At this moment, the result should be available for the main activity in mTermsOfUseAgreed...
if (!TermsOfUseAgreementHandler.mTermsOfUseAgreed)
TermsOfUseAgreementHandler.checkTermsOfUse(this);
}
AsyncTask code :
protected Boolean doInBackground(Void... params) {
Log.i(MainActivity.mTag, "Looking for user agreement file");
// Look for the agreement file with synchronous Google Drive API access
Query lookForAgreement = new Query.Builder().addFilter(Filters.eq(SearchableField.TITLE, mTermsOfUseAgreementFile)).build();
MetadataBuffer result = Drive.DriveApi.getAppFolder(MainActivity.mGoogleApiClient).queryChildren(MainActivity.mGoogleApiClient, lookForAgreement).await().getMetadataBuffer(); // ASYNCTASK HANGS HERE !
mTermsOfUseAgreed = result != null
&& result.getCount() != 0
&& result.get(0) != null
&& result.get(0).getTitle() != null
&& result.get(0).getTitle().equals(mTermsOfUseAgreementFile);
// Now that the result is available, decrease the countDownLatch so that the main activity may continue...
MainActivity.mCountDownLatch.countDown();
return mTermsOfUseAgreed;
}
------------------------- EDIT 2 ----------------------------------
I also tried to move the connection to the drive API from the main activity to the asynctask as shown in a drive API example link.
I still get the same result but the problem is now the connection because the associated callback listener is never called...
public class TermsOfUseAgreementSearchTask extends AsyncTask<Void, Void, Boolean> {
private GoogleApiClient mClient = null;
final private CountDownLatch latch = new CountDownLatch(1);
public TermsOfUseAgreementSearchTask(Context context) {
GoogleApiClient.Builder builder = new GoogleApiClient.Builder(context)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addScope(Drive.SCOPE_APPFOLDER);
mClient = builder.build();
mClient.registerConnectionCallbacks(new ConnectionCallbacks() {
#Override
public void onConnected(Bundle arg0) {
Log.i(MainActivity.mTag, "Connected");
latch.countDown();
}
#Override
public void onConnectionSuspended(int arg0) {}
});
mClient.registerConnectionFailedListener(new OnConnectionFailedListener() {
#Override
public void onConnectionFailed(ConnectionResult arg0) {
Log.i(MainActivity.mTag, "Connection failed");
latch.countDown();
}
});
}
#Override
protected Boolean doInBackground(Void... params) {
Log.i(MainActivity.mTag, "Connect to drive API");
mClient.connect();
try {
Log.i(MainActivity.mTag, "Wait for connection");
latch.await(); // ASYNC TASK HANGS HERE !
} catch (InterruptedException e) {
return false;
}
if (!mClient.isConnected()) {
return false;
}
try {
Log.i(MainActivity.mTag, "Looking for user agreement file");
// Look for the agreement file with synchronous Google Drive API access
Query lookForAgreement = new Query.Builder().addFilter(Filters.eq(SearchableField.TITLE, mTermsOfUseAgreementFile)).build();
MetadataBuffer result = Drive.DriveApi.getAppFolder(mClient).queryChildren(mClient, lookForAgreement).await().getMetadataBuffer();
mTermsOfUseAgreed = result != null
&& result.getCount() != 0
&& result.get(0) != null
&& result.get(0).getTitle() != null
&& result.get(0).getTitle().equals(mTermsOfUseAgreementFile);
// Now that the result is available, decrease the countDownLatch so that the main activity may continue...
//MainActivity.mCountDownLatch.countDown();
return mTermsOfUseAgreed;
} finally {
mClient.disconnect();
}
}
}
Related
In my android application I have a screen where I have 3 spinners that need to be
filled from APIs call.
static List<TripCode> tripCodeList = new ArrayList<>();
static List<Fleet> truckList = new ArrayList<>();
static List<Trailer> trailerList = new ArrayList<>();
And I don't want to inflate the layout unless I get the response from all the 3 different API calls so this is what I'm doing
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
if (MyApplication.isConnected()) {
getTripCodes();
} else {
Toast.makeText(this, "No internet Connection", Toast.LENGTH_LONG).show();
setContentView(R.layout.no_internet_connection);
}
}
Basically , I removed setContentView(R.layout.activity_create_trip);
from onCreate() And I called getTripCodes()
here's the code for getTripCodes()
public void getTripCodes() {
MyApplication.showProgressDialog(getString(R.string.please_wait), this);
IMyAPI iMyAPI = MyApplication.getIMyAPI();
Call<List<TripCode>> call = iMyAPI.getTripCodes();
call.enqueue(new Callback<List<TripCode>>() {
#Override
public void onResponse(Call<List<TripCode>> call, Response<List<TripCode>> response) {
if (response.isSuccessful() && response.body() != null) {
tripCodeList = response.body();
Log.d("test", "getTripCodes success = " + tripCodeList.size());
getTrucks();
} else {
MyApplication.dismissProgressDialog();
}
}
#Override
public void onFailure(Call<List<TripCode>> call, Throwable t) {
MyApplication.dismissProgressDialog();
}
});
}
So in the success of the call I'm calling the other function getTrucks() which also get result from API and in the success it will call getTrailers()
But I think it's a waste of time, because I can call the three function all together in parallel, and then check if all the list are filled or not.
But I don't know how to do it. How can I check if all the calls are success? And if one of them has failed, how will I know which one exactly failed?
I Believe for your problem you can easily use Retrofit 2.6.0 which has coroutine support and you can declare all the function's as suspended function's and dispatch them with async/launch dispatcher and if you want to wait for some result in some case use await() to wait for the result.
And use RxJava/liveData for responsive UI
sample code for you will look like
//maybe from Activity for ViewModel you can use ViewModelScope
GlobalScope.launch{
result1= async{ getTripCodes() }
result2= async{ getTrucks() }
result3= async{ getTrailers() }
doSomethingWithTripCodes(result1.await())
doSomethingWIthTrucks(result2.await())
doSomethingTrailers(result3.await())
}
Reference:
post1
I am querying POJO which is NOT being Observed / Non-Live data from an IntentService that was started in a PreferenceFragment. However a second my application crashes and log displays:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:204)
at android.arch.persistence.room.RoomDatabase.query(RoomDatabase.java:232)
at vault.dao.xxxDao_Impl.getAllNonLivePojoItems(xxxDao_Impl.java:231)
I want to know why is my program throwing this exception. as per https://stackoverflow.com/a/23935791/8623507
my database query[s] are inside an IntentService that runs In its own thread so i should be in the green. here is my code:
Inside IntentService
--------------------
// ERROR OCCURS HERE
List<POJO> pojoList = localRepo.getAllNonLivePojoItems(); // <= ERROR POINTS HERE
if (pojoList != null && pojoList.size() > 0) {
for (Pojo pojo : pojoList ){
// Do Long Running Task Here ....
}
Also I instantiate The Objects Being Used and call the above methods from those Objects Throughout the IntentService in OnHandleIntent like so:
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
LocalRepo localRepo = new LocalRepo(this.getApplication());
PojoHelper pojoHelper = new PojoHelper(this, localRepo);
if (LOGOUT.equals(action) && type != null) {
Log.d(TAG, "onHandleIntent: LOGOUT");
pojoHelper.logoutPojo();
}
else if(DELETE.equals(action) && type != null){
Log.d(TAG, "onHandleIntent: DELETE_POJO");
pojoHelper.deletePojo(true);
}
}
}
I assume you get callback from AsyncTask onPostExecute() method which runs on UI thread. It is prohibited to use database or network calls inside UI thread because it can block UI.
Execute your code where you access database inside new thread.
Example:
Executors.newSingleThreadExecutor().execute(()->{
//TODO access Database
});
One thing i failed to mention was that the method was being executed within an async's response callback method
PojoWarehouse.processPojoItems(new AsyncPojoCallback() {
#Override
public void done(Exception e) {
if (e == null) {
// ERROR OCCURS HERE
List<POJO> pojoList = localRepo.getAllNonLivePojoItems(); // <= ERROR POINTS HERE
if (pojoList != null && pojoList.size() > 0) {
for (Pojo pojo : pojoList ){
// Do Long Running Task Here ....
}
} else
Log.d(TAG, "done: Error Logging Out: " + e.getLocalizedMessage());
}
});
I cannot explain on a technical level why this fixed the issue, however suggestions are welcomed.
I'm trying to make what I thought would be a simple call to a google app engine project.
From a unit test, the call works fine if I access the api directly. I can't do this however, since the call is blocking, so it has to be run from an async task.
What seems to happen is:
A unit test inheriting from ActivityInstrumentationTestCase2 sets up the activity and populates some values
Then I click the button to make the request:
final Button mBet = (Button) mActivity.findViewById(R.id.mybutton);
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
mName.setText("blah");
mBet.performClick();
}
});
Then this calls the underlying code to start an async task:
public void bet() throws IOException, InterruptedException {
mMakeTask = new AsyncTask<Void, Void, Model>() {
#Override
protected void onPreExecute() {
super.onPreExecute();
mBetWait = new ProgressDialog(getActivity());
mBetWait.setCancelable(true);
mBetWait.show();
}
#Override
protected Model doInBackground(Void... voids) {
try {
mModel = mController.makeBet(mModel.getBetName(), mModel.getDescription(), mModel.getAnswer(), mModel.getAgainst());
} catch (IOException e) {
e.printStackTrace();
}
return mModel;
}
#Override
protected void onPostExecute(BetModel model) {
super.onPostExecute(model);
Context context = getActivity();
Intent intent = new Intent(context, OtherActivity.class);
intent.putExtra("bet", mModel);
mBetWait.dismiss();
context.startActivity(intent);
}
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
mMakeTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
mMakeTask.execute();
}
}
And I forgot to add... when controller is called, it boils down to an appengine call:
public BetInfo makeBet(BetRequest info) throws IOException {
Betrequestendpoint.Builder endpointBuilder = new Betrequestendpoint.Builder(
AndroidHttp.newCompatibleTransport(),
new JacksonFactory(),
new HttpRequestInitializer() {
public void initialize(HttpRequest httpRequest) { }
});
Betrequestendpoint endpoint = CloudEndpointUtils.updateBuilder(
endpointBuilder).build();
BetRequest result = endpoint.insertBetRequest(info).execute();
BetInfo resultInfo = new BetInfo();
resultInfo.setName(result.getName());
resultInfo.setDescription(result.getDescription());
resultInfo.setId(result.getId());
return resultInfo;
}
And to make this more odd... the request call appears to be blocked from the client with the stack trace:
"<13> AsyncTask #2"#831,912,607,944 in group "main": RUNNING
toUpperCase():3266, Character {java.lang}
toUpperCase():3251, Character {java.lang}
toUpperCase():162, CaseMapper {java.lang}
toUpperCase():1548, String {java.lang}
initServiceInfo():152, Services {org.apache.harmony.security.fortress}
getCacheVersion():211, Services {org.apache.harmony.security.fortress}
getInstance():137, Engine {org.apache.harmony.security.fortress}
getInstance():77, KeyManagerFactory {javax.net.ssl}
createDefaultKeyManager():362, SSLParametersImpl {com.android.org.conscrypt}
getDefaultKeyManager():355, SSLParametersImpl {com.android.org.conscrypt}
<init>():111, SSLParametersImpl {com.android.org.conscrypt}
getDefault():146, SSLParametersImpl {com.android.org.conscrypt}
<init>():34, OpenSSLSocketFactoryImpl {com.android.org.conscrypt}
newInstanceImpl():-1, Class {java.lang}
newInstance():1208, Class {java.lang}
getDefault():56, SSLSocketFactory {javax.net.ssl}
<clinit>():114, HttpsURLConnection$NoPreloadHolder {javax.net.ssl}
getDefaultSSLSocketFactory():163, HttpsURLConnection {javax.net.ssl}
copyWithDefaults():363, OkHttpClient {com.android.okhttp}
open():345, OkHttpClient {com.android.okhttp}
open():340, OkHttpClient {com.android.okhttp}
openConnection():28, HttpHandler {com.android.okhttp}
openConnection():479, URL {java.net}
buildRequest():133, NetHttpTransport {com.google.api.client.http.javanet}
buildRequest():68, NetHttpTransport {com.google.api.client.http.javanet}
execute():858, HttpRequest {com.google.api.client.http}
executeUnparsed():410, AbstractGoogleClientRequest {com.google.api.client.googleapis.services}
executeUnparsed():343, AbstractGoogleClientRequest {com.google.api.client.googleapis.services}
execute():460, AbstractGoogleClientRequest {com.google.api.client.googleapis.services}
makeBet():36, BetController {com.chillypixel.youwereright.controller}
makeBet():36, BetApiController {com.chillypixel.youwereright.controller}
doInBackground():129, BetFragment$1 {com.chillypixel.youwereright.bet}
doInBackground():117, BetFragment$1 {com.chillypixel.youwereright.bet}
call():288, AsyncTask$2 {android.os}
run():237, FutureTask {java.util.concurrent}
runWorker():1112, ThreadPoolExecutor {java.util.concurrent}
run():587, ThreadPoolExecutor$Worker {java.util.concurrent}
run():841, Thread {java.lang}
What seems to happen, is I run the test, and it gets to where the progress dialog pops up.
If I then manually interact with the app in the emulator (and hit back or something) the progress dialog goes away and the call completes - then the test passes.
If I just leave it alone though, it appears that the progress dialog just happily spins forever.
Any help would be greatly appreciated.
I'm using org.apache.commons.net.ftp.FTPClient to communicate with an ftp server via an android app I'm making that records video and then uploads it to the ftp server. Everything is fine until I call storeFile at which point the app prevents any interaction until the uploading is completed. Is there any way around this? I'm currently developing for API lvl 12.
My set up is as follows I have a class that calls a service in the background to handle recording of the video as well as the ftp setup. In the FTP class I have any of my fptclient interaction within asynctasks. Here is my method for uploading the file:
public boolean upload(String srcFilePath, String desFileName, String desDirectory)
{
if(!isLoggedIn())
{
return false;
}
boolean status = false;
try
{
status = new AsyncTask<String, Void, Boolean>(){
#Override
protected Boolean doInBackground(String... args) {
boolean status = false;
try {
String srcFilePath = args[0];
String desFileName = args[1];
String desDirectory = args[2];
FileInputStream srcFileStream = new FileInputStream(srcFilePath);
// change working directory to the destination directory
if (changeDirectory(desDirectory,true)) {
status = mFTPClient.storeFile(desFileName, srcFileStream);
}
srcFileStream.close();
return status;
} catch (Exception e) {
Log.d(TAG, "upload failed");
e.printStackTrace();
return false;
}
}
}.execute(srcFilePath, desFileName, desDirectory).get();
} catch (InterruptedException e)
{
e.printStackTrace();
return status;
} catch (ExecutionException e)
{
e.printStackTrace();
return status;
}
return status;
}
Any help would be immensely appreciated!
Devunwired's post worked! Hoorah!
You've started down the right path by placing this long-running task into a background thread using AsyncTask so that it doesn't block the UI. However, by immediately calling get() on the task after executing it, you effectively block the UI thread anyway because get() is a blocking method. From the SDK docs:
AsyncTask.get()
Waits if necessary for the computation to complete, and then retrieves its result.
What you should do is refactor your AsyncTask to make use of the onPostExecute() method. This method will be called when the background task is complete, with your result value as a parameter, and will be called on the UI thread so you can safely update anything you like with it.
HTH
I've run into this error before, but thought it was some mistake by the strict mode system. However, it apparently was right as I sadly found out now. :(
My programm is made of one Activity and loads of Fragments. I have a NetworkWorker fragment, which starts URL requests like this:
public void startURLRequest(Fragment target, String url, String message)
{
if (asyncTask != null) asyncTask.cancel(true);
asyncTask = new FragmentHttpHelper(url, message, target);
asyncTask.doInBackground();
return;
}
FragmentHttpHelper is a custom inner class derived from AsyncTask:
private class FragmentHttpHelper extends AsyncTask<Void, Void, String>
{
//...
#Override
protected String doInBackground(Void... params)
{
if (CheckInternet())
{
try
{
URL myURL = new URL(url);
httpClient = new DefaultHttpClient();
if (this.message == null)
{
httpRequest = new HttpGet(myURL.toExternalForm());
}
else
{
httpRequest = new HttpPost(myURL.toExternalForm());
HttpEntity myEntity = new StringEntity(message, "UTF-8");
((HttpPost) httpRequest).setEntity(myEntity);
}
// and so on...
}
//catches
finally
{
// auf jeden Fall Verbindung beenden
if (httpRequest != null) httpRequest.abort();
// if (httpClient != null) httpClient.close();
}
}
else
{
showDialog(getString(R.string.net_notify_no_network), target);
}
//...
}
/**
* gets called after AsyncTask has finished
*/
protected void onPostExecute(String result)
{
if (target == null)
{
((NetworkWorkerListener) getActivity()).onDownloadHasFinished((!result.contentEquals(ERROR)), result);
}
else
{
((NetworkWorkerListener) target).onDownloadHasFinished((!result.contentEquals(ERROR)), result);
}
}
}
NetworkWorkerListener is just an interface for a callback on the Fragment which started the URL request. This class has always worked fine when I used it in my 2.2 app. I would derive it in my Activities then.
Now, if a menu item is selected, another worker Fragment starts the URL request via the above method and opens a loading dialog:
FragmentManager fragmentManager = getFragmentManager();
NetworkWorker network = (NetworkWorker) fragmentManager.findFragmentByTag(TabletMain.NETWORK);
if (network == null) return WorkerFeedback.NO_NETWORK_WORKER;
myDialog = LoadingDialog.createInstance(getString(R.string.notify_download), this);
myDialog.show(fragmentManager, TabletMain.ONETIME);
network.startURLRequest(this, someurl, null);
At least that's what supposed to happen.
Instead, when I click the menu item, my app freezes and no loading dialog is shown until. Next happening is the reaction to the end of the download (or, in my case an error message, as I am sending nonsense strings). Meaning onPostExecute() was reached.
I feel really stuck now - is it not possible to use AsyncTask with Fragments? Or did I do something wrong?
Thanks for your help,
jellyfish
Don't call doInBackground directly, call execute instead (on the async task)