Activity restarted when i press home button in android - android

I am afraid that my activity is restarted when i open it through TaskManager....
and my DefaultHttpClient object treated as fresh one..so here i am loosing the
session.
I tried by overriding the onSaveInstanceState() method..but no use..
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); // the UI component values are saved here.
}
How i can get rid of this one...

when you press button Home, activity will pause and resume when reopen.
you shout put code to onCreate().
see activity lifecryde:

You could subclass Android Applications: You can init the HttpClient there and hold the reference.
Look here
Than you can access from activity your Application object with activity.getApplication()
If your session works with cookies than you may need a persistent cookie storeage (like database or shared preferences):
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.http.client.CookieStore;
import org.apache.http.cookie.Cookie;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
/**
* A persistent cookie store which implements the Apache HttpClient
* {#link CookieStore} interface. Cookies are stored and will persist on the
* user's device between application sessions since they are serialized and
* stored in {#link SharedPreferences}.
* <p>
*/
public class PersistentCookieStore implements CookieStore {
private static final String COOKIE_PREFS = "CookiePrefsFile";
private static final String COOKIE_NAME_STORE = "names";
private static final String COOKIE_NAME_PREFIX = "cookie_";
private final ConcurrentHashMap<String, Cookie> cookies;
private final SharedPreferences cookiePrefs;
/**
* Construct a persistent cookie store.
*/
public PersistentCookieStore(Context context) {
cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
cookies = new ConcurrentHashMap<String, Cookie>();
// Load any previously stored cookies into the store
String storedCookieNames = cookiePrefs.getString(COOKIE_NAME_STORE,
null);
if (storedCookieNames != null) {
String[] cookieNames = TextUtils.split(storedCookieNames, ",");
for (String name : cookieNames) {
String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX
+ name, null);
if (encodedCookie != null) {
Cookie decodedCookie = decodeCookie(encodedCookie);
if (decodedCookie != null) {
cookies.put(name, decodedCookie);
}
}
}
// Clear out expired cookies
clearExpired(new Date());
}
}
#Override
public synchronized void addCookie(Cookie cookie) {
String name = cookie.getName() + cookie.getDomain();
// Save cookie into local store, or remove if expired
if (!cookie.isExpired(new Date())) {
cookies.put(name, cookie);
} else {
cookies.remove(name);
}
// Save cookie into persistent store
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
prefsWriter.putString(COOKIE_NAME_STORE,
TextUtils.join(",", cookies.keySet()));
prefsWriter.putString(COOKIE_NAME_PREFIX + name,
encodeCookie(new SerializableCookie(cookie)));
prefsWriter.commit();
}
#Override
public synchronized void clear() {
// Clear cookies from persistent store
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
for (String name : cookies.keySet()) {
prefsWriter.remove(COOKIE_NAME_PREFIX + name);
}
prefsWriter.remove(COOKIE_NAME_STORE);
prefsWriter.commit();
// Clear cookies from local store
cookies.clear();
}
#Override
public synchronized boolean clearExpired(Date date) {
boolean clearedAny = false;
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
for (ConcurrentHashMap.Entry<String, Cookie> entry : cookies.entrySet()) {
String name = entry.getKey();
Cookie cookie = entry.getValue();
if (cookie.isExpired(date)) {
// Clear cookies from local store
cookies.remove(name);
// Clear cookies from persistent store
prefsWriter.remove(COOKIE_NAME_PREFIX + name);
// We've cleared at least one
clearedAny = true;
}
}
// Update names in persistent store
if (clearedAny) {
prefsWriter.putString(COOKIE_NAME_STORE,
TextUtils.join(",", cookies.keySet()));
}
prefsWriter.commit();
return clearedAny;
}
#Override
public synchronized List<Cookie> getCookies() {
return new CopyOnWriteArrayList<Cookie>(cookies.values());
}
//
// Cookie serialization/deserialization
//
protected synchronized String encodeCookie(SerializableCookie cookie) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ObjectOutputStream outputStream = new ObjectOutputStream(os);
outputStream.writeObject(cookie);
} catch (Exception e) {
return null;
}
return byteArrayToHexString(os.toByteArray());
}
protected synchronized Cookie decodeCookie(String cookieStr) {
byte[] bytes = hexStringToByteArray(cookieStr);
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
Cookie cookie = null;
try {
ObjectInputStream ois = new ObjectInputStream(is);
cookie = ((SerializableCookie) ois.readObject()).getCookie();
} catch (Exception e) {
e.printStackTrace();
}
return cookie;
}
// Using some super basic byte array <-> hex conversions so we don't have
// to rely on any large Base64 libraries. Can be overridden if you like!
protected synchronized String byteArrayToHexString(byte[] b) {
StringBuffer sb = new StringBuffer(b.length * 2);
for (byte element : b) {
int v = element & 0xff;
if (v < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(v));
}
return sb.toString().toUpperCase();
}
protected synchronized byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
.digit(s.charAt(i + 1), 16));
}
return data;
}
}
Do something like this:
httpClient.setCookieStoreage(new PersistentCookieStore(this)) in your application subclass where you init the httpclient

You are probably seeing a nasty, long-standing Android bug that causes the symptoms you are describing. Have a look at my answer here: https://stackoverflow.com/a/16447508/769265

You probably have to add following code inside the onCreate() event of launcher Activity.
if (!isTaskRoot()) {
final Intent intent = getIntent();
final String action = intent.getAction();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {
finish();//Launcher Activity is not the root. So,finish it instead of launching
return;
}
}

android:launchMode="singleInstance"
android:alwaysRetainTaskState="true"
Try adding this two attributes to your Activity in manifest, this will make sure newIntent is called when activity is resumed from background.

Related

AmazonSNS - AwsCredentials.properties - NullPointerException

