I have an IntentService which is supposed to send thousands of Volley requests to my server. It works fine and is very fast. I create my requests based on a Cursor I get from a ContentProvider (getContentResolver()).
I do however want to avoid making requests for items which are already available client-side. I get this list of items by calling
List<List> savedLyrics = DatabaseHelper.getInstance(this).listMetadata();
If that list is empty, it works fine. However, if it's not - then when this method
savedLyrics.contains(Arrays.asList(artist, title))
gets called, it seems to really slow things down.
public class BatchDownloaderService extends IntentService implements Response.Listener<String>, Response.ErrorListener {
private int total = 0;
private int count = 0;
private int successCount = 0;
private RequestQueue requestQueue;
private OkHttpClient client = null;
public BatchDownloaderService() {
super("Batch Downloader Service");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
onHandleIntent(intent);
return START_NOT_STICKY;
}
#Override
protected void onHandleIntent(Intent intent) {
if (client == null)
getClient();
Uri content = intent.getExtras().getParcelable("uri");
List<List> savedLyrics = DatabaseHelper.getInstance(this).listMetadata();
Cursor cursor = /* Get My Cursor */;
if (cursor == null)
return;
total = cursor.getCount();
updateProgress();
final Cache cache = new DiskBasedCache(getCacheDir(), 1024*1024);
final Network network = new BasicNetwork(new OkHttp3Stack(client));
requestQueue = new RequestQueue(cache, network, 8);
requestQueue.start();
while (cursor.moveToNext()) {
String artist = cursor.getString(0);
String title = cursor.getString(1);
if (artist == null || title == null || artist.isEmpty() || title.isEmpty() || savedLyrics.contains(Arrays.asList(artist, title))) {
// If the local database already contains this item (or if null), skip the request
updateProgress();
continue;
}
try {
Request request = QuickLyricAPI.getVolleyRequest(lrc, this, this, artist, title);
requestQueue.add(request);
} catch (Exception e) {
// Stuff
}
}
cursor.close();
}
private void updateProgress() {
/* Update the progressbar in the notification */
}
#Override
public void onErrorResponse(VolleyError error) {
updateProgress();
error.printStackTrace();
}
#Override
public void onResponse(String response) {
/* Stuff */
updateProgress();
}
private void getClient() {
/* Stuff */
}
}
If I comment out the call to .contains(), then I see no difference with the list being empty and everything is smooth.
I tried to replace the list with a TreeSet() with a comparator, it didn't make it faster. I tried to use String arrays instead of Lists, it didn't make it faster. I also tried using a Countdownlatch to make it so that onHandleIntent() doesn't finish before all the requests were done. Didn't work.
How can an IntentService slow down the whole device and even crash my SystemUI like this? I thought it was running in its own thread.
How can checking a list (2500 items in this case) be so expensive?
Testing this on a Nextbit Robin with 7.0
Cheers
Related
my below sample code work fine without any some problem, this sample code can cache received data from url and using that when device doesnt have any internet connection.
but this code have a big deficiency, it is when device have internet connection i cant recache and get newest data from internet and recache again until device doesnt have connection, some data can be image or json array or json object
public class MainActivity extends AppCompatActivity {
private final Context mContext = this;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView textView = (TextView) findViewById(R.id.textView);
RequestQueue queue = Volley.newRequestQueue(this);
String url = "http://192.168.1.2/test";
CacheRequest cacheRequest =
new CacheRequest(0, url, new Response.Listener<NetworkResponse>() {
#Override
public void onResponse(NetworkResponse response) {
try {
final String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
JSONObject jsonObject = new JSONObject(jsonString);
textView.setText(jsonObject.toString(5));
Log.e('OutPut',jsonObject.toString());
} catch (UnsupportedEncodingException | JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(mContext, "onErrorResponse:\n\n" + error.toString(), Toast.LENGTH_SHORT).show();
}
});
queue.add(cacheRequest);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private class CacheRequest extends Request<NetworkResponse> {
private final Response.Listener<NetworkResponse> mListener;
private final Response.ErrorListener mErrorListener;
public CacheRequest(int method, String url, Response.Listener<NetworkResponse> listener, Response.ErrorListener errorListener) {
super(method, url, errorListener);
this.mListener = listener;
this.mErrorListener = errorListener;
}
#Override
protected Response<NetworkResponse> parseNetworkResponse(NetworkResponse response) {
Cache.Entry cacheEntry = HttpHeaderParser.parseCacheHeaders(response);
if (cacheEntry == null) {
cacheEntry = new Cache.Entry();
}
final long cacheHitButRefreshed = 3 * 60 * 1000; // in 3 minutes cache will be hit, but also refreshed on background
final long cacheExpired = 24 * 60 * 60 * 1000; // in 24 hours this cache entry expires completely
long now = System.currentTimeMillis();
final long softExpire = now + cacheHitButRefreshed;
final long ttl = now + cacheExpired;
cacheEntry.data = response.data;
cacheEntry.softTtl = softExpire;
cacheEntry.ttl = ttl;
String headerValue;
headerValue = response.headers.get("Date");
if (headerValue != null) {
cacheEntry.serverDate = HttpHeaderParser.parseDateAsEpoch(headerValue);
}
headerValue = response.headers.get("Last-Modified");
if (headerValue != null) {
cacheEntry.lastModified = HttpHeaderParser.parseDateAsEpoch(headerValue);
}
cacheEntry.responseHeaders = response.headers;
return Response.success(response, cacheEntry);
}
#Override
protected void deliverResponse(NetworkResponse response) {
mListener.onResponse(response);
}
#Override
protected VolleyError parseNetworkError(VolleyError volleyError) {
return super.parseNetworkError(volleyError);
}
#Override
public void deliverError(VolleyError error) {
mErrorListener.onErrorResponse(error);
}
}
}
It will cache only once because onCreate method is called once the app created..
most suitable solution would be this:
You need to have a Service which uses AlarmManager for, say, every 5 minutes to trigger itself. And in this service you can cache the newest data in the background, of course for smaller time unit, more battery will be consumed.
update for cache resizing
RequestQueue volleyQueue = Volley.newRequestQueue(this);
DiskBasedCache cache = new DiskBasedCache(getCacheDir(), 16 * 1024 * 1024);
volleyQueue = new RequestQueue(cache, new BasicNetwork(new HurlStack()));
volleyQueue.start();
according to that discussion volley's cache can be manipulated, but when it gets full, automatically replaces new data with the old one so, cache size is not the focus right now.
update for cache cleaning
mahdi, according to official link of google volley's Cache class, there is a method called invalidate(), which invalidates the cached data, and next time volley checks if data was valid and update it.
you can appereantly delete cache for every 30 minutes according to this discussion:
serverDate: AppController.getInstance().getRequestQueue().getCache().get(url).serverDate
getMinutesDifference is method calculates time passed, can be found on reference link.
Calendar calendar = Calendar.getInstance();
long serverDate = AppController.getInstance().getRequestQueue().getCache().get(url).serverDate;
if(getMinutesDifference(serverDate, calendar.getTimeInMillis()) >=30)
{
AppController.getInstance().getRequestQueue().getCache().invalidate(url, true);
}
You can first check your Internet connection, if you have connection you can clear the cache and reload
ConnectivityManager connMgr = (ConnectivityManager) getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
MySocialMediaSingleton.getInstance(getContext()).getRequestQueue().getCache().invalidate("http://server/json.php?page=1", true);
}
//Calling again method to get data to fetch data
getData();
if (isConnected()) {
Event eInstance = new Event();
theEvents = eInstance.downloadEvents(eventsNightlife, getActivity());
rAdapter = new RecyclerAdapter(theEvents);
recyclerView.setAdapter(rAdapter);
progrsBar.setVisibility(View.GONE);
....
This is part of the code that runs at "onCreateView". The method downloadEvents uses Volley to download JSON data, extract it and return a list of items (theEvents). Now when my app starts, the recycler view is empty. If I go to my home screen out of the app and then run my app again, this time the data sometimes gets downloaded.
I debugged step by step, and at first launch (i mean when the app is not just resuming), theEvents is empty, so the download didn't return or manage to return anything...
Suggestions on how to execute things before the UI has been shown to the user or what actually needs to be done to approach this task better?
Also, I use a swipeRefreshLayout and at its onRefresh method I do:
public void onRefresh() {
Event eInstance = new Event();
theEvents = eInstance.downloadEvents(eventsNightlife, getActivity());
rAdapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
}
but it doesn't work. I also tried to
rAdapter = new RecyclerAdapter(theEvents);
rAdapter.notifyDataSetChanged();
recyclerView.swapAdapter(rAdapter, false);
still not working.
EDIT: My downloadEvents method implementing Volley:
public List<Event> downloadEvents(String urlService, Context context) {
eventsList = new ArrayList<>();
RequestQueue requestQueue = Volley.newRequestQueue(context);
JsonArrayRequest jsonArrayRequest = new JsonArrayRequest
(Request.Method.GET, urlService, null, new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
try {
String durationStr = null;
for (int i = 0; i < response.length(); i++) {
JSONObject eventJson = response.getJSONObject(i);
String title = eventJson.getString("EventTitle");
String body = eventJson.getString("EventBody");
String date = eventJson.getString("EventDate");
String time = eventJson.getString("EventTime");
int duration = Integer.parseInt(eventJson.getString("EventDuration"));
if (duration > 60) {
durationStr = "Duration: " + duration / 60 + " h";
} else if (duration < 60) {
durationStr = "Duration: " + duration + " m";
}
String place = eventJson.getString("EventPlace");
String organ = eventJson.getString("Organization");
Event event = new Event(title, body, date, time, durationStr, place, organ);
eventsList.add(event);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("VOLLEY ERROR", "" + error);
}
}
);
requestQueue.add(jsonArrayRequest);
return eventsList;
}
You can use EventBus for your purpose that is a simple and truth way.
Here, i write an example for how to use EventBus with volley.
Consider that i want to download some data.
This is the class that my download methods is inside it (you can add more methods to it in the future):
Im used volley to download my data:
// Download methods is inside volley
public class MyDownloader{
public static void downloadData(){
DownloadDataEvent dlDataEvent=new DownloadDataEvent();
List<String> myResult=new ArrayList<>();
...
#Override
public void onResponse(JSONArray response) {
super.onResponse(response);
if(respone!=null){
// Do what i want with my received data
dlDataEvent.setData(response);
}
// Post my event by EventBus
EventBus.getDefault().post(dlDataEvent);
...
}
}
}
This is my event:
public class DownloadDataEvent{
private JSONArray mData;
public void setData(JSONArray data){
mData=data;
}
public JSONArray setData(){
return mData;
}
}
Now i want to use my downloadData() method inside my MainActivity:
(I called my downloadData method inside onCreate.)
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
...
// I have to register this class for EventBus subscriber:
if(!EventBus.getDefault().isRegister(this)){
EventBus.getDefault().registerSticky(this);
}
// Call my downloadData method
if(isConnected()){
MyDownloader.downloadData();
}
}
// And for receive the data through EventBus, i have to create a
// method (subscriber) in this template:
public void onEventMainThread(DownloadDataEvent downloadDataEvent){
JSONArray result=downloadDataEvent.getData();
// Do what i want with my received data
}
}
you can create more than one subscriber every where you want to use received data.
I passed JSONArray to my DownloadDataEvent that it is not good. you can deserialize your received data and pass it to your DownloadDataEvent.
I used Volley to download data
Maybe my descriptions were confusing, but EventBus is a well-known library and is very easy to use.
I'm making multiple requests to Amazon Web Services. I'm getting the 503 error because I'm making too many request too quickly. I want to know how to set the time-out between different requests, not the same ones. I am not looking to set the retry policy. I am also not looking to time-trigger individual requests. I want to time the interval between requests. The reason is that I am looping so quickly and making so many requests, that timing-triggering them is equivalent to submitting them all that the same time. The whole point is to space the requests out evenly.
Since you don't show how you made multiple requests, so I suggest you refer to the following sample, then try applying to your code. Hope it helps!
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final RequestQueue queue = Volley.newRequestQueue(this);
final String url = "http://google.com";
final Handler handler = new Handler();
for (int i = 0; i <= 5; i++) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
StringRequest request = new StringRequest(url, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
Log.i("onResponse", response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("onErrorResponse", error.toString());
}
});
queue.add(request);
}
}, 2000); // 2000 miliseconds
}
}
assuming you have Request object , before adding the request to the queue you can do this
request.setRetryPolicy(new DefaultRetryPolicy(5000, 5, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
the 5000 indicates the time between each request in ms
the 5 is the number of times you want to send the request before it gives you timeout
for the sake of someone seeing this, this is how to use timers to manually seclude a task
Timer timer = new Timer();
final Handler handler = new Handler(){
#Override
public void handleMessage(Message msg) {
// Update UI here if u need
}
};
TimerTask task = new TimerTask () {
#Override
public void run () {
//send requests according to your logic here
}
};
timer.schedule(task, 0, 60000); // 60000 = 1 min
Was struggling with this too, til I got some help from another developer. Try something like this:
public class HTTP {
String getUrl;
Context context;
YTVisualizer ytv;
int numberOfCurrentRequests = 0;
public HTTP(Context context, YTVisualizer ytv){
this.context = context;
this.ytv = ytv;
}
public void get(final String url) {
numberOfCurrentRequests++;
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(250 * numberOfCurrentRequests);
} catch(InterruptedException e) {
e.printStackTrace();
}
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(context);
String[] parts = url.split("=");
final String key = parts[1];
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
#Override
//runs in thread main
public void onResponse(String response) {
Log.i("Response", response);
String title = new String();
try {
JSONObject obj = new JSONObject(response);
Iterator<String> str = obj.keys();
String key = str.next();
title = obj.getString(key);
} catch (JSONException e) {
e.printStackTrace();
}
ytv.SetVideosFromHTTPClass(key, title, response);
numberOfCurrentRequests--;
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(context, "Error: are you connected to the internet?", Toast.LENGTH_SHORT).show();
numberOfCurrentRequests--;
}
});
// Add the request to the RequestQueue.
queue.add(stringRequest);
}
}).start();
}
}
It pauses inbetween proportional to the amount of current requests. The 1st one sleeps .25 s, the 2nd one .5s, the third one .75s, and so on. Theyre all scheduled in order.
I know this is an old question, but here is a solution written in Kotlin:
Inside your class with the RequestQueue, you can add
val throttleTimeout : Long = 100L
private val throttleQueue : ArrayBlockingQueue<Request<*>> = ArrayBlockingQueue(100);
private val throttleThread = Thread {
while(true){
val rqst = throttleQueue.take()
requestQueue?.add(rqst)
Thread.sleep(throttleTimeout)
}
}
fun <T> addToThrottledRequestQueue(request: Request<T>, tag: String){
request.tag = if (TextUtils.isEmpty(tag)) TAG else tag
throttleQueue.put(request)
}
And just make sure to start the thread in your class initialization. You can also mix this with a function to create non-throttled request and mix them together.
fun <T> addToRequestQueue(request: Request<T>, tag: String) {
request.tag = if (TextUtils.isEmpty(tag)) TAG else tag
requestQueue?.add(request)
}
The addToThrottledRequestQueue function will make sure those requests are throttled while other requests can flow freely.
I've been reading all day threads regarding this issue I came up with a strategy but can't make it work
I have a listview fetching json data from a sql server
this listview already has a on swipe refresh function
I need this listview to refresh automatically only when new row was inserted in the data base.
So I wrote a php file fetching number of rows and echoing it witha 3 second refresh (on the php itself) so every time I enter the php file I get the realtime row numbers of my table.
I'm trying to build a function inside my MainActivity:
int OldNumberOfRows = data from the php file
while(true){
int newNumberOfRows = fetch data again using that php
if(both arent equal) execute refresh command.
}
Note: I got no idea how to extract the string from my asynctask to start manipulating my code with it.
That's it in general, Iv'e added the main activity , the "outer class" (FetchNumRowAsync) calling that php the swipe class and the php itself
MainActivity:
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
private String TAG = MainActivity.class.getSimpleName();
private String URL = "http://troyka.esy.es/troyka/orders.php";
private SwipeRefreshLayout swipeRefreshLayout;
private ListView listView;
private SwipeListAdapter adapter;
private List<Order> orderList;
// initially offset will be 0, later will be updated while parsing the json
private int offSet = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new FetchRowNumAsync(this).execute("http://troyka.esy.es/numberofrows.php");
listView = (ListView) findViewById(R.id.listView);
//RelativeLayout.LayoutParams layout_description = new RelativeLayout.LayoutParams(50,10);
//Rl.setLayoutParams(layout_description);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
orderList = new ArrayList<>();
adapter = new SwipeListAdapter(this, orderList);
listView.setAdapter(adapter);
swipeRefreshLayout.setOnRefreshListener(this);
/**
* Showing Swipe Refresh animation on activity create
* As animation won't start on onCreate, post runnable is used
*/
swipeRefreshLayout.post(new Runnable() {
#Override
public void run() {
swipeRefreshLayout.setRefreshing(true);
fetchOrders();
}
}
);
}
/**
* This method is called when swipe refresh is pulled down
*/
#Override
public void onRefresh() {
fetchOrders();
}
/**
* Fetching movies json by making http call
*/
private void fetchOrders() {
// showing refresh animation before making http call
swipeRefreshLayout.setRefreshing(true);
// appending offset to url
String url = URL + offSet;
// Volley's json array request object
JsonArrayRequest req = new JsonArrayRequest(url,
new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
Log.d(TAG, response.toString());
if (response.length() > 0) {
// looping through json and adding to order list
for (int i = 0; i < response.length(); i++) {
try {
JSONObject orderObj = response.getJSONObject(i);
int rank = orderObj.getInt("rank");
String title = orderObj.getString("title");
Order m = new Order(rank, title);
orderList.add(0, m);
// updating offset value to highest value
if (rank >= offSet)
offSet = rank;
} catch (JSONException e) {
Log.e(TAG, "JSON Parsing error: " + e.getMessage());
}
}
adapter.notifyDataSetChanged();
}
// stopping swipe refresh
swipeRefreshLayout.setRefreshing(false);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Server Error: " + error.getMessage());
Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_LONG).show();
// stopping swipe refresh
swipeRefreshLayout.setRefreshing(false);
}
});
// Adding request to request queue
MyApplication.getInstance().addToRequestQueue(req);
}
}
FetchRowNumAsync:
public class FetchRowNumAsync extends AsyncTask<String, Void, String> {
private Context mContext;
public FetchRowNumAsync(Context ctx){
this.mContext = ctx;
}
protected String doInBackground(String... urls)
{
String fullString = "";
try{
URL url = new URL(urls[0]);
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
String line;
while ((line = reader.readLine()) != null) {
fullString += line;
}
reader.close();
}catch(Exception e ){
e.getMessage();
}
return fullString;
}
#Override
protected void onPostExecute(String value){
try{
((OnValueFetchedListener) mContext).onValueFetched(value);
}catch(ClassCastException e){}
}
public interface OnValueFetchedListener{
void onValueFetched(String columns);
}
}
SwipeListAdapter:
public class SwipeListAdapter extends BaseAdapter {
private Activity activity;
private LayoutInflater inflater;
private List<Order> orderList;
private String[] bgColors;
public SwipeListAdapter(Activity activity, List<Order> orderList) {
this.activity = activity;
this.orderList = orderList;
bgColors = activity.getApplicationContext().getResources().getStringArray(R.array.movie_serial_bg);
}
#Override
public int getCount() {
return orderList.size();
}
#Override
public Object getItem(int location) {
return orderList.get(location);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (inflater == null)
inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null)
convertView = inflater.inflate(R.layout.list_row, null);
TextView serial = (TextView) convertView.findViewById(R.id.serial);
TextView title = (TextView) convertView.findViewById(R.id.title);
serial.setText(String.valueOf(orderList.get(position).id));
title.setText(orderList.get(position).title);
String color = bgColors[position % bgColors.length];
serial.setBackgroundColor(Color.parseColor(color));
return convertView;
}
}
PHP
<?php
header("refresh: 3;");
$mysqli = new mysqli("irrelevant","irrelevant","irrelevant","irrelevant");
/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
$query = "SELECT COUNT(*) FROM orders";
$result = mysqli_query($mysqli,$query);
$rows = mysqli_fetch_row($result);
echo ($rows[0]);
$result->close();
$mysqli->close();
?>
Try this approach:
Create an endpoint in your server like the following:
//http://somesite.com/api/data/pull/check
Then, you can easily check this endpoint that returns some value like true or false depending on whether there is new data inserted into the db.
From the result you receive, you can then decide on whether to refresh your data on the phone by making another HTTP request or not. You always want to avoid making unnecessary requests to the server - remember users spend money every time they use their data plan (service).
I, like in the comments above, recommend having a column with a timestamp that you can check so that you only get the newly added data instead of everything!
I hope this gives you a simple idea on how to approach this issue! Good luck!
android app will not know when you have added/updated data in your table on the server until and unless you call script from app and fetch the data and update in your device.
only if your app has implemented these feature's
push notification- call Script every time you receive notification.
XMPP service- used for chat apps(which is not probably answer for
your question right now)
here is my suggestion for you
From server side:
create timestamp field in your table on server. update it with
current timestamp value every time you do changes(i.e update/add) in
the table.and when when that script is called send it across in json
and make your app save it in sqlite along with data.
server will compare for timestamp posted by app everytime with the
saved timestamp in the server for new data.
from client side:
for fist time timestamp from app will be 0. server will check it and
send the whole data along with the timestamp saved during changes in
table. save the data along with time stamp . second time when the
script is called App will be sending the timestamp that was last
saved.
with all this your app will not come to know still if new data is added until you call script and check. but atleast it will come to know if new data is received or not and whether to refresh ur screen
now comes script calling part from client side that is executing of assynch task, do it using handler to execute assynch class every minute
final Handler timerHandler = new Handler();
Runnable timerRunnable;
timerRunnable = new Runnable() {
#Override
public void run() {
new FetchRowNumAsync(context).execute(url);
timerHandler.postDelayed(timerRunnable, 60000); // run every minute
}
};
and unregister it in onDestroy()
#Override
public void onDestroyView() {
// TODO Auto-generated method stub
super.onDestroyView();
timerHandler.removeCallbacks(timerRunnable);
}
i'm a newbie in android. In my app i create a many-to-many chat, and need to update from server a list of Messages. In order to do so, i created a service that updates every second from the server.
My problem is that i don't know how to pass data back to the application. I know that I should do it using intent and broadcast receiver, but in that I stuck with Bundle object that i have to serialize in order to pass it to the app, and it does not make sense to me, since this operation is not that efficient.
For now i'm using the ref to my application (i think it's not that good but don't know why), and after every update from server in the service i activate the application function, and updates it's fields directly. Moreover i think maybe my code will do some good for beginners as well :)
public class UpdateChatService extends Service {
private static final long DELAY_FOR_CHAT_TASK = 0;
private static final long PERIOD_FOR_CHAT_TASK = 1;
private static final TimeUnit TIME_UNIT_CHAT_TASK = TimeUnit.SECONDS;
//private Task retryTask; TODO: check this out
private ScheduledExecutorService scheduler;
private boolean timerRunning = false;
private long RETRY_TIME = 200000;
private long START_TIME = 5000;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
scheduleChatUpdate();
}
private void scheduleChatUpdate() {
BiggerGameApp app = (BiggerGameApp) getApplication();
this.scheduler = Executors.newScheduledThreadPool(3);
this.scheduler.scheduleAtFixedRate(new UpdateChatTask(app),
DELAY_FOR_CHAT_TASK, PERIOD_FOR_CHAT_TASK,
TIME_UNIT_CHAT_TASK);
timerRunning = true;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!timerRunning) {
scheduleChatUpdate();
}
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onDestroy() {
super.onDestroy();
if (scheduler != null) {
scheduler.shutdown();
}
timerRunning = false;
}
}
Here is the code of the asynchronous task the runs in the service.
Please tell me what i'm doing wrong, and how should pass data from the service to the application.
public void run() {
try {
if (this.app.getLastMsgFromServer() == null) {
this.app.setLastMsgFromServer(new Message(new Player(DEFAULT_EMAIL), "", -1));
this.app.getLastMsgFromServer().setMessageId(-1);
}
Gson gson = new GsonBuilder()
.registerTypeAdapter(DateTime.class, new DateTimeTypeConverter())
.create();
ServerHandler serverHandler = new ServerHandler();
String jsonString = gson.toJson(this.app.getLastMsgFromServer());
// Sending player to servlet in server
String resultString = serverHandler.getResultFromServlet(jsonString, "GetListOfMessages");
if (resultString.contains("Error")) {
return;
}
// Parsing answer
JSONObject json = new JSONObject(resultString);
Status status = null;
String statusString = json.getString("status");
if (statusString == null || statusString.length() == 0)
return;
status = Status.valueOf(statusString);
if (Status.SUCCESS.equals(status)) {
ArrayList<Message> tempChat = null;
JSONArray jsonList = json.getJSONArray("data");
MyJsonParser jsonParser = new MyJsonParser();
tempChat = jsonParser.getListOfMessagesFromJson(jsonList.toString());
if (tempChat != null && tempChat.size() != 0) {
// After getting the chat from the server, it saves the last msg
// For next syncing with the server
this.app.setLastMsgFromServer(tempChat.get(LAST_MSG_INDEX));
tempChat.addAll(this.app.getChat());
if (tempChat.size() > SIZE_OF_USER_CHAT) {
tempChat = (ArrayList<Message>) tempChat.subList(0, SIZE_OF_USER_CHAT - 1);
}
this.app.setChat(tempChat);
this.app.updateViews(null);
}
}
return;
Is the Service local only (I'm going to assume "yes")?
Communication with a local-only service can be done by passing an instance of android.os.Binder back, as shown below:
public class UpdateChatService extends Service {
public static final class UpdateChat extends Binder {
UpdateChatService mInstance;
UpdateChat(UpdateChatService instance) {
mInstance = instance;
}
public static UpdateChat asUpdateChat(IBinder binder) {
if (binder instanceof UpdateChat) {
return (UpdateChat) binder;
}
return null;
}
public String pollMessage() {
// Takes a message from the list or returns null
// if the list is empty.
return mInstance.mMessages.poll();
}
public void registerDataSetObserver(DataSetObserver observer) {
mInstance.mObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mInstance.mObservable.unregisterObserver(observer);
}
}
private ScheduledExecutorService mScheduler;
private LinkedList<String> mMessages;
private DataSetObservable mObservable;
#Override
public IBinder onBind(Intent intent) {
return new UpdateChat(this);
}
#Override
public void onCreate() {
super.onCreate();
mObservable = new DataSetObservable();
mMessages = new LinkedList<String>();
mScheduler = Executors.newScheduledThreadPool(3);
mScheduler.scheduleAtFixedRate(new UpdateChatTask(), 0, 1, TimeUnit.SECONDS);
}
#Override
public void onDestroy() {
super.onDestroy();
mScheduler.shutdownNow();
mObservable.notifyInvalidated();
}
class UpdateChatTask implements Runnable {
int mN = 0;
public void run() {
// This example uses a list to keep all received messages, your requirements may vary.
mMessages.add("Message #" + (++mN));
mObservable.notifyChanged();
}
}
}
This example could be used to feed an Activity (in this case a ListActivity) like this:
public class ChattrActivity extends ListActivity implements ServiceConnection {
LinkedList<String> mMessages;
ArrayAdapter<String> mAdapter;
UpdateChat mUpdateChat;
DataSetObserver mObserver;
Runnable mNotify;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMessages = new LinkedList<String>();
mNotify = new Runnable() {
public void run() {
mAdapter.notifyDataSetChanged();
}
};
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mMessages);
getListView().setAdapter(mAdapter);
// Bind to the Service if you do not need it to persist when this Activity
// dies - otherwise you must call #startService(..) before!
bindService(new Intent(this, UpdateChatService.class), this, BIND_AUTO_CREATE);
}
/**
* #see android.app.ListActivity#onDestroy()
*/
#Override
protected void onDestroy() {
super.onDestroy();
if (mUpdateChat != null) {
mUpdateChat.unregisterDataSetObserver(mObserver);
unbindService(this);
}
}
public void onServiceConnected(ComponentName name, IBinder service) {
mUpdateChat = UpdateChat.asUpdateChat(service);
mObserver = new DataSetObserver() {
#Override
public void onChanged() {
String message;
while ((message = mUpdateChat.pollMessage()) != null) {
mMessages.add(message);
}
runOnUiThread(mNotify);
}
#Override
public void onInvalidated() {
// Service was killed - restart or handle this error somehow.
}
};
// We use a DataSetObserver to notify us when a message has been "received".
mUpdateChat.registerDataSetObserver(mObserver);
}
public void onServiceDisconnected(ComponentName name) {
mUpdateChat = null;
}
}
If you need to communicate across processes you should look into implementing an AIDL interface - but for "local" versions this pattern works just fine & doesn't involve abusing the global Application instance.
You can use a static memory shared between your service and rest of application (activities). If you do not plan to expose this service to external apps, then sharing static memory is better than serializing/deserializing data via bundles.
Bundles based approach is encouraged for components that are to be exposed to outside world. A typical app usually has just the primary activity exposed in app manifest file.
If your don't pulibc your service , the static memory and the callback function can do.
If not , you can send broadcast.