I'm working in an application which must send a GPS location every 5 seconds to the server when I choose (auto send button on). I'm new with android so I don't know how I can make an on/off button and how can I invoke the method that sends the data every 5 seconds when the button is on.
The method which it must invoke every 5 seconds:
public void postData() throws ClientProtocolException, IOException, Exception {
String longitude="UK";
String latitude="UK";
String altitiude="UK";
String time="";
String speed="";
getCurrentLocation(); // gets the current location and update mobileLocation variables
if (mobileLocation != null) {
locManager.removeUpdates(locListener); // This needs to stop getting the location data and save the battery power.
longitude = ""+mobileLocation.getLongitude();
latitude = "" + mobileLocation.getLatitude();
altitiude = "" + mobileLocation.getAltitude();
String accuracy = "Accuracy" + mobileLocation.getAccuracy();
time = "" + mobileLocation.getTime();
speed =""+ (int)(4*mobileLocation.getSpeed());
editTextShowLocation.setText(longitude + "\n" + latitude + "\n"
+ altitiude + "\n" + accuracy + "\n" + time+ "\n" + speed);
} else {
editTextShowLocation.setText("Sorry, location is not determined");
}
String url = "http://www.itrack.somee.com/post.aspx?id="+"f1"+"&long="+longitude+"&lat="+latitude+"&alt="+altitiude+"&speed="+speed;
// Create a new HttpClient and Post Header
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
try {
// Execute HTTP Post Request
HttpResponse response = httpclient.execute(httppost);
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
} catch (IOException e) {
// TODO Auto-generated catch block
}
}
I have faced exactly the same problem, sending location periodically. I've used a handler and its postDelayed method.
The periodic call part of my code looks like this:
private final int FIVE_SECONDS = 5000;
public void scheduleSendLocation() {
handler.postDelayed(new Runnable() {
public void run() {
sendLocation(); // this method will contain your almost-finished HTTP calls
handler.postDelayed(this, FIVE_SECONDS);
}
}, FIVE_SECONDS);
}
Then you just need to call scheduleSendLocation when you want to start your period calls.
Ridcully is right, there is probably no reason to send the current location every 5 seconds. Here is the rational behind that:
You really only care about 2 things about the users location:
Where are they right now?
Have they moved since I got their first location?
So once you get a satisfactory initial location, you can just register to get callbacks whenever the users moves like this:
private LocationListener mLocationListener;
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLocationListener = new LocationListener() {
#Override
public void onLocationChanged(final Location location) {
updateLocation(location);
}
#Override
public void onStatusChanged(final String provider,
final int status, final Bundle extras) {
}
#Override
public void onProviderEnabled(final String provider) {
}
#Override
public void onProviderDisabled(final String provider) {
}
};
}
That being said, you could obviously do what these others have said and run the timer every 5 seconds. The thing is, most good locations take 10-20 seconds to run, so you might only want to run it in that interval. Also FYI, this WILL kill battery
Look at the AlarmManager class http://developer.android.com/reference/android/app/AlarmManager.html and particularly the setRepeating function. Its going to be a bit more complicated than you'd like though.
You can use a Timer. But I think it would be better if you only send the position of it has changed a certain distance from the last one you sent. This way you'd send way less data without losing any information.
Related
I am trying to build an app to replicate a video streamer.
I have encountered an issue that can't seem to solve.
The app is working fine when run in an emulator (tried a few emulated devices) but it is crashing 99% of the time in my Samsung android tablet running android 12L.
I have noticed that the app works 100% of the time, when commenting out the
bannerMoviesViewPager.setAdapter(bannerMoviesPagerAdapter);
I was hoping someone has some insight of why this could happen, and also, why does it not crash when running in an emulator.
Here are segments of code that I think might be important to share, but please let me know if more is needed (i am quite new at stack overflow)
private void setBannerMoviesPagerAdapter(List<BannerMovies> bannerMoviesList){
bannerMoviesViewPager = (ViewPager) findViewById(R.id.banner_viewPager);
bannerMoviesPagerAdapter = new BannerMoviesPagerAdapter(this, bannerMoviesList);
bannerMoviesViewPager.setAdapter(bannerMoviesPagerAdapter); // COMMENT THIS LINE AND IT WORKS
//tabLayout.setupWithViewPager(bannerMoviesViewPager);
Timer sliderTimer = new Timer();
sliderTimer.scheduleAtFixedRate(new AutoSlider(), 4000, 6000);
tabLayout.setupWithViewPager(bannerMoviesViewPager, true);
}
public void fetch_json_banner_list(){
System.out.println("Attempting to fetch JSON");
final String url = "http://*serverIP*:80/api/movie";
Request request = new Request.Builder().url(url).build();
OkHttpClient client = new OkHttpClient();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(#NonNull Call call, #NonNull IOException e) {
System.out.println("Failed to execute request");
}
#Override
public void onResponse(#NonNull Call call, #NonNull Response response) throws IOException {
bannerMoviesList = new ArrayList<>();
String body = response.body().string();
Gson gson = new GsonBuilder().create();
Type allFilmsType = new TypeToken<ArrayList<Film>>(){}.getType();
List<Film> allFilms = gson.fromJson(body, allFilmsType);
for(Film film : allFilms){
System.out.println(film.getArtwork());
System.out.println(film.getArtwork().equals("https://media.movieassets.com/static/images/items/movies/posters/216767680a8a72fff4a12c484c6ac589.jpg"));
bannerMoviesList.add(new BannerMovies(film.getMovieId(), film.getTitle(), film.getSynopsis(), film.getArtwork().trim(), "https://ia800306.us.archive.org/35/items/PopeyeAliBaba/PopeyeAliBaba_512kb.mp4"));
}
setBannerMoviesPagerAdapter(bannerMoviesList);
}
});
}
public class BannerMoviesPagerAdapter extends PagerAdapter {
Context context;
List<BannerMovies> bannerMoviesList;
public BannerMoviesPagerAdapter(Context context, List<BannerMovies> bannerMoviesList) {
this.context = context;
this.bannerMoviesList = bannerMoviesList;
System.out.println("GETS HERE....");
}
#Override
public int getCount() {
return bannerMoviesList.size();
}
Also, here is the last part of the logcat for the process..
enter image description here
I would very much appreciate any help
Thanks
I double checked if I was reading / using the response body more than once.
As #zaitsman pointed out, the problem was being on the background thread while trying to modify the UI.
I solved it by changing:
setBannerMoviesPagerAdapter(bannerMoviesList);
to the following:
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
setBannerMoviesPagerAdapter(bannerMoviesList);
}
});
Change
setBannerMoviesPagerAdapter(bannerMoviesList);
To
new android.os.Handler(android.os.Looper.getMainLooper()).post({
setBannerMoviesPagerAdapter(bannerMoviesList);
});
Essentially you are trying to modify the UI (set adapter) from the background thread (that thread is owned by OKHttp).
You need to dispatch back to main thread.
The method I showed is universal and will work from anywhere.
Alternatively if you can find your activity you can do
getActivity().runOnUiThread({
setBannerMoviesPagerAdapter(bannerMoviesList);
});
I'm attempting to convert the code located at How to use signalr in android Service from java to c# and have been making some progress. I'm now stuck at the final method. The java code is:
private void startSignalR() {
Platform.loadPlatformComponent(new AndroidPlatformComponent());
mInstance.setmHubConnection();
mInstance.setHubProxy();
ClientTransport clientTransport = new ServerSentEventsTransport(mInstance.mHubConnection.getLogger());
SignalRFuture<Void> signalRFuture = mInstance.mHubConnection.start(clientTransport);
try {
signalRFuture.get();
} catch (InterruptedException | ExecutionException e) {
Log.e("SimpleSignalR", e.toString());
return;
}
mInstance.sendMessage(MainActivity.unm,"Hello All!");
String CLIENT_METHOD_BROADAST_MESSAGE = "recievedMessage";
mInstance.mHubProxy.on(CLIENT_METHOD_BROADAST_MESSAGE,
new SubscriptionHandler2<String,LoginInfo>() {
#Override
public void run(final String msg,final LoginInfo loginInfo) {
final String finalMsg = loginInfo.FullName + " says " + loginInfo.Password;
Intent intent = new Intent();
intent.setAction(MY_ACTION);
intent.putExtra("DATAPASSED", finalMsg);
sendBroadcast(intent);
}
}
, String.class,LoginInfo.class);
}
Using a java to c# converter, this translated to:
private void startSignalR()
{
Platform.loadPlatformComponent(new AndroidPlatformComponent());
mInstance.setmHubConnection();
mInstance.setHubProxy();
ClientTransport clientTransport = new ServerSentEventsTransport(mInstance.mHubConnection.Logger);
SignalRFuture<Void> signalRFuture = mInstance.mHubConnection.Start(clientTransport);
try
{
signalRFuture.get();
}
catch (Exception e) when (e is InterruptedException || e is ExecutionException)
{
// Log.e("SimpleSignalR", e.ToString());
return;
}
mInstance.sendMessage("", "Hello All!");
string CLIENT_METHOD_BROADAST_MESSAGE = "recievedMessage";
//String CLIENT_METHOD_BROADAST_MESSAGE = "messageReceived";
mInstance.mHubProxy.on(CLIENT_METHOD_BROADAST_MESSAGE, new SubscriptionHandler2AnonymousInnerClass(this)
, typeof(string), typeof(LoginInfo));
}
private class SubscriptionHandler2AnonymousInnerClass : SubscriptionHandler2<string, LoginInfo>
{
private readonly SignalRSrv outerInstance;
public SubscriptionHandler2AnonymousInnerClass(SignalRSrv outerInstance)
{
this.outerInstance = outerInstance;
}
//JAVA TO C# CONVERTER WARNING: 'final' parameters are not available in .NET:
//ORIGINAL LINE: #Override public void run(final String msg,final LoginInfo loginInfo)
public override void run(string msg, LoginInfo loginInfo)
{
//JAVA TO C# CONVERTER WARNING: The original Java variable was marked 'final':
//ORIGINAL LINE: final String finalMsg = loginInfo.FullName + " says " + loginInfo.Password;
string finalMsg = loginInfo.FullName + " says " + loginInfo.Password;
Intent intent = new Intent();
intent.Action = MY_ACTION;
intent.PutExtra("DATAPASSED", finalMsg);
sendBroadcast(intent);
}
}
This, of course, generated several errors in Visual Studio 2017.
First, the line Platform.loadPlatformComponent(new AndroidPlatformComponent()); generated the error Platform is inaccessible due to its protection level. Platform in Xamarin for Visual Studio 2017 is indeed protected and is a internal class in System and I cannot change this, so I'm at a loss as how to proceed with it. The same line generates the error The type or namespace name 'AndroidPlatformComponent' could not be found, these errors a numerous and not unexpected I just can't find an equivalent to AndroidPlatformComponent in Visual Studio 2017 so I'm at a loss as how to solve this one.
Next, on this line ClientTransport clientTransport = new ServerSentEventsTransport(mInstance.mHubConnection.Logger); generates the error The type or namespace name 'ClientTransport' could not be found, I was also unable to find an equivalent to this and again I'm at a loss as to proceed. Also on this line, .Logger is not defined for the hub connection, apparently it's .getLogger() in java, I was unable to find an equivalent for this one as well.
Next the line SignalRFuture<Void> signalRFuture = mInstance.mHubConnection.Start(clientTransport);' generates the error 1The type or namespace name 'SignalRFuture<>' could not be found, this seemes to be specific to SignalR, again, I am unable to find an equivalent.
The next one has me totally stumped, the line private class SubscriptionHandler2AnonymousInnerClass : SubscriptionHandler2<string, LoginInfo> generates the error The type or namespace name 'SubscriptionHandler2<,>' could not be found. I've looked everywhere online and read up on AnonymousInnerClass, but it was not help with this.
I'm hoping that the users here are more familiar with SignalR and the differences between c# functionality and java functionality. I'm not at all familiar with java nor am I familiar with SignalR and foreground services.
As it turns out, the last method in the java code I was converting was wiring up an event to pass the message received from the hub to the activity. In c# / Visual Studio (2017), that's done very differently which is why I didn't understand/recognize what was going on. So I created a handler in C# and execute a popup message for the message. This in itself may pose problems, but at least I know what's going on. This is the code I wrote to start SignalR from within the service and WireUp the handler:
private void startSignalR()
{
// Company, Department, and section are private variables
// their values are pulled from the intent bundle
// in the OnBind method, that code is:
// Bundle bundlee = intent.GetBundleExtra("TheBundle");
// MyUser = bundlee.GetParcelable("MyUser") as User;
// This information is put into the bundle when the user is logged in.
// I then pass that information to the SignalR client
// when I set the hub connection and placed on the querystring to the hub.
mInstance.setmHubConnection(username, firstname,lastname,company,department,section);
mInstance.setHubProxy();
try
{
// Connect the client to the hup
mInstance.mHubConnection.Start();
// Set the event handler
mInstance.WireUp();
}
catch (System.Exception e) when (e is InterruptedException || e is ExecutionException)
{
ShowMessage("Error: + e.Message)
}
}
This is the WireUp code, this is a method in the client code:
public void WireUp()
{
// set the event handler
mHubProxy.On("broadcastMessage", (string platform, string message) =>
{
if (OnMessageReceived != null)
OnMessageReceived(this, string.Format("{0}: {1}", platform, message));
});
}
As I had anticipated, the popup message won't appear when the app is in the background, so I'm researching a workaround
I have a strange issue, which I'm hoping I can explain well enough. My app has two activities - MainActivity and SearchActivity. I have a button on MainActivity which triggers an upload from the database on the device to a remote database on my web server. If I click the button when I first launch the app, no problem, works fine. If I switch to the SearchActivity, don't do anything, and switch back, then try the button, the app crashes with a ConcurrentModificationException.
I've got an AsyncTask which sends the contents of a local database (already pulled out of the database and sent to the thread through the parameters as an ArrayList). I've spent hours debugging this and still can't work out where it is. Any suggestions would be greatly appreciated.
This is the code triggered on the button press, to request the contents of the database from a separate Databaser thread
Button btnRemoteSync = (Button)findViewById(R.id.btnSync);
btnRemoteSync.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent startUpload = new Intent(getString(R.string.broadcast_search_database));
startUpload.putExtra("type-id",1);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(startUpload);
}
});
This is the code in the BroadcastReceiver which gets each response from the databaser and adds them to an ArrayList of custom ResponseObjects. When the databaser thread sends a bssid value of DONE, the AsyncTask is launched with the ArrayList passed in as a parameter.
#Override
public void onReceive(Context context, Intent intent) {
String bssid = intent.getStringExtra(getString(R.string.data_bssid));
if (bssid.equals("DONE")) {
RemoteDatabaseUploader rdb = new RemoteDatabaseUploader(getApplicationContext());
rdb.execute(databases);
} else {
databases.add(new ResponseObject(getApplicationContext(),
bssid,
intent.getStringExtra(getString(R.string.data_ssid)),
intent.getStringExtra(getString(R.string.data_capabilities)),
intent.getIntExtra(getString(R.string.data_level), 0),
intent.getIntExtra(getString(R.string.data_frequency), 0),
intent.getStringExtra(getString(R.string.data_timestamp)),
intent.getDoubleExtra(getString(R.string.data_latitude), 0),
intent.getDoubleExtra(getString(R.string.data_longitude), 0)));
}
}
Below is the doInBackground code for the AsyncTask
#Override
protected Integer doInBackground(ArrayList<ResponseObject>... params) {
ArrayList<ResponseObject> entries = params[0];
try {
URL url = new URL(insertURL);
for (Iterator<ResponseObject> it = entries.iterator(); it.hasNext();) {
ResponseObject ro = it.next(); // THIS IS WHERE THE EXCEPTION REFERENCES IN THE DEBUG OUTPUT
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("USER-AGENT", "Mozilla/5.0");
urlConnection.setRequestProperty("ACCEPT-LANGUAGE", "en-US,en;0.5");
urlConnection.setDoOutput(true);
String postParams = "bssid=" + ro.BSSID
+ "&ssid=" + ro.SSID
+ "&capabilities=" + ro.CAPABILITIES
+ "&level=" + String.valueOf(ro.LEVEL)
+ "&frequency=" + String.valueOf(ro.FREQUENCY)
+ "×tamp=" + ro.TIMESTAMP
+ "&lat=" + String.valueOf(ro.LAT)
+ "&long=" + String.valueOf(ro.LON);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes(postParams);
wr.flush();
wr.close();
Log.d("RemoteDatabase : ", "Post sent " + ro.BSSID + " || " + String.valueOf(urlConnection.getResponseCode()));
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
entries.clear();
return null;
}
EDIT -- I appear to have traced the issue down to another section of code, where a broadcast is sent on clicking the button. The button definitely only sends once (been checking using Log.d) but the received in the databaser is receiving it twice. Trying to fix this now.
It's been a while but realised I forgot to add how I solved this, just in case it is a help to anyone else.
I found the issue here was actually that some Android devices send multiple copies of a broadcast. I was using an HTC handset for testing and apparently for some reason they send 2 copies of all broadcasts. The way my code was working I was spawning a thread off a broadcast, which was resulting in 2 identical threads operating on the same data. When these completed they were each sending their "Done" broadcast, which was resulting in 4 of them being received by the main thread. Complete mess.
I ended up having to add a unique ID token to each broadcast and record the values at the receiving end, so if the same ID was received twice no action would be taken the second time.
Please insert a progress bar in your onPreExecute() method of async task and dismiss it in onPostExecute().I thing it is taking too much time to complete the async task,and you are tapping the button again prior to the completion of the async task.
I'm trying to wait the response of AdvertisingIdClient.getAdvertisingIdInfo(activity) without success. This method never response until the main thread has finished.
import android.app.Activity;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesUtil;
import java.io.IOException;
public class MyActivity extends Activity {
private Activity m_activity = null;
private AdvertisingIdClient.Info m_info = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// start the thread with the getAdvertisingIdInfo()
startGoogleAdvertisingIdRequest(this);
// simulate a waiting loop, others app init, ...
for (int i=0; i<20; i++) {
SystemClock.sleep(100);
}
// get the uuid
String uuid = getGoogleAdvertisingId();
// call a method who need the uuid
Log.i("UUID", "receive uuid: " + uuid);
}
public String getGoogleAdvertisingId() {
String uuid = null;
if (m_info != null) {
if (!m_info.isLimitAdTrackingEnabled()) {
uuid = m_info.getId();
} else {
uuid = "another uuid";
}
} else {
uuid = "another uuid";
}
return uuid;
}
public void startGoogleAdvertisingIdRequest(final Activity activity) {
m_activity = activity;
if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(activity) == ConnectionResult.SUCCESS) {
new Thread(new Runnable() {
#Override
public void run() {
AdvertisingIdClient.Info adInfo = null;
try {
Log.i("UUID", "before google request");
adInfo = AdvertisingIdClient.getAdvertisingIdInfo(activity);
Log.i("UUID", "after google request");
} catch (IOException e) {
Log.w("UUID", "getAdvertisingIdInfo IOException: " + e.getMessage());
} catch (GooglePlayServicesNotAvailableException e) {
Log.w("UUID", "GooglePlayServicesNotAvailableException: " + e.getMessage());
} catch (Exception e) {
Log.w("UUID", "GooglePlayServicesException: " + e.getMessage());
} finally {
finished(adInfo);
}
}
}).start();
}
}
private void finished(final AdvertisingIdClient.Info adInfo){
if(adInfo != null){
m_activity.runOnUiThread(new Runnable() {
#Override
public void run() {
m_info = adInfo;
Log.i("UUID", "runOnUiThread id: " + adInfo.getId());
}
});
}
}
}
Logcat of this code
11:29:52.103 30810-30828/com.example.testuuid I/UUID﹕ before google request
11:29:54.107 30810-30810/com.example.testuuid I/UUID﹕ receive uuid: another uuid
11:29:54.127 30810-30828/com.example.testuuid I/UUID﹕ after google request
11:29:54.151 30810-30810/com.example.testuuid I/UUID﹕ runOnUiThread id: d5dc3bfb-4756-490c-8f8e-2bedfb5e827a
Same logcat with more waiting time (5s)
11:36:14.215 31413-31436/com.example.testuuid I/UUID﹕ before google request
11:36:19.225 31413-31413/com.example.testuuid I/UUID﹕ receive uuid: another uuid
11:36:19.293 31413-31436/com.example.testuuid I/UUID﹕ after google request
11:36:19.315 31413-31413/com.example.testuuid I/UUID﹕ runOnUiThread id: d5dc3bfb-4756-490c-8f8e-2bedfb5e827a
Each time the getAdvertisingIdInfo(), who is in another thread, is blocked by the main thread.
What is the reason ? and how to do this ?
To get the google ad ID you need not to run the method getAdvertisingIdInfo on the main thread.
I use Async Task to manage the extraction of the google ad ID.
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
String GAID; // this is the String of the Google Ad ID that you'll receive upon onPostExecute
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new GetGAIDTask().execute();
}
private class GetGAIDTask extends AsyncTask<String, Integer, String> {
#Override
protected String doInBackground(String... strings) {
AdvertisingIdClient.Info adInfo;
adInfo = null;
try {
adInfo = AdvertisingIdClient.getAdvertisingIdInfo(MainActivity.this.getApplicationContext());
if (adInfo.isLimitAdTrackingEnabled()) // check if user has opted out of tracking
return "did not found GAID... sorry";
} catch (IOException e) {
e.printStackTrace();
} catch (GooglePlayServicesNotAvailableException e) {
e.printStackTrace();
} catch (GooglePlayServicesRepairableException e) {
e.printStackTrace();
}
return adInfo.getId();
}
#Override
protected void onPostExecute(String s) {
GAID = s;
}
}
You also need to add to the app build.gradle on the dependencies the line
compile 'com.google.android.gms:play-services-ads:7.8.0'
And be sure you have on the Android SDK manager the "EXTRAS Google Repository" updated
In the literature about AdvertisingIdClient it says to not use on main thread. It will throw an exception. So if you put it into it's own thread you will be fine, most likely.
AdvertisingIdClient reference
The issue you are seeing, where no amount of time seems long enough for the getAdvertisingIdInfo call to complete, is caused by how you are waiting and how runOnUiThread works. The key is that runOnUiThread will queue the code to be run after what is currently running on the ui thread, in this case the onCreate. The sleep calls used to "simulate waiting" will let your background thread run and do its work, but the final operation to set m_info will always be queued and executed after onCreate completes.
One solution would be to ensure that m_info is safe to access from multiple threads and simply assign it on the background thread. There would be no need for runOnUiThread. This would remove queuing, and allow your code to work with minimal changes.
A better solution would be to keep the use of runOnUiThread and remove the sleep used to wait. You would need to keep in mind that m_info will always be null in onCreate, but other events can check if the value is non null and use it as needed.
https://developer.android.com/reference/android/app/Activity#runOnUiThread(java.lang.Runnable)
Runs the specified action on the UI thread. If the current thread is the UI thread, then the action is executed immediately. If the current thread is not the UI thread, the action is posted to the event queue of the UI thread.
According to the docs (here) :
Unfortunately, in these cases using the com.google.android.gms.iid
InstanceID API or system functions to create an app-scoped ID are not
appropriate solutions because the ID may need to be shared across
apps. An alternative solution is to use the Advertising Identifier
available from the AdvertisingIdClient.Info class via the getId()
method. You can create an AdvertisingIdClient.Info object using the
getAdvertisingIdInfo(Context) method and call the getId() method to
use the identifier. Note that this method is blocking, so you should
not call it from the main thread; a detailed explanation of this
method is available here.
Also here:
public static AdvertisingIdClient.Info getAdvertisingIdInfo (Context
context)
Retrieves the user's advertising ID and limit ad tracking preference.
This method cannot be called in the main thread as it may block
leading to ANRs. An IllegalStateException will be thrown if this is
called on the main thread.
So they say it's blocking...
You need to put this code in a background thread.
I did a mistake that apparently can be solved only by uninstalling and then installing my app again.
I delivered a message to the users, but no-one seems to uninstall it.
AFAIK, if I change the certificate file, the play store won't let me upload the application, and
obviously I don't want to upload a new app.
Is there a way to force uninstall in order to update?
Thanks!
There's no killswitch to remotely force uninstalls (that'd be a security nightmare). What you can do is publish a fixed version on Google Play, and wait for users to upgrade.
I don't know if this can help you but i had the same problem. The solution for me is that i check the app version every time the user opens it and compare it with a version code stored on apache server (in a checkversion.php file).
If versions doesn't match, i show a not cancelable dialog that ask the user to go to market and download the update.
Here is an example (keep in mind that i use Volley library to handle connections):
public class UpdateManager {
private Activity ac;
private HashMap<String,String> params;
public UpdateManager(Activity ac) {
this.ac = ac;
}
public void checkForUpdates() {
Log.d("UpdateManager","checkForUpdates() - Started...");
params = new HashMap<String,String>();
params.put("request","checkforupdates");
try {
params.put("versioncode", String.valueOf(ac.getPackageManager().getPackageInfo(ac.getPackageName(), 0).versionCode));
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (Helper.isInternetAvailable(ac)) { //this is a class i made to check internet connection availability
checkAppVersion();
} else { Log.d("UpdateManager","CheckForUpdates(): Impossible to update version due to lack of connection"); }
}
private void checkAppVersion() {
Log.d("UpdateManager","checkAppVersion() - Request started...");
JsonObjectRequest req = new JsonObjectRequest("http://yourserver/checkappversion.php", new JSONObject(params),
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
if (response != null && response.has("result")) {
try {
Log.d("UpdateManager","checkAppVersion() - Request finished - Response: "+response.getString("result"));
if (response.getString("result").matches("updaterequested")) { //Update requested. Show the relative dialog
Log.d("UpdateManager","Update requested");
askUserForUpdate();
}
else if (response.getString("result").matches("current")) { //Same version. Do nothing
Log.d("UpdateManager","Version is up to date");
}
else if (response.getString("result").matches("error")) { //You can return an error message if error occurred on server
Log.d("UpdateManager","checkappversion Error - "+response.getString("error"));
}
VolleyLog.v("Response:%n %s", response.toString(4));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("UpdateManager","Volley Error - "+error.getMessage());
}
});
req.setRetryPolicy(new DefaultRetryPolicy(60000,0,DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
ConnectionController.getInstance().addToRequestQueue(req);
}
public void askUserForUpdate() {
final Dialog diag = new Dialog(ac);
diag.requestWindowFeature(Window.FEATURE_NO_TITLE);
diag.setContentView(R.layout.updatemanager_requestupdate_dialog);
diag.setCancelable(false);
diag.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
TextView t = (TextView)diag.findViewById(R.id.requestupdate_dialog_main_text);
ImageView im_ok = (ImageView)diag.findViewById(R.id.requestupdate_dialog_ok);
ImageView im_canc = (ImageView)diag.findViewById(R.id.requestupdate_dialog_canc);
t.setText(ac.getResources().getString(R.string.update_manager_askuserforupdate));
im_canc.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
diag.dismiss();
ac.finish();
}
});
im_ok.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("market://details?id="+ac.getPackageName()));
diag.dismiss();
ac.startActivity(intent);
ac.finish();
}
});
diag.show();
}
}
You can then use it when your main activity (or maybe login activity) starts like this:
UpdateManager updateManager = new UpdateManager(MainActivity.this); //i assume MainActicity as the calling activity
updateManager.checkForUpdates();
Obviously this has to be implemented into the application code so, the first time, you have to rely only on the user to manually upgrade it. But this can help if you have the same problem in the future.
This is an extract from my personal code so you have to rearrange it to your needings. Hope this helps someone.
Users should be able to go to Settings > Applications > Manage Applications and select the application to be removed. I've never seen a case where the application can't be removed this way, except in the case of built-in applications which require a rooted device to remove.