SharedPreferences.apply() and ANR application - android

I used to SharedPreferences.apply() method. When this method is called very often, then it hangs the application. Commit() method is very slow, but is working properly.
You can get the ANR in my example. Fold and unfold the activity!
public class Main extends Activity {
#Override
public void onCreate(Bundle b) {
super.onCreate(b);
setContentView(R.layout.main);
new Thread(new Runnable() {
#Override
public void run() {
while(true) {
SharedPreferences.Editor ed = getEditor();
ed.putString(getUUID(), getUUID());
ed.apply();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
public static String getUUID() {
return UUID.randomUUID().toString();
}
final private String BASE = "BASE";
private SharedPreferences shadPrefBase = null;
SharedPreferences getSharedPreferences() {
if(shadPrefBase == null) {
shadPrefBase = getSharedPreferences(BASE, Context.MODE_MULTI_PROCESS);
}
return shadPrefBase;
}
private SharedPreferences.Editor editorShared = null;
private SharedPreferences.Editor getEditor() {
if(editorShared == null) {
editorShared = getSharedPreferences().edit();
}
return editorShared;
}
}
Fold and unfold the activity!

Every 10 milliseconds, indefinitely, you are forking a background thread via the apply() call, all of which are going to queue up as they attempt to do I/O on the same data. That is not going to give you good results.
Beyond that, I would be very careful about sharing Editor instances across threads the way you are.

Related

What's a good way to asynchronously update the progress of ProgressDialog from background thread?

I want to have a Splash screen that has an inderteminate ProgressDialog and its progress gets updated by async calls from within a Presenter class (from MVP architecture).
I have a number of API calls to make to my BaaS server and for every successfull call, I would like to update the progress bar.
What's the best way to accomplish this?
I have been trying using EventBus to send notifications to my SplashActivity but it seems that all the API calls are first completed and only then the bus notifications are getting consumed and updating the UI.
What I have done so far is:
SplashActivity:
#Subscribe(threadMode = ThreadMode.MAIN)
public void onProgressBar(String event) {
Timber.d("onProgressBar");
if(event.contains("Done")) {
roundCornerProgressBar.setProgress(100);
} else {
roundCornerProgressBar.setProgress(roundCornerProgressBar.getProgress() + 10);
}
textViewTips.setText(event);
}
Presenter:
InstanceID iid = InstanceID.getInstance(ctx);
String id = iid.getId();
mDataManager.getPreferencesHelper().putInstanceId(id);
GSUtil.instance().deviceAuthentication(id, "android", mDataManager);
GSUtil.instance().getPropertySetRequest("PRTSET", mDataManager);
GSUtil:
public void deviceAuthentication(String deviceId, String deviceOS, final DataManager mDataManager) {
gs.getRequestBuilder().createDeviceAuthenticationRequest()
.setDeviceId(deviceId)
.setDeviceOS(deviceOS)
.send(new GSEventConsumer<GSResponseBuilder.AuthenticationResponse>() {
#Override
public void onEvent(GSResponseBuilder.AuthenticationResponse authenticationResponse) {
if(mDataManager != null) {
mDataManager.getPreferencesHelper().putGameSparksUserId(authenticationResponse.getUserId());
}
EventBus.getDefault().post("Reading player data");
}
});
}
public void getPropertySetRequest(String propertySetShortCode, final DataManager mDataManager) {
gs.getRequestBuilder().createGetPropertySetRequest()
.setPropertySetShortCode(propertySetShortCode)
.send(new GSEventConsumer<GSResponseBuilder.GetPropertySetResponse>() {
#Override
public void onEvent(GSResponseBuilder.GetPropertySetResponse getPropertySetResponse) {
GSData propertySet = getPropertySetResponse.getPropertySet();
GSData scriptData = getPropertySetResponse.getScriptData();
try {
JSONObject jObject = new JSONObject(propertySet.getAttribute("max_tickets").toString());
mDataManager.getPreferencesHelper().putGameDataMaxTickets(jObject.getInt("max_tickets"));
jObject = new JSONObject(propertySet.getAttribute("tickets_refresh_time").toString());
mDataManager.getPreferencesHelper().putGameDataTicketsRefreshTime(jObject.getLong("refresh_time"));
} catch (JSONException e) {
e.printStackTrace();
}
EventBus.getDefault().post("Game data ready");
EventBus.getDefault().post("Done!");
}
});
}
Right now I am just showing you 2 API calls, but I will need another 2.
Thank you
I found the answer! It's easier that I thought, which is unfortunate as I spend about 4 hours on this:
First, I created two new methods on my MVPView interface:
public interface SplashMvpView extends MvpView {
void updateProgressBarWithTips(float prog, String tip);
void gameDataLoaded();
}
Then, in the presenter itself, I call every API call and for every call, I update the View with the updateProgressBarWithTips method and when everything is completed, I finalise it so I can move from Splash screen to Main screen:
private void doGSData(String id) {
getMvpView().updateProgressBarWithTips(10, "Synced player data");
GSAndroidPlatform.gs().getRequestBuilder().createDeviceAuthenticationRequest()
.setDeviceId(id)
.setDeviceOS("android")
.send(new GSEventConsumer<GSResponseBuilder.AuthenticationResponse>() {
#Override
public void onEvent(GSResponseBuilder.AuthenticationResponse authenticationResponse) {
if(mDataManager != null) {
mDataManager.getPreferencesHelper().putGameSparksUserId(authenticationResponse.getUserId());
}
getMvpView().updateProgressBarWithTips(10, "Synced game data");
GSAndroidPlatform.gs().getRequestBuilder().createGetPropertySetRequest()
.setPropertySetShortCode("PRTSET")
.send(new GSEventConsumer<GSResponseBuilder.GetPropertySetResponse>() {
#Override
public void onEvent(GSResponseBuilder.GetPropertySetResponse getPropertySetResponse) {
GSData propertySet = getPropertySetResponse.getPropertySet();
GSData scriptData = getPropertySetResponse.getScriptData();
try {
JSONObject jObject = new JSONObject(propertySet.getAttribute("max_tickets").toString());
mDataManager.getPreferencesHelper().putGameDataMaxTickets(jObject.getInt("max_tickets"));
jObject = new JSONObject(propertySet.getAttribute("tickets_refresh_time").toString());
mDataManager.getPreferencesHelper().putGameDataTicketsRefreshTime(jObject.getLong("refresh_time"));
} catch (JSONException e) {
e.printStackTrace();
}
getMvpView().gameDataLoaded();
}
});
}
});
}
I hope this helps someone, if you're using MVP architecture.
Cheers

Can't create handler inside thread that has not called Looper.prepare(), while implementing IdlingResource

I’m attempting to write Espresso unit test that depends on a component that makes TCP/IP network connection to an external app in order to pass successfully.
The test failed to due the fact that the TCP/IP network took longer than the allowed by Espresso...
Therefore, we need to have TCP/IP code Class TCPConnectionTask implement IdlingResource:
However, I'm getting, this exception:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at android.app.Activity.<init>(Activity.java:786)
at com.sample.QuicksetSampleActivity.<init>(QuicksetSampleActivity.java:82)
at com.unitTests.QuicksetSampleActivityTest.<init>(QuicksetSampleActivityTest.java:52)
I enclosed the TCPConnectionTask and called Looper.prepare() & also attempted Looper.prepareMainLooper() , with no success, see below (TCPConnectionTask):
/**
* Async task to connect to create TCPIPDataComm and connect to external IRB.
*
*/
public class TCPConnectionTask extends AsyncTask<String, Void, Void > implements IdlingResource {
String ip_user = null;
int port_user;
private ResourceCallback callback;
private boolean flag = false;
protected Void doInBackground(String... args) {
try {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(
new Runnable() {
#Override
public void run() {
Looper.prepare();
//Looper.prepareMainLooper();
flag = true;
TCPIPDataComm tcp = new TCPIPDataComm(ip_user, port_user);
if(tcp != null){
tcp.open();
_TCPDataComm = tcp;
// we can enable the DataComm interface for simulation in UI app
int resultCode = 0;
try {
resultCode = QuicksetSampleApplication.getSetup().setDataCommInfo(
getAuthKey(), _TCPDataComm.getHostName(),
_TCPDataComm.getPortNumber());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
//task completed
flag = false;
}
Log.d(QuicksetSampleActivity.LOGTAG,
"Setting DataComm Result = "
+ resultCode
+ " - "
+ ResultCode
.getString(resultCode));
}
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void setInfo(String ipValue, int portNumber)
{
ip_user = ipValue;
port_user = portNumber;
}
#Override
public String getName() {
return this.getClass().getName().toString();
}
#Override public boolean isIdleNow() {
if (flag && callback != null) {
callback.onTransitionToIdle();
}
return flag;
}
#Override public void registerIdleTransitionCallback(ResourceCallback callback) {
this.callback = callback;
}
}
Below is the relevant snippet of the unit test class, QuicksetSampleActivityTest:
#RunWith(AndroidJUnit4.class)
public class QuicksetSampleActivityTest extends ActivityInstrumentationTestCase2<QuicksetSampleActivity> {
private QuicksetSampleActivity newQuicksetSampleActivity = null;
private final String ip = "192.168.43.139";
private final int port = 9999;
private final int timeOutTime = 1000;
//This is the idling resource that takes time to complete due to network latency...
private QuicksetSampleActivity.TCPConnectionTask taskIdlingResource = null;
//const
public QuicksetSampleActivityTest() {
super(QuicksetSampleActivity.class);
//instantiation of idling resource that is used for TCP connection
taskIdlingResource = new QuicksetSampleActivity().new TCPConnectionTask();
}
#Before
public void setUp() throws Exception {
super.setUp();
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
//open activity
newQuicksetSampleActivity = getActivity();
// Make sure Espresso does not time out
IdlingPolicies.setMasterPolicyTimeout(timeOutTime * 10, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(timeOutTime * 10, TimeUnit.MILLISECONDS);
//register idling resource
Espresso.registerIdlingResources(taskIdlingResource);
}
#After
public void unregisterIntentServiceIdlingResource() {
//unregister idling resource
Espresso.unregisterIdlingResources(taskIdlingResource);
}
//The EditText GUI with the port & Ip was noe found using espresso, we need to set teh ip & port programmatically
public void setIpandPortToPcBridge() {
// Use TCPCommunicatuonTask interface
taskIdlingResource.setInfo(ip, port);
taskIdlingResource.execute();
}
//after TCP connection is made and/or tested
#Test
public void testActionBarMenuItemsIrDevicesAfterTCPConnectionFunctions() {
//we were not able to find the IP & Port fields so set them programmatically
setIpandPortToPcBridge();
//open action bar menu
Espresso.openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getTargetContext());
//test IR Devices/Functions menu item
Espresso.onData(Matchers.allOf(Matchers.instanceOf(MenuItem.class), MatcherUtility.menuItemWithTitle("IR Devices/Functions"))).perform(ViewActions.click());
//add new device will connect the app
Espresso.onView(ViewMatchers.withId(R.id.btAdd)).perform(ViewActions.click());
//DeviceFunctionsActivity is rendered
Espresso.onView(ViewMatchers.withText("IR Devices")).check(ViewAssertions.matches(ViewMatchers.withText("IR Devices")));
//find the 3 required buttons for this UI
//test START learning
//Espresso.onView(ViewMatchers.withText("Start")).check(ViewAssertions.matches(ViewMatchers.withText("Start")));
//click
//test CANCEL learning
//test TEST Learned IR
//Espresso.onView(ViewMatchers.withText("Test Learned IR")).check(ViewAssertions.matches(ViewMatchers.withText("Test Learned IR")));
//click
//test Delete Learn Code
// Espresso.onView(ViewMatchers.withText("Delete Learn Code")).check(ViewAssertions.matches(ViewMatchers.withText("Delete Learn Code")));
//click
//go back
//ViewActions.pressBack();
}
}
}
How can I resolve this exception, and run the Espresso IdlingResource successfully?
Try
getInstrumentation().runOnMainSync(new Runnable() {
#Override
public void run() {
// Your testActionBarMenuItemsIrDevicesAfterTCPConnectionFunctions() test body
}
});
Example of usage with ActivityTestRule:
getInstrumentation().runOnMainSync(new Runnable() {
#Override
public void run() {
mMusicPlayerActivityTestRule.getActivity()
.getSupportMediaController().registerCallback(
new MediaControllerCompat.Callback() {
#Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
super.onPlaybackStateChanged(state);
if (state.getState() == STATE_PLAYING) {
countDownLatch.countDown();
}
}
});
}});

Second time running the thread makes application crash

I use a worker thread to read text from a url. My thread is as follow. In the first time running, I am sure thread running is finished as I can check sdcard_readstr is null.
In the second time running, when I call thread_download.start();, then the program crashed.
What could be wrong? Thanks
public class DownloadingThread extends AbstractDataDownloading {
#Override
public void doRun() {
// TODO Auto-generated method stub
try {
// Create a URL for the desired page
URL url = new URL(SDcard_DetailView.textfileurl);
// Read all the text returned by the server
BufferedReader in = new BufferedReader(new
InputStreamReader(url.openStream()));
do{
sdcard_readstr = in.readLine();
}while(sdcard_readstr!=null);
in.close();
} catch (MalformedURLException e) {
} catch (IOException e) {
}
}
}
public abstract class AbstractDataDownloading extends Thread{
private final Set<ThreadCompleteListener> listeners
= new CopyOnWriteArraySet<ThreadCompleteListener>();
public final void addListener(final ThreadCompleteListener listener) {
listeners.add(listener);
}
public final void removeListener(final ThreadCompleteListener listener) {
listeners.remove(listener);
}
private final void notifyListeners() {
for (ThreadCompleteListener listener : listeners) {
listener.notifyOfThreadComplete(this);
}
}
#Override
public final void run() {
try {
doRun();
} finally {
notifyListeners();
}
}
public abstract void doRun();
}
EDIT1:
In my thread complete notification, I use runOnUiThreadto use the UI components.
Is that causing problem?
public void notifyOfThreadComplete(Thread thread) {
// TODO Auto-generated method stub
if(downloadingStopbuttonispressed == false){//background process completed
textfileurl = null;
this.runOnUiThread(new Runnable() {
public void run() {
Wifibutton = (Button) findViewById(R.id.Wifiscanning);
Wifibutton.setText("Load another day's data");
final MenuItem refreshItem = optionsMenu.findItem(R.id.airport_menuRefresh);
refreshItem.setActionView(null);
}
});
}
}
I called thread start in onResume() as
#Override
protected void onResume() {
super.onResume();
if(textfileurl != null){
Wifibutton.setText("Stop Data Loading");
buttonStatus = "loading";
setRefreshActionButtonState(true);
thread_download.start();
}
}
EDIT2:
My LogCat image is attached.
My solution is here . I can't reuse the same instance of the Thread object in the second time. I need to create a new instance to call the Thread in the second time. So Thread is suitable for single time running process, for multiple time running process I should use AsyncTask. Even AsyncTack is only for one time execution and for multiple time execution, we should use as new MyAsyncTask().execute(""); I don't understand why people downvote with no reason given. I couldn't find the link in my first search.

Handling a facebook-object through multiple activities

Currently I'm writing an adapter class to provide a convenient way for communication with the facebook API.
The way I thought about using it is to run the authentication when the app is starting up, downloading user's private picture, and later in the app publishing updates on users facebook wall using an AsyncFacebookRunner.
However flipping through the documentation it seems for every authorize() implementation the first parameter have to be an activity.
void authorize(Activity activity, final DialogListener listener):
And here I begin to wonder.
Thinking about activities and life cycles what will happen when the activity I threw in will be destroyed? Wouldn't the reference for this object Facebook.mAuthActivity become invalid as well.
I see the logout() method "only" asks for a context.
String logout(Context context) throws ...:
context - The Android context in which the logout should be called: it should be the same context in which the login occurred in order to clear any stored cookies
From what I see I can not guarantee the "login-activity" will still be present as app's uptime increases - actually the opposite is more likely.
Are there any special situations I should consider to prevent the app form total crashing in a later state?
You can try use my FBHelper class.
public class FBHelper {
private SharedPreferences mPrefs;
private Context context;
private final String ACCES_TOKEN = "access_token";
private final String ACCES_EXPIRES = "access_expires";
private Facebook facebook;
private FBHelperCallbacks callback;
public FBHelper(Context context, Facebook facebook)
{
this.context = context;
this.facebook = facebook;
}
public void setSignInFinishListener(FBHelperCallbacks callback)
{
this.callback = callback;
}
public void FacebookSingleSignIn() {
mPrefs = ((Activity)context).getPreferences(Context.MODE_PRIVATE);
String access_token = mPrefs.getString(ACCES_TOKEN, null);
long expires = mPrefs.getLong(ACCES_EXPIRES, 0);
if(access_token != null) {
facebook.setAccessToken(access_token);
}
if(expires != 0) {
facebook.setAccessExpires(expires);
}
/*
* Only call authorize if the access_token has expired.
*/
if(!facebook.isSessionValid()) {
Log.i("Facebook","Facebook session is not valid based on acces token... authorizing again");
facebook.authorize((Activity)context, new String[] {"user_about_me"},new DialogListener() {
#Override
public void onFacebookError(FacebookError e) {
e.printStackTrace();
callback.onError(e.toString());
}
#Override
public void onError(DialogError e) {
Log.i("Facebook","onError inner");
callback.onError(e.toString());
}
#Override
public void onComplete(Bundle values) {
SharedPreferences.Editor editor = mPrefs.edit();
editor.putString(ACCES_TOKEN, facebook.getAccessToken());
Log.i("Facebook","Saving acces token:"+facebook.getAccessToken());
editor.putLong(ACCES_EXPIRES, facebook.getAccessExpires());
editor.commit();
callback.onSignedInFinished(facebook.getAccessToken());
}
#Override
public void onCancel() {
callback.onError("onCancel");
}
});
}
else
{
Log.i("Facebook","Accces token read form preferencesno no need to authorize");
callback.onSignedInFinished(facebook.getAccessToken());
}
}
public String LogOut()
{
try {
//set ACCES_TOKEN to null
mPrefs = ((Activity)context).getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = mPrefs.edit();
editor.putString(ACCES_TOKEN, null);
editor.putLong(ACCES_EXPIRES, 0);
editor.commit();
return facebook.logout(context);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "Error";
}
public static abstract class FBHelperCallbacks{
public abstract void onSignedInFinished(String accesToken);
public abstract void onError(String message);
}
}
This is how you use this class.
public class LogInActivity extends Activity {
private static final String TAG = "LogInActivity";
public static final int REQUEST_CODE = 1;
private Context context;
private Facebook facebook;
private FBHelper fbhelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_log_in);
this.context = this;
Handler pauser = new Handler();
pauser.postDelayed (new Runnable() {
public void run() {
facebook = new Facebook(context.getString(R.string.FACEBOOK_APP_ID));
fbhelper = new FBHelper(context, facebook);
if (aHelper.isLogedIn())
{
//log out
fbhelper.LogOut();
}
else
{
//facebook login
fbhelper.setSignInFinishListener(fbcallback);
fbhelper.FacebookSingleSignIn();
}
}
}, 100);
}
FBHelperCallbacks fbcallback = new FBHelperCallbacks() {
#Override
public void onSignedInFinished(String accesToken) {
Log.d(TAG,"log in finish");
}
#Override
public void onError(String message) {
setResult(RESULT_CANCELED);
finish();
}
};
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
facebook.authorizeCallback(requestCode, resultCode, data);
}
}
aHelper is object that hold some application specific data. Basically you should decide here if you want to log in or log out.
using facebook API for the android is easy and in your case you don't need to save the Facebook instance the only thing you need is to save the authKey of the facebook on the first login then you can use it anywhere.
this means that you can create more than one instance of the facebook object in mutiple activities based on the authKey.
Otherwise you need to put this facebook object in a singleton handler to save it among the application :
class x {
private Facebook obj;
private static x instance;
private x (){
}
public static x getX(){
if(instance == null){
instance = new x();
}
return instance;
}
public void setIt(Facebook obj){
this.obj = obj;
}
public Facebook getIt(){
return obj;
}
}
but this way is not the best way to implement the code you need to create a Facebook instance for each activity using the authKy.

how to pass data between service and it's application in the right way?

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.

Categories

Resources