I am using the Google Backup API for SharedPreferences as described here:
http://developer.android.com/guide/topics/data/backup.html#RequestingRestore
When using bmgr, as described here:
http://developer.android.com/tools/help/bmgr.html
I do get the log messages for onRestore an onBackup methods, but in onRestore I get this line:
06-06 11:24:16.546: D/backup_data(24615): SKIP_PADDING FAILED at line 332
The only reference for it is here:
https://github.com/comex/frash/blob/master/utils/utils/BackupData.cpp
And no input is read.
My Helper class:
public class NoteBackupAgent extends BackupAgentHelper {
public static final Object[] DATA_LOCK = new Object[0];
private static final String PREFS_BACKUP_KEY = Const.ENTRY_PREFS;
#Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
Log.d(toString(), "########### onBackup() " + oldState.getStatSize());
synchronized (DATA_LOCK) {
super.onBackup(oldState, data, newState);
}
}
#Override
public void onCreate() {
SharedPreferencesBackupHelper preferencesHelper = new SharedPreferencesBackupHelper(this,
getPackageName() + "_preferences");
Toast.makeText(getApplicationContext(), "onCreate", Toast.LENGTH_LONG).show();
addHelper(PREFS_BACKUP_KEY, preferencesHelper);
}
#Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
synchronized (DATA_LOCK) {
//super.onRestore(data, appVersionCode, newState);
// There should be only one entity, but the safest
// way to consume it is using a while loop
StringBuilder total = new StringBuilder();
while (data.readNextHeader()) {
String key = data.getKey();
int dataSize = data.getDataSize();
// If the key is ours (for saving top score). Note this key was used when
// we wrote the backup entity header
if (PREFS_BACKUP_KEY.equals(key)) {
// Create an input stream for the BackupDataInput
byte[] dataBuf = new byte[dataSize];
data.readEntityData(dataBuf, 0, dataSize);
ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
DataInputStream in = new DataInputStream(baStream);
BufferedReader r = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = r.readLine()) != null) {
total.append(line);
}
Log.d(toString(), "########## data " + total);
} else {
data.skipEntityData();
}
}
Log.d(toString(), "########## onRestore()" + total.toString());
}
}
}
In our case the solution was preventing enforcing Base64 encodings on the values stored in our shared preferences.
As you mentoined #Demonick, https://github.com/comex/frash/blob/master/utils/utils/BackupData.cpp is the cause of the error, BackupDataReader::skip_padding() is using Base32 encoding for paddings.
Conclusion is:
Prevent using Base64.encodeToString(input, Base64.NO_PADDING) for writing shared preferences.
Related
In my Android application I am getting below exception when I try to sync my data to the server which is large in size. I get this exception when data size is more than 20 MB I think. I am saving bitmap images as string using base64 encoding after sacle down the image size which makes such huge data.
04-18 13:51:51.957 16199-16816/com.example.myproject.app E/art﹕ Throwing OutOfMemoryError "Failed to allocate a 128887990 byte allocation with 16777216 free bytes and 70MB until OOM"
04-18 13:51:52.037 16199-16816/com.example.myproject.app E/AndroidRuntime﹕ FATAL EXCEPTION: Thread-4482
Process: com.example.myproject.app, PID: 16199
java.lang.OutOfMemoryError: Failed to allocate a 128887990 byte allocation with 16777216 free bytes and 70MB until OOM
at java.lang.AbstractStringBuilder.enlargeBuffer(AbstractStringBuilder.java:95)
at java.lang.AbstractStringBuilder.append0(AbstractStringBuilder.java:146)
at java.lang.StringBuffer.append(StringBuffer.java:219)
at java.io.StringWriter.write(StringWriter.java:167)
at com.google.gson.stream.JsonWriter.string(JsonWriter.java:570)
at com.google.gson.stream.JsonWriter.value(JsonWriter.java:419)
at com.google.gson.internal.bind.TypeAdapters$16.write(TypeAdapters.java:426)
at com.google.gson.internal.bind.TypeAdapters$16.write(TypeAdapters.java:410)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:112)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:239)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:97)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:61)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
How to solve this problem? I know that this comes when I convert the data from class to json using Gson. Below is my code:
SimpleDateFormat dtf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.ENGLISH);
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
#Override
public Date deserialize(JsonElement json, Type type, JsonDeserializationContext deserializationContext) throws JsonParseException {
String frStr = json.getAsJsonPrimitive().getAsString();
Date retDate =null;
try {
retDate = dtf.parse(frStr);
} catch (ParseException e) {
e.printStackTrace();
}
return retDate;
}
});
builder.registerTypeAdapter(Date.class, new JsonSerializer<Date>() {
#Override
public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
String jsDate = dtf.format(src);
return new JsonPrimitive(jsDate);
}
});
builder.registerTypeAdapter(byte[].class, new JsonDeserializer<byte[]>() {
#Override
public byte[] deserialize(JsonElement json, Type type, JsonDeserializationContext deserializationContext) throws JsonParseException {
return Base64.decode(json.getAsString(), Base64.NO_WRAP);
}
});
gson = builder.create();
attDataAcc.setAttList(attList);
String jsonAttAccts = gson.toJson(attDataAcc, AttachmentDataList.class);
HttpEntity<String> entityAtt = new HttpEntity<String>(jsonAttAccts,headers);
ResponseEntity<String> restResA = restTemplate.exchange(strUrl+"/saveAttToServer", HttpMethod.POST, entityAtt, String.class);
public class Attachment implements Serializable {
#DatabaseField(columnName = "id",id = true)
private String id;
#DatabaseField(columnName = "user_id")
private Integer userId;
#DatabaseField(columnName = "attachment_id")
private String attachmentId;
#DatabaseField(columnName = "file_name")
private String fileName;
#DatabaseField(columnName = "file_data")
private String fileData;
#DatabaseField(columnName = "date",dataType=DataType.DATE)
private Date date;
public Attachment() {
super();
// TODO Auto-generated constructor stub
}
public Attachment(String id, Integer userId, String attachmentId, String fileName, String fileData, Date date) {
this.id = id;
this.userId = userId;
this.attachmentId = attachmentId;
this.fileName = fileName;
this.fileData = fileData;
this.date = date;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getAttachmentId() {
return attachmentId;
}
public void setAttachmentId(String attachmentId) {
this.attachmentId = attachmentId;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileData() {
return fileData;
}
public void setFileData(String fileData) {
this.fileData = fileData;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Attachment that = (Attachment) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false;
if (attachmentId != null ? !attachmentId.equals(that.attachmentId) : that.attachmentId != null) return false;
if (fileName != null ? !fileName.equals(that.fileName) : that.fileName != null) return false;
if (fileData != null ? !fileData.equals(that.fileData) : that.fileData != null) return false;
if (date != null ? !date.equals(that.date) : that.date != null) return false;
}
#Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (userId != null ? userId.hashCode() : 0);
result = 31 * result + (attachmentId != null ? attachmentId.hashCode() : 0);
result = 31 * result + (fileName != null ? fileName.hashCode() : 0);
result = 31 * result + (fileData != null ? fileData.hashCode() : 0);
result = 31 * result + (date != null ? date.hashCode() : 0);
return result;
}
#Override
public String toString() {
return userFileName;
}
}
public class AttachmentDataList implements Serializable {
private ArrayList<Attachment> attList;
public ArrayList<Attachment> getAttList() {
return attList;
}
public void setAttList(ArrayList<Attachment> attList) {
this.attList = attList;
}
}
You're running into OutOfMemoryError because you're using inefficient and very memory-consuming Base64 transformations. Another hit here is Gson: it does not provide any raw-write methods for JsonWriter and JsonReader classes: the most you can do here is writing/reading a single string value. Collecting a huge input to a single string is another very memory-consuming operation: check your stacktrace to make sure that a string builder instance is used under the very hood -- and this is just to write a single value to the output stream. In short, it all looks like this (if I'm not mistaken about your code, because it seems to lack really important parts, so I'm just trying to reconstruct your scenario):
Obtaining a byte array (that would be a new object, probably a clone of another byte array);
Converting the byte array to a Base64 encoded string (it also hits performance because the would clone the byte array to create a defensive copy);
Converting ALL to a String gson.toJson(attDataAcc, AttachmentDataList.class); -- another huge hit.
All of this is extremely memory consuming. And it would be nice if Gson could support raw write to the output stream, but currently it lacks any of it.
In theory, you might overcome this issue by just writing to the underlying streams (probably directly from your byte array source without any massive transformations because Base64 can be streamed as well thus consuming memory minimum). You mentioned Gson 2.6.2, but I'm working with Gson 2.8.0, so the below solution can 100% work with Gson 2.8.0 only, and may not work even for any other minor Gson versions because it uses reflection in order to "hack" the JsonWriter class.
final class ByteArrayTypeAdapter
extends TypeAdapter<byte[]> {
// These two methods and one field from the super class privates are necessary to make it all work
private static final Method writeDeferredNameMethod;
private static final Method beforeValueMethod;
private static final Field writerField;
static {
try {
writeDeferredNameMethod = JsonWriter.class.getDeclaredMethod("writeDeferredName");
writeDeferredNameMethod.setAccessible(true);
beforeValueMethod = JsonWriter.class.getDeclaredMethod("beforeValue");
beforeValueMethod.setAccessible(true);
writerField = JsonWriter.class.getDeclaredField("out");
writerField.setAccessible(true);
} catch ( final NoSuchMethodException | NoSuchFieldException ex ) {
throw new RuntimeException(ex);
}
}
// This type adapter is effectively a singleton having no any internal state
private static final TypeAdapter<byte[]> byteArrayTypeAdapter = new ByteArrayTypeAdapter();
private ByteArrayTypeAdapter() {
}
// But making the constructor private and providing access to the instance via the method, we make sure that the only instance exists and it's safe
static TypeAdapter<byte[]> getByteArrayTypeAdapter() {
return byteArrayTypeAdapter;
}
#Override
public void write(final JsonWriter out, final byte[] bytes)
throws IOException {
try {
// Since we're writing a byte[] array, that's probably a field value, make sure that the corresponding property name has been written to the output stream
writeDeferredNameAndFlush(out);
// Now simulate JsonWriter.value(byte[]) if such a method could exist
writeRawBase64ValueAndFlush(bytes, (Writer) writerField.get(out));
} catch ( IllegalAccessException | InvocationTargetException ex ) {
throw new IOException(ex);
}
}
#Override
public byte[] read(final JsonReader in) {
// If necessary, requires more hacks...
// And this is crucial for the server-side:
// In theory, the client can generate HUGE Base64 strings,
// So the server could crash with OutOfMemoryError too
throw new UnsupportedOperationException();
}
private static void writeDeferredNameAndFlush(final Flushable out)
throws IOException, IllegalAccessException, InvocationTargetException {
writeDeferredNameMethod.invoke(out);
beforeValueMethod.invoke(out);
// Flush is necessary: the JsonWriter does not know that we're using its private field intruding to its privates and may not flush
out.flush();
}
private static void writeRawBase64ValueAndFlush(final byte[] bytes, final Writer writer)
throws IOException {
// Writing leading "
writer.write('\"');
// This comes from Google Guava
final BaseEncoding baseEncoding = BaseEncoding.base64();
final OutputStream outputStream = baseEncoding.encodingStream(writer);
// This too
// Note that we just r_e_d_i_r_e_c_t streams on fly not making heavy transformations
ByteStreams.copy(new ByteArrayInputStream(bytes), outputStream);
// This is necessary too
outputStream.close();
// Writing trailing "
writer.write('\"');
// Flush again to keep it all in sync
writer.flush();
}
}
I know it's a hack, but it's better than just getting OutOfMemoryError constantly.
Now, just make it work with Spring RestTemplates:
// Gson is thread-safe and can be re-used
private static final Gson gson = new GsonBuilder()
// SimpleDateFormat may be NOT thread-safe so you should not share the single SimpleDateFormat between threads
// However Gson supports date/time formats out of box
.setDateFormat("yyyy-MM-dd HH:mm:ss")
// Registering byte[] to the type adapter
.registerTypeAdapter(byte[].class, getByteArrayTypeAdapter())
.create();
private static final RestTemplate restTemplate = new RestTemplate();
private static final String URL = "http://localhost";
public static void main(final String... args) {
sendPostRequest("hello world".getBytes(), byte[].class);
}
private static void sendPostRequest(final Object object, final Type type) {
// This is where we're binding the output stream I was asking in the question comments
final RequestCallback requestCallback = request -> gson.toJson(object, type, new OutputStreamWriter(request.getBody()));
// Spring RestTemplates stuff here...
final SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
final ResponseExtractor<String> responseExtractor = new HttpMessageConverterExtractor<>(String.class, restTemplate.getMessageConverters());
restTemplate.setRequestFactory(requestFactory);
// Let it fly
restTemplate.execute(URL, POST, requestCallback, responseExtractor);
}
Note that you might write a specialized type adapter for special types that could write directly to output stream so you could not get rid of byte[] at all. You could also vote up for this issue at the official Gson issue tracker: https://github.com/google/gson/issues/971 and probably there could no any need in using any Java Reflection API hacks in a future version of Gson.
In my application I have local service, which needs to be run in separate process. It is specified as
<service android:name=".MyService" android:process=":myservice"></service>
in AndroidManifest.xml. I also subclass Application object and want to detect in it's onCreate method when it is called by ordinary launch and when by myservice launch. The only working solution that I have found is described by
https://stackoverflow.com/a/28907058/2289482
But I don't want to get all running processes on device and iterate over them. I try to use getApplicationInfo().processName from Context, but unfortunately it always return the same String, while the solution in the link above return: myPackage, myPackage:myservice. I don't need processName at the first place, but some good solution to determine when onCreate method is called by ordinary launch and when by myservice launch. May be it can be done by applying some kind of tag or label somewhere, but i didn't find how to do it.
You can use this code to get your process name:
int myPid = android.os.Process.myPid(); // Get my Process ID
InputStreamReader reader = null;
try {
reader = new InputStreamReader(
new FileInputStream("/proc/" + myPid + "/cmdline"));
StringBuilder processName = new StringBuilder();
int c;
while ((c = reader.read()) > 0) {
processName.append((char) c);
}
// processName.toString() is my process name!
Log.v("XXX", "My process name is: " + processName.toString());
} catch (Exception e) {
// ignore
} finally {
if (reader != null) {
try {
reader.close();
} catch (Exception e) {
// Ignore
}
}
}
From Acra sources. The same way as above answer but presents useful methods
private static final String ACRA_PRIVATE_PROCESS_NAME= ":acra";
/**
* #return true if the current process is the process running the SenderService.
* NB this assumes that your SenderService is configured to used the default ':acra' process.
*/
public static boolean isACRASenderServiceProcess() {
final String processName = getCurrentProcessName();
if (ACRA.DEV_LOGGING) log.d(LOG_TAG, "ACRA processName='" + processName + '\'');
//processName sometimes (or always?) starts with the package name, so we use endsWith instead of equals
return processName != null && processName.endsWith(ACRA_PRIVATE_PROCESS_NAME);
}
#Nullable
private static String getCurrentProcessName() {
try {
return IOUtils.streamToString(new FileInputStream("/proc/self/cmdline")).trim();
} catch (IOException e) {
return null;
}
}
private static final Predicate<String> DEFAULT_FILTER = new Predicate<String>() {
#Override
public boolean apply(String s) {
return true;
}
};
private static final int NO_LIMIT = -1;
public static final int DEFAULT_BUFFER_SIZE_IN_BYTES = 8192;
/**
* Reads an InputStream into a string
*
* #param input InputStream to read.
* #return the String that was read.
* #throws IOException if the InputStream could not be read.
*/
#NonNull
public static String streamToString(#NonNull InputStream input) throws IOException {
return streamToString(input, DEFAULT_FILTER, NO_LIMIT);
}
/**
* Reads an InputStream into a string
*
* #param input InputStream to read.
* #param filter Predicate that should return false for lines which should be excluded.
* #param limit the maximum number of lines to read (the last x lines are kept)
* #return the String that was read.
* #throws IOException if the InputStream could not be read.
*/
#NonNull
public static String streamToString(#NonNull InputStream input, Predicate<String> filter, int limit) throws IOException {
final BufferedReader reader = new BufferedReader(new InputStreamReader(input), ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES);
try {
String line;
final List<String> buffer = limit == NO_LIMIT ? new LinkedList<String>() : new BoundedLinkedList<String>(limit);
while ((line = reader.readLine()) != null) {
if (filter.apply(line)) {
buffer.add(line);
}
}
return TextUtils.join("\n", buffer);
} finally {
safeClose(reader);
}
}
/**
* Closes a Closeable.
*
* #param closeable Closeable to close. If closeable is null then method just returns.
*/
public static void safeClose(#Nullable Closeable closeable) {
if (closeable == null) return;
try {
closeable.close();
} catch (IOException ignored) {
// We made out best effort to release this resource. Nothing more we can do.
}
}
You can use the next method
#Nullable
public static String getProcessName(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo processInfo : activityManager.getRunningAppProcesses()) {
if (processInfo.pid == android.os.Process.myPid()) {
return processInfo.processName;
}
}
return null;
}
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.
Actually ,I had searched some questions and go to the github. But I'm new ,I cannot understand the example.
I want to create the http server in android so I can access it in PC browser.
I had instance a class extend nanohttpd, but the server just don't work. I don't know why ,my computer and phone are in the same WIFI,uh.....
public class MyHTTPD extends NanoHTTPD {
/**
* Constructs an HTTP server on given port.
*/
public MyHTTPD()throws IOException {
super(8080);
}
#Override
public Response serve( String uri, Method method,
Map<String, String> header, Map<String, String> parms,
Map<String, String> files )
{
System.out.println( method + " '222" + uri + "' " );
String msg = "<html><body><h1>Hello server</h1>\n";
if ( parms.get("username") == null )
msg +=
"<form action='?' method='get'>\n" +
" <p>Your name: <input type='text' name='username'></p>\n" +
"</form>\n";
else
msg += "<p>Hello, " + parms.get("username") + "!</p>";
msg += "</body></html>\n";
return new NanoHTTPD.Response(msg );
}
public static void main( String[] args )
{
try
{
new MyHTTPD();
}
catch( IOException ioe )
{
System.err.println( "Couldn't start server:\n" + ioe );
System.exit( -1 );
}
System.out.println( "Listening on port 8080. Hit Enter to stop.\n" );
try { System.in.read(); } catch( Throwable t ) {
System.out.println("read error");
};
}
}
Your sample code is missing one small detail - you create the server but you never call the "start()" method which kicks it off to listen for incoming connections. In your main() method, you could write
(new MyHTTPD()).start();
and all would be well, your server would respond the way you hoped it would.
The reason it works that way is twofold: I want the constructor to be a cheap, inexpensive operation, without side-effects. For instance, while unit testing, I call "start()" in the setup and "stop()" in the teardown methods of my jUnit test.
This is the code working for me, but I have different version of NANOHTTPD, I don't have time right now to test out your solution. Here is UploadServer class and Nano class. I return file-upload.htm from sdcard/Discover Control/Web path
public class UploadServer extends NanoHTTPD {
public UploadServer() throws IOException {
super(8080, new File("."));
}
public Response serve( String uri, String method, Properties header, Properties parms, Properties files ) {
File rootsd = Environment.getExternalStorageDirectory();
File path = new File(rootsd.getAbsolutePath() + "/Discover Control/Web");
Response r = super.serveFile("/file-upload.htm", header, path, true);
return r;
}
}
NanoHttpd class
NanoHTTPD.java
FILE UPLOAD
file-upload.htm
Hope this helps and enjoy your work.
Android Activities have a lifecycle and do not use a main() function.
If you want to start and stop the webserver as part of the Activity then you need call start and stop
in onPause and onResume, ie
public class MyActivity extends Activity {
private MyHTTPD mServer;
#Override
protected void onResume() {
super.onResume();
try {
mServer = new MyHTTPD();
mServer.start();
} catch (IOException e) {
e.printStackTrace();
mServer = null;
}
#Override
protected void onPause() {
super.onPause();
if(mServer != null) {
mServer.stop();
mServer = null;
}
}
}
An alternative is to implement the webserver as part of a Service.
In an app I'm working I have a requirement to keep the webserver running even if the user leaves the app. The only way to do this is to start and stop the webserver as part of a long-running Service that is not bound to the Activity. See Vogella's great tutorial on Android Services.
This code working for fine viewing html pages with css class which are in my assesst folders
androidWebServer.start();
this will start the server below code for server functions
public class AndroidWebServer extends NanoHTTPD {
Realm realm;
Map<String, String> parms;
DBHelper db = new DBHelper(OpenRAP.getContext());
boolean isStartedHS = MainActivity.isStartedHS;
private AsyncHttpServer server = new AsyncHttpServer();
private AsyncServer mAsyncServer = new AsyncServer();
private String TAG = "androidwebserver";
Storage storage = new Storage(OpenRAP.getContext());
public AndroidWebServer(int port) {
super(port);
}
public AndroidWebServer(String hostname, int port) {
super(hostname, port);
}
#Override
public String getHostname() {
return super.getHostname();
}
#Override
public Response serve(IHTTPSession session) {
Method method = session.getMethod();
String uri = session.getUri();
Map<String, String> files = new HashMap<>();
SharedPreferences prefs = OpenRAP.getContext().getSharedPreferences(MainActivity.mypreference, MODE_PRIVATE);
OpenRAP app = (OpenRAP) OpenRAP.getContext();
Storage storage = new Storage(OpenRAP.getContext());
String currentpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/www/";
String temp = Environment.getExternalStorageDirectory().getAbsolutePath() + "/www/temp/";
String ecarpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/www/ecars_files/";
String xcontent = Environment.getExternalStorageDirectory().getAbsolutePath() + "/www/xcontent/";
String Endpoint = session.getUri();
if (Endpoint.equals("/")) {
String answer = "";
try {
// Open file from SD Card
File root = Environment.getExternalStorageDirectory().getAbsoluteFile();
FileReader index = new FileReader(root +
"/www/openrap/index.html");
BufferedReader reader = new BufferedReader(index);
String line = "";
while ((line = reader.readLine()) != null) {
answer += line;
}
reader.close();
} catch (IOException ioe) {
Log.w("Httpd", ioe.toString());
}
return newFixedLengthResponse(answer);
}
Due to simplicity i have a text file with entries separated by ; and parses every line into an object. The problem is that the text file contains almost 10 000 rows.
I also need to create keys for each object im parsing so i can filter the results in a search interface.
It takes almost 16 seconds in emulator to parse the text and add the keys. I'm i doing something wrong here? Or is there a more efficient way?
Here is my database singleton:
public class Database {
private static Database instance = null; private final Map<String, List<Stop>> mDict = new ConcurrentHashMap<String, List<Stop>>();
public static Database getInstance() { if (instance == null) { instance = new Database(); } return instance; } public List<Stop> getMatches(String query) {
List<Stop> list = mDict.get(query);
return list == null ? Collections.EMPTY_LIST : list;
}
private boolean mLoaded = false;
/**
* Loads the words and definitions if they haven't been loaded already.
*
* #param resources Used to load the file containing the words and definitions.
*/
public synchronized void ensureLoaded(final Resources resources) {
if (mLoaded) return;
new Thread(new Runnable() {
public void run() {
try {
loadStops(resources);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
private synchronized void loadStops(Resources resources) throws IOException
{
if (mLoaded) return;
Log.d("database", "loading stops");
InputStream inputStream = resources.openRawResource(R.raw.allstops);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
String line;
while((line = reader.readLine()) != null) {
String[] strings = TextUtils.split(line, ";");
addStop(strings[0], strings[1], strings[2]);
}
} finally {
reader.close();
}
Log.d("database", "loading stops completed");
mLoaded = true;
}
private void addStop(String name, String district, String id) {
Stop stop = new Stop(id, name, district);
int len = name.length();
for (int i = 0; i < len; i++) {
String prefix = name.substring(0, len - i).toLowerCase();
addMatch(prefix, stop);
}
}
private void addMatch(String query, Stop stop) {
List<Stop> matches = mDict.get(query);
if (matches == null) {
matches = new ArrayList<Stop>();
mDict.put(query, matches);
}
matches.add(stop);
}
}
Here is some sample data:
Mosseporten Senter;Norge;9021014089003000;59.445422;10.701055;273
Oslo Bussterminal;Norge;9021014089004000;59.911369;10.759665;273
Långegärde;Strömstad;9021014026420000;58.891462;11.007767;68
Västra bryggan;Strömstad;9021014026421000;58.893080;11.009997;7
Vettnet;Strömstad;9021014026422000;58.903184;11.020739;7
Ekenäs;Strömstad;9021014026410000;58.893610;11.048821;7
Kilesand;Strömstad;9021014026411000;58.878472;11.052983;7
Ramsö;Strömstad;9021014026430000;58.831531;11.067402;7
Sarpsborg;Norge;9021014089002000;59.280937;11.111763;273
Styrsö;Strömstad;9021014026180000;58.908110;11.115818;7
Capri/Källviken;Strömstad;9021014026440000;58.965200;11.124384;63
Lindholmens vändplan;Strömstad;9021014026156000;58.890212;11.128393;64
Öddö;Strömstad;9021014026190000;58.923490;11.130767;7
Källviksdalen;Strömstad;9021014026439000;58.962414;11.131962;64
Husevägen;Strömstad;9021014026505000;58.960094;11.133535;274
Caprivägen;Strömstad;9021014026284000;58.958404;11.134281;64
Stensviks korsväg;Strömstad;9021014026341000;59.001499;11.137203;63
Kungbäck;Strömstad;9021014026340000;59.006056;11.140313;63
Kase;Strömstad;9021014026173000;58.957649;11.141904;274
You should add the information into a SQLite database and ship the app with the database in res/raw.
Additionally, the db file can often be effectively compressed into a zip file.
See this for more information: Ship an application with a database
The fastest way to load that data into memory is to place it right into .java file. E.g. stopNames={"Stop1", "Stop2", ...}; latitudes={...};
I do this in my public transit app, loading similar amounts of the data this way takes under a second, filtering is instant.