I am new to Android Studio and intelliJ.
I am trying to work with AmazonSNS - Push. I am unable to figure out how to add AwsCredentials.properties file to classpath of the module. I get a NPE at line 57 in the image below(at method getResourceAsStream()). I added the required keys in AwsCredentials.properties file.
Error:
In the questions that i have come across on StackOverflow regarding similar issues, some suggested that the file should be in the root folder, where, src is. I placed it in the same folder as that of src, but still getting the NPE. I also tried placing the file in com/test/ but with no use.
How do i solve this? Are there any other steps involved?
EDIT after starting a bounty - Adding java files
Here is what i did till now..
Create an Android Application called MyApplication. Imported all classes(AndroidMobilePushApp.java, ExternalReceiver.java, MessageReceivingService.java) from the demo application. Added required libs, and ran it and got the registationId as response from Amazon.
In the same application, i created a new module called snspush and imported SNSMobilePush.java file into it. Also imported the AwsCredentials.properties file to the same path as that of SNSMobilePush.java. Added the keys in AwsCredentials.properties file.
Followed the steps in documentation to uncomment necessary funtions.
Project Structure:
Java files:
AndroidMobilePushApp.java:
public class AndroidMobilePushApp extends AppCompatActivity {
private TextView tView;
private SharedPreferences savedValues;
private String numOfMissedMessages;
// Since this activity is SingleTop, there can only ever be one instance. This variable corresponds to this instance.
public static Boolean inBackground = true;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
numOfMissedMessages = getString(R.string.num_of_missed_messages);
setContentView(R.layout.activity_main);
tView = (TextView) findViewById(R.id.tViewId);
tView.setMovementMethod(new ScrollingMovementMethod());
startService(new Intent(this, MessageReceivingService.class));
}
public void onStop(){
super.onStop();
inBackground = true;
}
public void onRestart(){
super.onRestart();
tView.setText("");;
}
public void onResume(){
super.onResume();
inBackground = false;
savedValues = MessageReceivingService.savedValues;
int numOfMissedMessages = 0;
if(savedValues != null){
numOfMissedMessages = savedValues.getInt(this.numOfMissedMessages, 0);
}
String newMessage = getMessage(numOfMissedMessages);
if(newMessage!=""){
Log.i("displaying message", newMessage);
tView.append(newMessage);
}
}
public void onNewIntent(Intent intent){
super.onNewIntent(intent);
setIntent(intent);
}
// If messages have been missed, check the backlog. Otherwise check the current intent for a new message.
private String getMessage(int numOfMissedMessages) {
String message = "";
String linesOfMessageCount = getString(R.string.lines_of_message_count);
if(numOfMissedMessages > 0){
String plural = numOfMissedMessages > 1 ? "s" : "";
Log.i("onResume","missed " + numOfMissedMessages + " message" + plural);
tView.append("You missed " + numOfMissedMessages +" message" + plural + ". Your most recent was:\n");
for(int i = 0; i < savedValues.getInt(linesOfMessageCount, 0); i++){
String line = savedValues.getString("MessageLine"+i, "");
message+= (line + "\n");
}
NotificationManager mNotification = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotification.cancel(R.string.notification_number);
SharedPreferences.Editor editor=savedValues.edit();
editor.putInt(this.numOfMissedMessages, 0);
editor.putInt(linesOfMessageCount, 0);
editor.commit();
}
else{
Log.i("onResume","no missed messages");
Intent intent = getIntent();
if(intent!=null){
Bundle extras = intent.getExtras();
if(extras!=null){
for(String key: extras.keySet()){
message+= key + "=" + extras.getString(key) + "\n";
}
}
}
}
message+="\n";
return message;
}
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == R.id.menu_clear){
tView.setText("");
return true;
}
else{
return super.onOptionsItemSelected(item);
}
}
}
ExternalReceiver.java
package com.test.awstestapp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class ExternalReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if(intent!=null){
Bundle extras = intent.getExtras();
if(!AndroidMobilePushApp.inBackground){
MessageReceivingService.sendToApp(extras, context);
}
else{
MessageReceivingService.saveToLog(extras, context);
}
}
}
}
MessageReceivingService.java
public class MessageReceivingService extends Service{
private GoogleCloudMessaging gcm;
public static SharedPreferences savedValues;
public static void sendToApp(Bundle extras, Context context){
Intent newIntent = new Intent();
newIntent.setClass(context, AndroidMobilePushApp.class);
newIntent.putExtras(extras);
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(newIntent);
}
public void onCreate(){
super.onCreate();
final String preferences = getString(R.string.preferences);
savedValues = getSharedPreferences(preferences, Context.MODE_PRIVATE);
// In later versions multi_process is no longer the default
if(VERSION.SDK_INT > 9){
savedValues = getSharedPreferences(preferences, Context.MODE_MULTI_PROCESS);
}
gcm = GoogleCloudMessaging.getInstance(getBaseContext());
SharedPreferences savedValues = PreferenceManager.getDefaultSharedPreferences(this);
if(savedValues.getBoolean(getString(R.string.first_launch), true)){
register();
SharedPreferences.Editor editor = savedValues.edit();
editor.putBoolean(getString(R.string.first_launch), false);
editor.commit();
}
// Let AndroidMobilePushApp know we have just initialized and there may be stored messages
sendToApp(new Bundle(), this);
}
protected static void saveToLog(Bundle extras, Context context){
SharedPreferences.Editor editor=savedValues.edit();
String numOfMissedMessages = context.getString(R.string.num_of_missed_messages);
int linesOfMessageCount = 0;
for(String key : extras.keySet()){
String line = String.format("%s=%s", key, extras.getString(key));
editor.putString("MessageLine" + linesOfMessageCount, line);
linesOfMessageCount++;
}
editor.putInt(context.getString(R.string.lines_of_message_count), linesOfMessageCount);
editor.putInt(context.getString(R.string.lines_of_message_count), linesOfMessageCount);
editor.putInt(numOfMissedMessages, savedValues.getInt(numOfMissedMessages, 0) + 1);
editor.commit();
postNotification(new Intent(context, AndroidMobilePushApp.class), context);
}
protected static void postNotification(Intent intentAction, Context context){
final NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intentAction, Notification.DEFAULT_LIGHTS | Notification.FLAG_AUTO_CANCEL);
final Notification notification = new NotificationCompat.Builder(context).setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Message Received!")
.setContentText("")
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.getNotification();
mNotificationManager.notify(R.string.notification_number, notification);
}
private void register() {
new AsyncTask(){
protected Object doInBackground(final Object... params) {
String token;
try {
token = gcm.register(getString(R.string.project_number));
Log.i("registrationId", token);
}
catch (IOException e) {
Log.i("Registration Error", e.getMessage());
}
return true;
}
}.execute(null, null, null);
}
public IBinder onBind(Intent arg0) {
return null;
}
}
SNSMobilePush.java
package com.test;
public class SNSMobilePush {
private AmazonSNSClientWrapper snsClientWrapper;
public SNSMobilePush(AmazonSNS snsClient) {
this.snsClientWrapper = new AmazonSNSClientWrapper(snsClient);
}
public static final Map<Platform, Map<String, MessageAttributeValue>> attributesMap = new HashMap<Platform, Map<String, MessageAttributeValue>>();
static {
attributesMap.put(Platform.ADM, null);
attributesMap.put(Platform.GCM, null);
attributesMap.put(Platform.APNS, null);
attributesMap.put(Platform.APNS_SANDBOX, null);
attributesMap.put(Platform.BAIDU, addBaiduNotificationAttributes());
attributesMap.put(Platform.WNS, addWNSNotificationAttributes());
attributesMap.put(Platform.MPNS, addMPNSNotificationAttributes());
}
public static void main(String[] args) throws IOException {
/*
* TODO: Be sure to fill in your AWS access credentials in the
* AwsCredentials.properties file before you try to run this sample.
* http://aws.amazon.com/security-credentials
*/
AmazonSNS sns = new AmazonSNSClient(new PropertiesCredentials(
SNSMobilePush.class
.getResourceAsStream("AwsCredentials.properties")));
sns.setEndpoint("https://sns.us-west-2.amazonaws.com");
System.out.println("===========================================\n");
System.out.println("Getting Started with Amazon SNS");
System.out.println("===========================================\n");
try {
SNSMobilePush sample = new SNSMobilePush(sns);
/* TODO: Uncomment the services you wish to use. */
sample.demoAndroidAppNotification();
// sample.demoKindleAppNotification();
// sample.demoAppleAppNotification();
// sample.demoAppleSandboxAppNotification();
// sample.demoBaiduAppNotification();
// sample.demoWNSAppNotification();
// sample.demoMPNSAppNotification();
} catch (AmazonServiceException ase) {
System.out
.println("Caught an AmazonServiceException, which means your request made it "
+ "to Amazon SNS, but was rejected with an error response for some reason.");
System.out.println("Error Message: " + ase.getMessage());
System.out.println("HTTP Status Code: " + ase.getStatusCode());
System.out.println("AWS Error Code: " + ase.getErrorCode());
System.out.println("Error Type: " + ase.getErrorType());
System.out.println("Request ID: " + ase.getRequestId());
} catch (AmazonClientException ace) {
System.out
.println("Caught an AmazonClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with SNS, such as not "
+ "being able to access the network.");
System.out.println("Error Message: " + ace.getMessage());
}
}
public void demoAndroidAppNotification() {
// TODO: Please fill in following values for your application. You can
// also change the notification payload as per your preferences using
// the method
// com.amazonaws.sns.samples.tools.SampleMessageGenerator.getSampleAndroidMessage()
String serverAPIKey = "REPLACED_WITH_SERVER_API_KEY";
String applicationName = "snspushtest";
String registrationId = "REPLACED_WITH_REG_ID_FROM_AMAZON";
snsClientWrapper.demoNotification(Platform.GCM, "", serverAPIKey,
registrationId, applicationName, attributesMap);
}
public void demoKindleAppNotification() {
// TODO: Please fill in following values for your application. You can
// also change the notification payload as per your preferences using
// the method
// com.amazonaws.sns.samples.tools.SampleMessageGenerator.getSampleKindleMessage()
String clientId = "";
String clientSecret = "";
String applicationName = "";
String registrationId = "";
snsClientWrapper.demoNotification(Platform.ADM, clientId, clientSecret,
registrationId, applicationName, attributesMap);
}
public void demoAppleAppNotification() {
// TODO: Please fill in following values for your application. You can
// also change the notification payload as per your preferences using
// the method
// com.amazonaws.sns.samples.tools.SampleMessageGenerator.getSampleAppleMessage()
String certificate = ""; // This should be in pem format with \n at the
// end of each line.
String privateKey = ""; // This should be in pem format with \n at the
// end of each line.
String applicationName = "";
String deviceToken = ""; // This is 64 hex characters.
snsClientWrapper.demoNotification(Platform.APNS, certificate,
privateKey, deviceToken, applicationName, attributesMap);
}
public void demoAppleSandboxAppNotification() {
// TODO: Please fill in following values for your application. You can
// also change the notification payload as per your preferences using
// the method
// com.amazonaws.sns.samples.tools.SampleMessageGenerator.getSampleAppleMessage()
String certificate = ""; // This should be in pem format with \n at the
// end of each line.
String privateKey = ""; // This should be in pem format with \n at the
// end of each line.
String applicationName = "";
String deviceToken = ""; // This is 64 hex characters.
snsClientWrapper.demoNotification(Platform.APNS_SANDBOX, certificate,
privateKey, deviceToken, applicationName, attributesMap);
}
public void demoBaiduAppNotification() {
/*
* TODO: Please fill in the following values for your application. If
* you wish to change the properties of your Baidu notification, you can
* do so by modifying the attribute values in the method
* addBaiduNotificationAttributes() . You can also change the
* notification payload as per your preferences using the method
* com.amazonaws
* .sns.samples.tools.SampleMessageGenerator.getSampleBaiduMessage()
*/
String userId = "";
String channelId = "";
String apiKey = "";
String secretKey = "";
String applicationName = "";
snsClientWrapper.demoNotification(Platform.BAIDU, apiKey, secretKey,
channelId + "|" + userId, applicationName, attributesMap);
}
public void demoWNSAppNotification() {
/*
* TODO: Please fill in the following values for your application. If
* you wish to change the properties of your WNS notification, you can
* do so by modifying the attribute values in the method
* addWNSNotificationAttributes() . You can also change the notification
* payload as per your preferences using the method
* com.amazonaws.sns.samples
* .tools.SampleMessageGenerator.getSampleWNSMessage()
*/
String notificationChannelURI = "";
String packageSecurityIdentifier = "";
String secretKey = "";
String applicationName = "";
snsClientWrapper.demoNotification(Platform.WNS,
packageSecurityIdentifier, secretKey, notificationChannelURI,
applicationName, attributesMap);
}
public void demoMPNSAppNotification() {
/*
* TODO: Please fill in the following values for your application. If
* you wish to change the properties of your MPNS notification, you can
* do so by modifying the attribute values in the method
* addMPNSNotificationAttributes() . You can also change the
* notification payload as per your preferences using the method
* com.amazonaws
* .sns.samples.tools.SampleMessageGenerator.getSampleMPNSMessage ()
*/
String notificationChannelURI = "";
String applicationName = "";
snsClientWrapper.demoNotification(Platform.MPNS, "", "",
notificationChannelURI, applicationName, attributesMap);
}
private static Map<String, MessageAttributeValue> addBaiduNotificationAttributes() {
Map<String, MessageAttributeValue> notificationAttributes = new HashMap<String, MessageAttributeValue>();
notificationAttributes.put("AWS.SNS.MOBILE.BAIDU.DeployStatus",
new MessageAttributeValue().withDataType("String")
.withStringValue("1"));
notificationAttributes.put("AWS.SNS.MOBILE.BAIDU.MessageKey",
new MessageAttributeValue().withDataType("String")
.withStringValue("default-channel-msg-key"));
notificationAttributes.put("AWS.SNS.MOBILE.BAIDU.MessageType",
new MessageAttributeValue().withDataType("String")
.withStringValue("0"));
return notificationAttributes;
}
private static Map<String, MessageAttributeValue> addWNSNotificationAttributes() {
Map<String, MessageAttributeValue> notificationAttributes = new HashMap<String, MessageAttributeValue>();
notificationAttributes.put("AWS.SNS.MOBILE.WNS.CachePolicy",
new MessageAttributeValue().withDataType("String")
.withStringValue("cache"));
notificationAttributes.put("AWS.SNS.MOBILE.WNS.Type",
new MessageAttributeValue().withDataType("String")
.withStringValue("wns/badge"));
return notificationAttributes;
}
private static Map<String, MessageAttributeValue> addMPNSNotificationAttributes() {
Map<String, MessageAttributeValue> notificationAttributes = new HashMap<String, MessageAttributeValue>();
notificationAttributes.put("AWS.SNS.MOBILE.MPNS.Type",
new MessageAttributeValue().withDataType("String")
.withStringValue("token")); // This attribute is required.
notificationAttributes.put("AWS.SNS.MOBILE.MPNS.NotificationClass",
new MessageAttributeValue().withDataType("String")
.withStringValue("realtime")); // This attribute is required.
return notificationAttributes;
}
}
EDIT 2:
EDIT 3:
I changed the following code to:
AmazonSNS sns = new AmazonSNSClient(new PropertiesCredentials(
SNSMobilePush.class
.getResourceAsStream("AwsCredentials.properties")));
to
AmazonSNS sns = new AmazonSNSClient(new BasicAWSCredentials("ACCESS_KEY_REPLACED",
"SECRET_KEY_REPLACED"));
Now, there is a different error: Logcat
===========================================
Getting Started with Amazon SNS
===========================================
Exception in thread "main" java.lang.NoClassDefFoundError: org/xmlpull/v1/XmlPullParserException
at com.amazonaws.services.sns.AmazonSNSClient.invoke(AmazonSNSClient.java:2263)
at com.amazonaws.services.sns.AmazonSNSClient.createPlatformApplication(AmazonSNSClient.java:358)
at com.test.tools.AmazonSNSClientWrapper.createPlatformApplication(AmazonSNSClientWrapper.java:49)
at com.test.tools.AmazonSNSClientWrapper.demoNotification(AmazonSNSClientWrapper.java:119)
at com.test.SNSMobilePush.demoAndroidAppNotification(SNSMobilePush.java:104)
at com.test.SNSMobilePush.main(SNSMobilePush.java:71)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
Caused by: java.lang.ClassNotFoundException: org.xmlpull.v1.XmlPullParserException
at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 11 more
Process finished with exit code 1
I'm using sns v2.2.5 :
build.gradle > compile 'com.amazonaws:aws-android-sdk-sns:2.2.5'
Here is my solution for subscribe/unsubscribe:
public class AmazonPushClient {
private static final String TAG = "AmazonPushClient";
private static final String PLATFORM_APPLICATION_ARN = "*****";
private static final String IDENTITY_POOL_ID = "******";
private AmazonSNSClient mClient;
private boolean isUnregistering;
private Application mApp;
private NotifPreferencesHelper mNotifPreferencesHelper;
public AmazonPushClient(Application application) {
try {
mApp = application;
mClient = createPushClient(application);
mNotifPreferencesHelper = new NotifPreferencesHelper(application);
} catch (Exception e) {
LOGE(TAG, "AmazonPushClient", e);
}
}
#Nullable
private String token() {
try {
return InstanceID.getInstance(mApp).getToken(mApp.getString(R.string.gcm_defaultSenderId),
GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
} catch (Exception e) {
LOGW(TAG, "token", e);
return null;
}
}
private CognitoCachingCredentialsProvider cognitoCachingCredentialsProvider(Application application) {
return new CognitoCachingCredentialsProvider(
application,
IDENTITY_POOL_ID,
Regions.EU_WEST_1 // if your identity_pool_id start with : eu-west-1
);
}
private AmazonSNSClient createPushClient(Application application) {
AmazonSNSClient client = new AmazonSNSClient(cognitoCachingCredentialsProvider(application));
client.setRegion(Region.getRegion(Regions.EU_WEST_1));
client.addRequestHandler(mHandler);
return client;
}
public void pRegister() {
synchronized (TAG) {
LOGD(TAG, "registering");
isUnregistering = true;
String token = token();
if(TextUtils.isEmpty(token)) {
return;
}
mNotifPreferencesHelper.saveNotificationPreferences(true);
CreatePlatformEndpointRequest platformEndpointRequest = new CreatePlatformEndpointRequest();
platformEndpointRequest.setToken(token());
platformEndpointRequest.setPlatformApplicationArn(PLATFORM_APPLICATION_ARN);
CreatePlatformEndpointResult result = mClient.createPlatformEndpoint(platformEndpointRequest);
mNotifPreferencesHelper.storeEndpointArn(result.getEndpointArn());
}
}
public void pUnregister() {
synchronized (TAG) {
LOGD(TAG, "unregistering");
isUnregistering = false;
mNotifPreferencesHelper.saveNotificationPreferences(false);
DeleteEndpointRequest deletePlatformApplicationRequest = new DeleteEndpointRequest();
deletePlatformApplicationRequest.setEndpointArn(mNotifPreferencesHelper.getEndpointArn());
mClient.deleteEndpoint(deletePlatformApplicationRequest);
}
}
private RequestHandler2 mHandler = new RequestHandler2() {
#Override
public void beforeRequest(Request<?> request) {
}
#Override
public void afterResponse(Request<?> request, Response<?> response) {
if (isUnregistering) {
mNotifPreferencesHelper.storeEndpointArn(null);
}
}
#Override
public void afterError(Request<?> request, Response<?> response, Exception e) {
}
};
}
NotifPreferencesHelper is just something to store the EndpointARN. You have to use this class in a background thread
In association you have to implements GcmListenerService etc.. SNS is just for subscribe, not receive.
https://developers.google.com/cloud-messaging/
With respect to the Properties Credentials
A. Are you sure you are exporting the file in the build? Have you made sure you can access the file using regular file I/O outside of the credentials provider? Are you sure the file is formatted correctly (see https://github.com/aws/aws-sdk-android/blob/master/aws-android-sdk-core/src/main/java/com/amazonaws/auth/PropertiesCredentials.java)
accessKey=KEY
secretKey=SECRET
Also looking at the source you should be able to load this file yourself using http://developer.android.com/reference/java/util/Properties.html.
However, I highly recommend not using this credentials provider. It is extremely unsafe in a mobile applications. Anyone could decompile your app and steal your credentials. A much safer approach is to use Amazon Cognito, which there is a plethora of examples. (Bottom of https://docs.aws.amazon.com/mobile/sdkforandroid/developerguide/setup.html , any of the examples here: https://docs.aws.amazon.com/mobile/sdkforandroid/developerguide/getting-started-android.html as well as samples in the GitHub repo https://github.com/awslabs/aws-sdk-android-samples
Cognito requires a little bit of set-up but the guides are tested, and it really doesn't take much more than a few minutes to be much more secure.
Don't think you should import 'SNSMobilePush' to a module of your android application
In the same application, i created a new module called snspush and
imported SNSMobilePush.java file into it. Also imported the
AwsCredentials.properties file to the same path as that of
SNSMobilePush.java. Added the keys in AwsCredentials.properties file.
SNSMobilePush is just a Java app provided by AWS to do tasks like
upload (bulkupload package) several tokens (device tokens or registration IDs) to Amazon SNS or
send a push notification.
You need to register your mobile app with AWS (using AndroidMobilePushApp android app). You should obtain below information (refer this link)
Client ID and client secret
API key
Device token or Registration ID (per device)
Then you can use SNSMobilePush java app or even a AWS SNS console as described here to send push notification to your registered device.
I would suggest you to try out sending push notification from the console instead of java app. You can register tokens from devices that will install your app in the future as described in the one of the options (preferably the last option) as described here

Store more than one value in Android app preferences using the same key

I'm not so sure on how can I store more than one value in shared preferences on an Android app using the same key.
What the app do is show a list and a button to add that item to favorites I want to store that numeric value to preferences and when the user go to the favorite list send all the stored favorites through http post in an array.
EDIT: I'm doing it this way but when I add a new value it overrides the last one, check implode method, I add the new value to the stored list empty or not it has to add the new value and preserve the last ones.
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager;
public class FavoriteActivity {
private static String FAVORITE_LIST = "FAV_LIST";
private static SharedPreferences sharedPreference;
private static Editor sharedPrefEditor;
public static List<NameValuePair> getFavoriteList(Context context) {
if (sharedPreference == null) {
sharedPreference = PreferenceManager
.getDefaultSharedPreferences(context);
}
String storedList = sharedPreference.getString(FAVORITE_LIST, "");
return explode(storedList);
}
public static void saveFavorite(String fav, Context context) {
if (sharedPreference == null) {
sharedPreference = PreferenceManager
.getDefaultSharedPreferences(context);
}
sharedPrefEditor = sharedPreference.edit();
implode(getFavoriteList(context), fav, 1);
sharedPrefEditor.putString(FAVORITE_LIST, fav);
sharedPrefEditor.commit();
}
public static List<NameValuePair> explode(String string) {
StringTokenizer st = new StringTokenizer(string, ",");
List<NameValuePair> v = new ArrayList<NameValuePair>();
for (; st.hasMoreTokens();) {
v.add(new BasicNameValuePair("id[]", st.nextToken()));
}
return v;
}
public static String implode(List<NameValuePair> list, String value,
int mode) {
StringBuffer out = new StringBuffer();
switch (mode) {
case 0:
list.remove(new BasicNameValuePair("id[]", value));
break;
case 1:
list.add(new BasicNameValuePair("id[]", value));
break;
}
boolean first = true;
for (NameValuePair v : list) {
if (first)
first = false;
else
out.append(",");
out.append(v.getValue());
}
return out.toString();
}
}
You can't do this. If you use the same key anywhere, it will overwrite the previous value.
Perhaps you could convert your values into an array and store the array, or maybe look into using an SQLite database, in which you can specify the keys in one column, and the corresponding value in another and then run a SELECT statement that selects all rows with the key in it.
Since api 11 you can put StringSet. Another way is not possible.
OK, this is the way I did it and works perfect, also it doesn't add any duplicates.
private static String FAVORITE_LIST = "FAV_LIST";
private static SharedPreferences sharedPreference;
private static Editor sharedPrefEditor;
public static List<NameValuePair> getFavoriteList(Context context) {
if (sharedPreference == null) {
sharedPreference = PreferenceManager
.getDefaultSharedPreferences(context);
}
String storedList = sharedPreference.getString(FAVORITE_LIST, "");
return explode(storedList);
}
public static void saveFavorite(String fav, Context context) {
modifyFavorite(fav, context, 1);
}
public static void removeFavorite(String fav, Context context) {
modifyFavorite(fav, context, 0);
}
private static void modifyFavorite(String fav, Context context, int mode) {
if (sharedPreference == null) {
sharedPreference = PreferenceManager
.getDefaultSharedPreferences(context);
}
sharedPrefEditor = sharedPreference.edit();
String newList = implode(getFavoriteList(context), fav, mode);
sharedPrefEditor.putString(FAVORITE_LIST, newList);
sharedPrefEditor.commit();
}
private static List<NameValuePair> explode(String string) {
StringTokenizer st = new StringTokenizer(string, ",");
List<NameValuePair> v = new ArrayList<NameValuePair>();
for (; st.hasMoreTokens();) {
v.add(new BasicNameValuePair("id[]", st.nextToken()));
}
return v;
}
private static String implode(List<NameValuePair> list, String value,
int mode) {
StringBuffer out = new StringBuffer();
switch (mode) {
case 0:
list.remove(new BasicNameValuePair("id[]", value));
break;
case 1:
list.add(new BasicNameValuePair("id[]", value));
break;
}
boolean first = true;
for (NameValuePair v : list) {
if (out.lastIndexOf(v.getValue()) == -1) {
if (first) {
first = false;
} else {
out.append(",");
}
out.append(v.getValue());
}
}
return out.toString();
}

Android consuming RestService with/without cache

I am currently in the process of creating a high performance mobile application. Now i am looking at various design patterns for consuming rest services. One such pattern that stands out is the Google IO discussion here. How i have am looking at the code to develop this design. I will be using Spring Rest for doing the actual HTTP Rest and serialization to POJO with the Serialization Library. I came across this implementation here, and will be using it as a blue print for my application. Now a major question is here.
public interface HttpMethods {
public Object getForObject(Object ... params);
public Object putForObject(Object ... params);
}
public class LocationsHttpMethods implements HttpMethods{
private final Context mContext;
public LocationsHttpMethods(Context context)
{
mContext=context;
}
#Override
public Location[] getForObject(Object... params) {
return null;
}
#Override
public Object putForObject(Object... params) {
return null;
}
}
My Location is just a pojo class. Now the question that troubles me is that the second link that i have given just uses Boolean to return data. I will be returning an array of something.
package com.confiz.rest.services;
import java.util.ArrayList;
import java.util.HashMap;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import com.confiz.rest.providers.IProvider;
import com.confiz.rest.providers.LocationsProvider;
public class ProcessorService extends Service
{
private Integer lastStartId;
private final Context mContext = this;
/**
* The keys to be used for the required actions to start this service.
*/
public static class Extras
{
/**
* The provider which the called method is on.
*/
public static final String PROVIDER_EXTRA = "PROVIDER_EXTRA";
/**
* The method to call.
*/
public static final String METHOD_EXTRA = "METHOD_EXTRA";
/**
* The action to used for the result intent.
*/
public static final String RESULT_ACTION_EXTRA = "RESULT_ACTION_EXTRA";
/**
* The extra used in the result intent to return the result.
*/
public static final String RESULT_EXTRA = "RESULT_EXTRA";
}
private final HashMap<String, AsyncServiceTask> mTasks = new HashMap<String, AsyncServiceTask>();
/**
* Identifier for each supported provider.
* Cannot use 0 as Bundle.getInt(key) returns 0 when the key does not exist.
*/
public static class Providers
{
public static final int LOATIONS_PROVIDER = 1;
}
private IProvider GetProvider(int providerId)
{
switch(providerId)
{
case Providers.LOATIONS_PROVIDER:
return new LocationsProvider(this);
}
return null;
}
/**
* Builds a string identifier for this method call.
* The identifier will contain data about:
* What processor was the method called on
* What method was called
* What parameters were passed
* This should be enough data to identify a task to detect if a similar task is already running.
*/
private String getTaskIdentifier(Bundle extras)
{
String[] keys = extras.keySet().toArray(new String[0]);
java.util.Arrays.sort(keys);
StringBuilder identifier = new StringBuilder();
for (int keyIndex = 0; keyIndex < keys.length; keyIndex++)
{
String key = keys[keyIndex];
// The result action may be different for each call.
if (key.equals(Extras.RESULT_ACTION_EXTRA))
{
continue;
}
identifier.append("{");
identifier.append(key);
identifier.append(":");
identifier.append(extras.get(key).toString());
identifier.append("}");
}
return identifier.toString();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
// This must be synchronised so that service is not stopped while a new task is being added.
synchronized (mTasks)
{
// stopSelf will be called later and if a new task is being added we do not want to stop the service.
lastStartId = startId;
Bundle extras = intent.getExtras();
String taskIdentifier = getTaskIdentifier(extras);
Log.i("ProcessorService", "starting " + taskIdentifier);
// If a similar task is already running then lets use that task.
AsyncServiceTask task = mTasks.get(taskIdentifier);
if (task == null)
{
task = new AsyncServiceTask(taskIdentifier, extras);
mTasks.put(taskIdentifier, task);
// AsyncTasks are by default only run in serial (depending on the android version)
// see android documentation for AsyncTask.execute()
task.execute((Void[]) null);
}
// Add this Result Action to the task so that the calling activity can be notified when the task is complete.
String resultAction = extras.getString(Extras.RESULT_ACTION_EXTRA);
if (resultAction != "")
{
task.addResultAction(extras.getString(Extras.RESULT_ACTION_EXTRA));
}
}
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent)
{
return null;
}
public class AsyncServiceTask extends AsyncTask<Void, Void, Object>
{
private final Bundle mExtras;
private final ArrayList<String> mResultActions = new ArrayList<String>();
private final String mTaskIdentifier;
/**
* Constructor for AsyncServiceTask
*
* #param taskIdentifier A string which describes the method being called.
* #param extras The Extras from the Intent which was used to start this method call.
*/
public AsyncServiceTask(String taskIdentifier, Bundle extras)
{
mTaskIdentifier = taskIdentifier;
mExtras = extras;
}
public void addResultAction(String resultAction)
{
if (!mResultActions.contains(resultAction))
{
mResultActions.add(resultAction);
}
}
#Override
protected Object doInBackground(Void... params)
{
Log.i("ProcessorService", "working " + mTaskIdentifier);
Object result = false;
final int providerId = mExtras.getInt(Extras.PROVIDER_EXTRA);
final int methodId = mExtras.getInt(Extras.METHOD_EXTRA);
if (providerId != 0 && methodId != 0)
{
final IProvider provider = GetProvider(providerId);
if (provider != null)
{
try
{
result = provider.RunTask(methodId, mExtras);
} catch (Exception e)
{
result = false;
}
}
}
return result;
}
#Override
protected void onPostExecute(Object result)
{
// This must be synchronised so that service is not stopped while a new task is being added.
synchronized (mTasks)
{
Log.i("ProcessorService", "finishing " + mTaskIdentifier);
// Notify the caller(s) that the method has finished executing
for (int i = 0; i < mResultActions.size(); i++)
{
Intent resultIntent = new Intent(mResultActions.get(i));
//What to do here
resultIntent.put(Extras.RESULT_EXTRA, true);
//What to do here ends.
resultIntent.putExtras(mExtras);
resultIntent.setPackage(mContext.getPackageName());
mContext.sendBroadcast(resultIntent);
}
// The task is complete so remove it from the running tasks list
mTasks.remove(mTaskIdentifier);
// If there are no other executing methods then stop the service
if (mTasks.size() < 1)
{
stopSelf(lastStartId);
}
}
}
}
}
Now if you browse to the code that contain the AsyncService, and puts the resultIntent.put(Extras.RESULT_EXTRA, true);
Now how should i pass the data back to the intent. I heard Serializable is bad, and Parceable is ugly code. What else can i use. Secondly, where do i add the SQL cache retrieve code. How can i add this code to the framework. Hope i make sense.

Android WebView -> Display WebArchive

Android's WebView has this saveWebArchive method since API level 11: http://developer.android.com/.
It can save entire websites as webarchives, which is great! But how do I get the downloaded contents back into a webview? I tried
webview.loadUrl(Uri.fromFile(mywebarchivefile));
But that only displays xml on the screen.
Update Feb. 21, 2014
My answer posted below does not apply to web archive files saved under Android 4.4 KitKat and newer. The saveWebArchive() method of WebView under Android 4.4 "KitKat" (and probably newer versions too) does not save the web archive in XML code that this reader code posted below. Instead it saves pages in MHT (MHTML) format. It is easy to read back the .mht files - just use:
webView.loadUrl("file:///my_dir/mySavedWebPage.mht");
That's all, much easier than the previous method, and compatible with other platforms.
Previously posted
I needed it myself, and everywhere I searched, there were unanswered questions like this. So I had to work it out myself. Below is my little WebArchiveReader class and sample code on how to use it. Please note that despite the Android docs declaring that shouldInterceptRequest() was added to WebViewClient in API11 (Honeycomb), this code works and was tested successfully in Android emulators down to API8 (Froyo). Below is all the code that's needed, I also uploaded the full project to GitHub repository at https://github.com/gregko/WebArchiveReader
File WebArchiveReader.java:
package com.hyperionics.war_test;
import android.util.Base64;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.w3c.dom.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
public abstract class WebArchiveReader {
private Document myDoc = null;
private static boolean myLoadingArchive = false;
private WebView myWebView = null;
private ArrayList<String> urlList = new ArrayList<String>();
private ArrayList<Element> urlNodes = new ArrayList<Element>();
abstract void onFinished(WebView webView);
public boolean readWebArchive(InputStream is) {
DocumentBuilderFactory builderFactory =
DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
myDoc = null;
try {
builder = builderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
try {
myDoc = builder.parse(is);
NodeList nl = myDoc.getElementsByTagName("url");
for (int i = 0; i < nl.getLength(); i++) {
Node nd = nl.item(i);
if(nd instanceof Element) {
Element el = (Element) nd;
// siblings of el (url) are: mimeType, textEncoding, frameName, data
NodeList nodes = el.getChildNodes();
for (int j = 0; j < nodes.getLength(); j++) {
Node node = nodes.item(j);
if (node instanceof Text) {
String dt = ((Text)node).getData();
byte[] b = Base64.decode(dt, Base64.DEFAULT);
dt = new String(b);
urlList.add(dt);
urlNodes.add((Element) el.getParentNode());
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
myDoc = null;
}
return myDoc != null;
}
private byte [] getElBytes(Element el, String childName) {
try {
Node kid = el.getFirstChild();
while (kid != null) {
if (childName.equals(kid.getNodeName())) {
Node nn = kid.getFirstChild();
if (nn instanceof Text) {
String dt = ((Text)nn).getData();
return Base64.decode(dt, Base64.DEFAULT);
}
}
kid = kid.getNextSibling();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public boolean loadToWebView(WebView v) {
myWebView = v;
v.setWebViewClient(new WebClient());
WebSettings webSettings = v.getSettings();
webSettings.setDefaultTextEncodingName("UTF-8");
myLoadingArchive = true;
try {
// Find the first ArchiveResource in myDoc, should be <ArchiveResource>
Element ar = (Element) myDoc.getDocumentElement().getFirstChild().getFirstChild();
byte b[] = getElBytes(ar, "data");
// Find out the web page charset encoding
String charset = null;
String topHtml = new String(b).toLowerCase();
int n1 = topHtml.indexOf("<meta http-equiv=\"content-type\"");
if (n1 > -1) {
int n2 = topHtml.indexOf('>', n1);
if (n2 > -1) {
String tag = topHtml.substring(n1, n2);
n1 = tag.indexOf("charset");
if (n1 > -1) {
tag = tag.substring(n1);
n1 = tag.indexOf('=');
if (n1 > -1) {
tag = tag.substring(n1+1);
tag = tag.trim();
n1 = tag.indexOf('\"');
if (n1 < 0)
n1 = tag.indexOf('\'');
if (n1 > -1) {
charset = tag.substring(0, n1).trim();
}
}
}
}
}
if (charset != null)
topHtml = new String(b, charset);
else
topHtml = new String(b);
String baseUrl = new String(getElBytes(ar, "url"));
v.loadDataWithBaseURL(baseUrl, topHtml, "text/html", "UTF-8", null);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
private class WebClient extends WebViewClient {
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if (!myLoadingArchive)
return null;
int n = urlList.indexOf(url);
if (n < 0)
return null;
Element parentEl = urlNodes.get(n);
byte [] b = getElBytes(parentEl, "mimeType");
String mimeType = b == null ? "text/html" : new String(b);
b = getElBytes(parentEl, "textEncoding");
String encoding = b == null ? "UTF-8" : new String(b);
b = getElBytes(parentEl, "data");
return new WebResourceResponse(mimeType, encoding, new ByteArrayInputStream(b));
}
#Override
public void onPageFinished(WebView view, String url)
{
// our WebClient is no longer needed in view
view.setWebViewClient(null);
myLoadingArchive = false;
onFinished(myWebView);
}
}
}
Here is how to use this class, sample MyActivity.java class:
package com.hyperionics.war_test;
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import java.io.IOException;
import java.io.InputStream;
public class MyActivity extends Activity {
// Sample WebViewClient in case it was needed...
// See continueWhenLoaded() sample function for the best place to set it on our webView
private class MyWebClient extends WebViewClient {
#Override
public void onPageFinished(WebView view, String url)
{
Lt.d("Web page loaded: " + url);
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
WebView webView = (WebView) findViewById(R.id.webView);
try {
InputStream is = getAssets().open("TestHtmlArchive.xml");
WebArchiveReader wr = new WebArchiveReader() {
void onFinished(WebView v) {
// we are notified here when the page is fully loaded.
continueWhenLoaded(v);
}
};
// To read from a file instead of an asset, use:
// FileInputStream is = new FileInputStream(fileName);
if (wr.readWebArchive(is)) {
wr.loadToWebView(webView);
}
} catch (IOException e) {
e.printStackTrace();
}
}
void continueWhenLoaded(WebView webView) {
Lt.d("Page from WebArchive fully loaded.");
// If you need to set your own WebViewClient, do it here,
// after the WebArchive was fully loaded:
webView.setWebViewClient(new MyWebClient());
// Any other code we need to execute after loading a page from a WebArchive...
}
}
To make things complete, here is my little Lt.java class for debug output:
package com.hyperionics.war_test;
import android.util.Log;
public class Lt {
private static String myTag = "war_test";
private Lt() {}
static void setTag(String tag) { myTag = tag; }
public static void d(String msg) {
// Uncomment line below to turn on debug output
Log.d(myTag, msg == null ? "(null)" : msg);
}
public static void df(String msg) {
// Forced output, do not comment out - for exceptions etc.
Log.d(myTag, msg == null ? "(null)" : msg);
}
}
Hope this is helpful.
Update July 19, 2013
Some web pages don't have meta tag specifying text encoding, and then the code we show above does not display the characters correctly. In the GitHub version of this code I now added charset detection algorithm, which guesses the encoding in such cases. Again, see https://github.com/gregko/WebArchiveReader
Greg
I've found an undocumented way of reading saved webarchive. Just do:
String raw_data = (read the mywebarchivefile as a string)
and then call
webview.loadDataWithBaseURL(mywebarchivefile, raw_data, "application/x-webarchive-xml", "UTF-8", null);
The reference:
http://androidxref.com/4.0.4/xref/external/webkit/Source/WebCore/loader/archive/ArchiveFactory.cpp
Available from Android 3.0, api level 11.

How to serialize a Bundle?

I'd like to serialize a Bundle object, but can't seem to find a simple way of doing it. Using Parcel doesn't seem like an option, since I want to store the serialized data to file.
Any ideas on ways to do this?
The reason I want this is to save and restore the state of my activity, also when it's killed by the user. I already create a Bundle with the state I want to save in onSaveInstanceState. But android only keeps this Bundle when the activity is killed by the SYSTEM. When the user kills the activity, I need to store it myself. Hence i'd like to serialize and store it to file. Of course, if you have any other way of accomplishing the same thing, i'd be thankful for that too.
Edit:
I decided to encode my state as a JSONObject instead of a Bundle. The JSON object can then be put in a Bundle as a Serializable, or stored to file. Probably not the most efficient way, but it's simple, and it seems to work ok.
storing any Parcelable to a file is very easy:
FileOutputStream fos = context.openFileOutput(localFilename, Context.MODE_PRIVATE);
Parcel p = Parcel.obtain(); // i make an empty one here, but you can use yours
fos.write(p.marshall());
fos.flush();
fos.close();
enjoy!
I use SharedPreferences to get around that limitation, it uses the same putXXX() and getXXX() style of storing and retrieving data as the Bundle class does and is relatively simple to implement if you have used a Bundle before.
So in onCreate I have a check like this
if(savedInstanceState != null)
{
loadGameDataFromSavedInstanceState(savedInstanceState);
}
else
{
loadGameDataFromSharedPreferences(getPreferences(MODE_PRIVATE));
}
I save my game data to a Bundle in onSaveInstanceState(), and load data from a Bundle in onRestoreInstanceState()
AND
I also save game data to SharedPreferences in onPause(), and load data from SharedPreferences in onResume()
onPause()
{
// get a SharedPreferences editor for storing game data to
SharedPreferences.Editor mySharedPreferences = getPreferences(MODE_PRIVATE).edit();
// call a function to actually store the game data
saveGameDataToSharedPreferences(mySharedPreferences);
// make sure you call mySharedPreferences.commit() at the end of your function
}
onResume()
{
loadGameDataFromSharedPreferences(getPreferences(MODE_PRIVATE));
}
I wouldn't be surprised if some people feel this is an incorrect use of SharedPreferences, but it gets the job done. I have been using this method in all my games (nearly 2 million downloads) for over a year and it works.
Convert it to SharedPreferences:
private void saveToPreferences(Bundle in) {
Parcel parcel = Parcel.obtain();
String serialized = null;
try {
in.writeToParcel(parcel, 0);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.write(parcel.marshall(), bos);
serialized = Base64.encodeToString(bos.toByteArray(), 0);
} catch (IOException e) {
Log.e(getClass().getSimpleName(), e.toString(), e);
} finally {
parcel.recycle();
}
if (serialized != null) {
SharedPreferences settings = getSharedPreferences(PREFS, 0);
Editor editor = settings.edit();
editor.putString("parcel", serialized);
editor.commit();
}
}
private Bundle restoreFromPreferences() {
Bundle bundle = null;
SharedPreferences settings = getSharedPreferences(PREFS, 0);
String serialized = settings.getString("parcel", null);
if (serialized != null) {
Parcel parcel = Parcel.obtain();
try {
byte[] data = Base64.decode(serialized, 0);
parcel.unmarshall(data, 0, data.length);
parcel.setDataPosition(0);
bundle = parcel.readBundle();
} finally {
parcel.recycle();
}
}
return bundle;
}
In case you want to store it in persistent storage you can't rely on parcelable nor serializable mechanism. You have to do it by yourself and below is the way how I usually do it:
private static final Gson sGson = new GsonBuilder().create();
private static final String CHARSET = "UTF-8";
// taken from http://www.javacamp.org/javaI/primitiveTypes.html
private static final int BOOLEAN_LEN = 1;
private static final int INTEGER_LEN = 4;
private static final int DOUBLE_LEN = 8;
public static byte[] serializeBundle(Bundle bundle) {
try {
List<SerializedItem> list = new ArrayList<>();
if (bundle != null) {
Set<String> keys = bundle.keySet();
for (String key : keys) {
Object value = bundle.get(key);
if (value == null) continue;
SerializedItem bis = new SerializedItem();
bis.setClassName(value.getClass().getCanonicalName());
bis.setKey(key);
if (value instanceof String)
bis.setValue(((String) value).getBytes(CHARSET));
else if (value instanceof SpannableString) {
String str = Html.toHtml((Spanned) value);
bis.setValue(str.getBytes(CHARSET));
} else if (value.getClass().isAssignableFrom(Integer.class)) {
ByteBuffer b = ByteBuffer.allocate(INTEGER_LEN);
b.putInt((Integer) value);
bis.setValue(b.array());
} else if (value.getClass().isAssignableFrom(Double.class)) {
ByteBuffer b = ByteBuffer.allocate(DOUBLE_LEN);
b.putDouble((Double) value);
bis.setValue(b.array());
} else if (value.getClass().isAssignableFrom(Boolean.class)) {
ByteBuffer b = ByteBuffer.allocate(INTEGER_LEN);
boolean v = (boolean) value;
b.putInt(v ? 1 : 0);
bis.setValue(b.array());
} else
continue; // we do nothing in this case since there is amazing amount of stuff you can put into bundle but if you want something specific you can still add it
// throw new IllegalStateException("Unable to serialize class + " + value.getClass().getCanonicalName());
list.add(bis);
}
return sGson.toJson(list).getBytes(CHARSET);
}
} catch (Exception e) {
e.printStackTrace();
}
throw new IllegalStateException("Unable to serialize " + bundle);
}
public static Bundle deserializeBundle(byte[] toDeserialize) {
try {
Bundle bundle = new Bundle();
if (toDeserialize != null) {
SerializedItem[] bundleItems = new Gson().fromJson(new String(toDeserialize, CHARSET), SerializedItem[].class);
for (SerializedItem bis : bundleItems) {
if (String.class.getCanonicalName().equals(bis.getClassName()))
bundle.putString(bis.getKey(), new String(bis.getValue()));
else if (Integer.class.getCanonicalName().equals(bis.getClassName()))
bundle.putInt(bis.getKey(), ByteBuffer.wrap(bis.getValue()).getInt());
else if (Double.class.getCanonicalName().equals(bis.getClassName()))
bundle.putDouble(bis.getKey(), ByteBuffer.wrap(bis.getValue()).getDouble());
else if (Boolean.class.getCanonicalName().equals(bis.getClassName())) {
int v = ByteBuffer.wrap(bis.getValue()).getInt();
bundle.putBoolean(bis.getKey(), v == 1);
} else
throw new IllegalStateException("Unable to deserialize class " + bis.getClassName());
}
}
return bundle;
} catch (Exception e) {
e.printStackTrace();
}
throw new IllegalStateException("Unable to deserialize " + Arrays.toString(toDeserialize));
}
You represent data as byte array which you can easily store to file, send via network or store to sql database using ormLite as follows:
#DatabaseField(dataType = DataType.BYTE_ARRAY)
private byte[] mRawBundle;
and SerializedItem:
public class SerializedItem {
private String mClassName;
private String mKey;
private byte[] mValue;
// + getters and setters
}
PS: the code above is dependent on Gson library (which is pretty common, just to let you know).

Categories

Resources