Maximum length of Intent putExtra method? (Force close) - java

I need some help with debugging my application. First of all: In emulator and on some other devices my app is running fine. On my device I got a force close (without a force close message).
The "crash" happens if the Activity of the app is changed.
Here is some code of the MainActivity class. It just reads html content from a web page over webview. And no, it is NOT possible to do this over HttpRequest because I was not able to simulate the post request.
public class MainActivity extends Activity {
public final static String EXTRA_HTML = "com.example.com.test.HTML";
private WebView mWebView;
private ProgressDialog mDialog;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webView1);
CookieSyncManager.createInstance(this);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
mWebView.setBackgroundColor(0);
mWebView.setWebChromeClient(new WebChromeClient() {
public boolean onConsoleMessage(ConsoleMessage cmsg) {
if (cmsg.message().startsWith("MAGIC")) {
mDialog.cancel();
/*HashMap<String, String> message = new HashMap<String, String>();*/
String msg = cmsg.message().substring(5);
Intent intent = new Intent(MainActivity.this,
ReadDataActivity.class);
/*message.put("message", msg);*/
/*intent.putExtra(EXTRA_HTML, message);*/
intent.putExtra(EXTRA_HTML, msg);
startActivity(intent);
}
return false;
}
});
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setPluginState(PluginState.OFF);
mWebView.getSettings().setLoadsImagesAutomatically(false);
mWebView.getSettings().setBlockNetworkImage(true);
mWebView.getSettings().setAppCacheEnabled(true);
mWebView.getSettings().setSavePassword(true);
mWebView.getSettings()
.setCacheMode(WebSettings.LOAD_NORMAL);
mWebView.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String address) {
if (address.indexOf("mySession") != -1) {
view.loadUrl("javascript:console.log('MAGIC'+document.getElementsByTagName('html')[0].innerHTML);");
}
});
mWebView.loadUrl("http://www.myurl.de");
}
So, in the onConsoleMessage() method I just pass the html code to another Activity class which read, parse and display the content.
The problem is now that at this point when the ReadDataActivity class should be loaded the application just close and go back to the home screen without any message or user dialog.
Is it possible that the html code which is passed as a string to the ReadDataActivity is to big? I also try to add the html code as a string in a HashMap but the problem is the same.
Some ideas what I can do to debug the problem? Maybe I should try to create a Parcelable object?
In the emulator everything is working fine.

As per my experience (sometime ago), you are able to parcel up to 1MB of data in a Bundle for IPC. This limit can be reduced if a lot of transactions are happening at a given time. Further information here.
In order to overcome this issue, I would suggest you to save your content on a temp file and pass the path/URI of your temp file to your second activity. Then in your second activity, read the contents out from file, perform your desired operations and finally delete that file.
If you want, you may also incorporate Shared_Preferences for this task - if you think handling files is cumbersome.

I did some research on the maximum amount of data you can transfer using an Intent. And it seems that the limit is nowhere near 1MB or 90KB, it's more like 500KB (tested on API 10, 16, 19 and 23).
I wrote a blog post about this topic, you can find it here: http://web.archive.org/web/20200217153215/http://neotechsoftware.com/blog/android-intent-size-limit

The size limit of Intent is still pretty low in Jelly Bean, which is somewhat lower than 1MB (around 90K), so you should always be cautious about your data length, even if your application targets only latest Android versions.

I have seen that by writing and reading from a file consists of less performance .
Then I have seen this solution : . So I am using this solution :
public class ExtendedDataHolder {
private static ExtendedDataHolder ourInstance = new ExtendedDataHolder();
private final Map<String, Object> extras = new HashMap<>();
private ExtendedDataHolder() {
}
public static ExtendedDataHolder getInstance() {
return ourInstance;
}
public void putExtra(String name, Object object) {
extras.put(name, object);
}
public Object getExtra(String name) {
return extras.get(name);
}
public boolean hasExtra(String name) {
return extras.containsKey(name);
}
public void clear() {
extras.clear();
}
}
Then in MainActivity I have called it like the following :
ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
extras.putExtra("extra", new byte[1024 * 1024]);
extras.putExtra("other", "hello world");
startActivity(new Intent(MainActivity.this, DetailActivity.class));
and in DetailActivity
ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
if (extras.hasExtra("other")) {
String other = (String) extras.getExtra("other");
}

The fixed size of 1MB is not only limited to intents. As Intents, Content Providers, Messenger, all system services like Telephone, Vibrator etc. utilize IPC infrastructure provider by Binder. Moreover the activity lifecycle callbacks also use this infrastructure.
1MB is the overall limit on all the binder transactions executed in the system at a particular moment.
In case there are lot of transactions happening when the intent is sent,it may fail even though extra data is not large.
http://codetheory.in/an-overview-of-android-binder-framework/

A little late to the game, but I just ran up against the same issue. Writing the data to file didn't really make sense performance-wise in my case, but I came across this in my search for answers:
http://developer.android.com/guide/faq/framework.html#3
Using a singleton is better for me as there's no need for disk IO. Better performance if the data doesn't need to be persisted.
Here's an example implementation:
public class DataResult {
private static DataResult instance;
private List<SomeObject> data = null;
protected DataResult() {
}
public static DataResult getInstance() {
if (instance == null) {
instance = new DataResult();
}
return instance;
}
public List<SomeObject> getData() { return data; }
public void setData(List<SomeObject> data) { this.data = data; }
}
Then you can set using this in one activity:
DataResult.getInstance().setData(data);
And get it in the other activity like this:
List<SomeObject> data = DataResult.getInstance().getData();

The Binder transaction buffer has a limited fixed size - 1Mb.
But the problem is that buffer shared by all transactions in progress for the process.
So try to keep your intent's data as small as possible every time.

The use of static String variable is good. If there is a need for the user to go back & forth between different pieces of HTML, you can also use LruCache like this:
static LruCache<String, String> mMemoryCache;
final int kiloByte = 1024;
.
.
final int maxMemoryKB = (int) (Runtime.getRuntime().maxMemory() / kiloByte);
// then choose how much you want to allocate for cache
final int cacheSizeKB = maxMemoryKB / 8;
.
.
mMemoryCache = new LruCache<String, String>(cacheSizeKB) {
//#Override
protected int sizeOf(String key, String value) {
try {
byte[] bytesUtf8 = value.getBytes("UTF-8");
return bytesUtf8.length / kiloByte;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return -1;
}
};
.
.
String cacheKey = generateUniqueString(key);
if (mMemoryCache.get(key) == null) {
mMemoryCache.put(cacheKey, yourContent);
}
Intent intent = new Intent(getApplicationContext(), ReadDataActivity.class);
intent.putExtra(EXTRA_HTML, cacheKey);
startActivity(intent);
Then on the ReadDataActivity side
Intent intent = getIntent();
String cacheKey = intent.getStringExtra(EXTRA_HTML);
String contentString = MainActivity.mMemoryCache.get(cacheKey);
doSomethingWith(contentString);
This idea came from here.

An alternative solution for passing large data between activities is to use a static field. In your case add this line to ReadDataActivity class
public static String msg;
Then you can use the static field msg within MainActivity class as follows
ReadDataActivity.msg = cmsg.message().substring(5);
And finally start your activity without extra put
Intent intent = new Intent(MainActivity.this, ReadDataActivity.class);
startActivity(intent);

Related

Strange LiveData behaviour?

Im trying to implement MVVM architecture using ViewModel and LiveData. These two methods are inside a Activity:
private void handleResult(BoardViewModel vm) {
vm.getLiveDataSingleObj("Result").observe(this, new Observer<Object>() {
#Override
public void onChanged(#Nullable Object resultObj) {
Result result = (Result) resultObj;
if (!result.isCompleted()) return;
gotoResult();
}
});
}
And
private void gotoResult() {
Log.w(LOG_TAG, "Result: Moving to next activity");
Intent intent = new Intent(boardActivity, ResultActivity.class);
intent.putExtra("LEVEL", levelIndex);
intent.putExtra("MAP", mapIndex);
startActivity(intent);
}
The handleResult method is setup to listen for result objects that indicate that the game has ended and it is time to move on to the next activity ("gotoResult"). However, this completely breaks the navigation of the app, when i go back and then say attempt to start a new game session i instead instantly go to the next activity telling me I've already won.
Any ideas as to why it fires multiple times and eventually stops, letting me start a new session. To clarify, if I remove the gotoResult the logic works every single time no errors with indexes out of bounds or what have you, it's only when I add the goto that everything breaks.
ViewModel:
private void setupHashTypes() {
hashLiveData.put(KEY_BOARD, liveDataBoardQuery);
hashLiveData.put(KEY_STEPS_COUNTER, game.getStepsTakenLiveData());
hashLiveData.put(KEY_PATH_CHANGE, game.getPathChangedLiveData());
hashLiveData.put(KEY_VALUE_CHANGE, game.getValueChangeLiveData());
hashLiveData.put(KEY_TIMER, game.getTimerLiveData());
hashLiveData.put(KEY_SELECTED, game.getSelectedLiveData());
hashLiveData.put(KEY_DESELECTED, game.getDeselectedLiveData());
hashLiveData.put(KEY_HOLD, game.getHoldLiveData());
hashLiveData.put(KEY_UNHOLD, game.getUnholdLiveData());
hashLiveData.put(KEY_RESULT, game.getResultLiveData());
}
public LiveData<Object> getLiveDataSingleObj(String type) {
if (hashLiveData.containsKey(type)) {
return (LiveData<Object>) hashLiveData.get(type);
}
throw new IllegalArgumentException("Invalid: key was not found: " + type);
}
And the Model has getters, example:
private final SingleLiveEvent<Result> resultLiveData = new SingleLiveEvent<>();
public LiveData<Result> getResultLiveData() {
return resultLiveData;
}
you should remove the observer in onDestroy() method
Changing from MutableLiveData which always resends the previous set values to new subscribers, to SingleLiveEvent which doesn't have this behaviour, solved the problem.
The class can be found here: https://github.com/googlesamples/android-architecture/tree/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp

Checking Azure connected Database onClick for login

So Azure spit the following code for me to insert into an activity (Android Studio is what I'm using)
Add the following line to the top of the .java file containing your launcher activity:
import com.microsoft.windowsazure.mobileservices.*;
Inside your activity, add a private variable
private MobileServiceClient mClient;
Add the following code the onCreate method of the activity:
mClient = new MobileServiceClient("https://pbbingo.azurewebsites.net", this);
Add a sample item class to your project::
public class ToDoItem{ public String id; public String Text;}
In the same activity where you defined mClient, add the following code:
ToDoItem item = new ToDoItem();
item.Text = "Don't text and drive";
mClient.getTable(ToDoItem.class).insert(item, new TableOperationCallback<item>(){
public void onCompleted(ToDoItem entity, Exception exception, ServiceFilter response)
{
if(exception == null){
//Insert Succeeded
} else {
//Insert Failed
}
}});
My goal is to create a login page. I understand that the above was probably offered up more with a ToList in mind. I just want to get the syntax correct today. The problem I think, is my basic class structure. I have created an OnClick Listener within my on create that gets the ID from a button in my layout. I don't need it checking for anything in the database until the button has been actually clicked to either login or register.
public class LoginClass extends AppCompatActivity{
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.MyLoginLayout);
MobileServiceClient mClient = null;
try {
mClient = new MobileServiceClient ("myAzureWebsite", "AzureKey", this);
} catch (MalformedURLException e) {
e.printStackTrace();
}
Button Attempt = (Button) findViewById (R.id.mySubmitButton);
final MobileServiceClient finalMClient = mClient; // finalized so I can use it later.
Attempt.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick (View v) {
final View thisView = v;
final MyToDoItemClass item = new MyToDoItemClass();
In MyToDoItemClass I have two variables (Both String) Just left over from
the example of a ToDoList (they are String ID and String Text)
item.Text = "Filler";
item.ID = "Fill";
finalMClient.getTable(MyToDoItemClass.class).insert(new Table OperationCallback<item>() { //<--- I'm getting an error that the variable, item
is from an unknown class...
public void onCompleted (Item entity, Exception exception, ServiceFilterResponse response){
if(exception == null) {
Intent i = new Intent (LoginClass.this, MainActivity.class);
startActivity(i);
}else{
Toast.makeText(thisView.getContext(), "Failed", Toast.LENGTH_LONG).show();
}}
});
}
});
}}
The problem is with that the TableOperationCallback is saying that the item from MyToDoItemClass class is from an unknown class.
There are many issues in your code, as below.
According to the javadoc for class MobileServiceClient, there is not a method insert(TableOperationCallback<E> callback), so the code finalMClient.getTable(MyToDoItemClass.class).insert(new Table OperationCallback<item>() {...} is invalid.
The generics E in Table OperationCallback<E> means that you need to write a POJO class name instead of E, not an object variable name like item, so the correct code should be new Table OperationCallback<MyToDoItemClass>, please see the Oracle tutorial for Generics to know more details.
The figure below shows all methods insert of class MobileServiceClient. The bold word Deprecated under the method name means that you should not use it for developing on new project, it‘s only compatible for old project on the new version of Java SDK.
Please follow the offical tutorial to develop your app. Any concern, please feel free to let me know.

Android Passing big data on preview screen before user loads MainActivity with HashMap TransactionTooLargeException

Hi everyone i am having a problem here so here is what i am trying to achieve.
Achieve:
Trying to create a Map and share it between activitys! And use it in MainActivity.
Reason:
Trying to avoid the big load of the data on the MainThread by loading the data in my SplashActivity into a Map and sending the loaded data into my MainActivty
so i can use it there to check it from user Input if it matches.
Problem:
Problem is that the data i am trying to load is this one:
http://www-01.sil.org/linguistics/wordlists/english/wordlist/wordsEn.txt
Which is a big wordlist and it takes a while to load in background. It has to be fast thats why i am trying to load it in Map before the MainActivity Load and then passing it when its done loading btw user is waiting for SplashScreen while load to the Map.
Getting error that the data i am trying to send true Bundle, Intent is too large.
Anyone having thoughs how i can pass the loaded Map into the MainActivity?
Yes i know i can use AsyncTask as the background thread...
Yes i know it can be done with Serialize but please show some examples of your thoughts.
Thanks for everyone who is trying to help!
ThePreviewActivity code:
public class PreviewActivity extends AppCompatActivity {
private Scanner scanner;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Map<Character, ArrayList<String>> charCount = new HashMap<Character, ArrayList<String>>();
//get Assets
AssetManager am = this.getAssets();
//create input stream
InputStream is = null;
try {
//open the word txt
is = am.open("words.txt");
} catch (IOException e) {
e.printStackTrace();
}
if (is != null) {
scanner = new Scanner(is);
}
while (scanner.hasNext()) {
char firstChar = scanner.next().charAt(0);
ArrayList<String> list;
if (charCount.containsKey(firstChar)) {
list = charCount.get(firstChar);
} else {
list = new ArrayList<>();
}
list.add(scanner.next());
charCount.put(firstChar, list);
}
Intent intent = new Intent(this, MainActivity.class);
Bundle extras = new Bundle();
extras.putSerializable("HashMap", (Serializable) charCount);
intent.putExtras(extras);
startActivity(intent);
finish();
}
}
MainActivity.java code:
Bundle bundle = this.getIntent().getExtras();
if(bundle!=null) {
charCount = (Map<Character, ArrayList<String>>) bundle.getSerializable("HashMap");
}
Error i am getting trying to pass it with a Bundle: Caused by: android.os.TransactionTooLargeException: data parcel size 3560868 bytes
First, please move all this I/O-and-parsing code to a background thread.
In terms of your TransactionTooLargeException, you simply cannot pass that much data around via Intent objects using startActivity(). You could:
Store this data in a singleton, particularly since it will not be changing (presumably), or
Wrap the data in a ContentProvider, and have other activities use a ContentResolver to work with that provider

Fitbit for Android, automatically grab user data without OAuth each time I start app

I have the following code for FitBit integration into Android, it is used from this library https://github.com/manishsri01/FitbitIntegration, I can get the response.getBody() to show the JSON body in the webview but I would like the application to be able to automatically update the code without having to login and grab the PIN for OAuth everytime I run the app. What can I do to fix this? I would also like to parse the JSON .getBody() into separate string variables. How can I accomplish this?
MainActivity
public class MainActivity extends Activity {
OAuthService service;
Token requestToken;
// Replace these with your own api key and secret
private String apiKey = "************************";
private String apiSecret = "*************************";
private String accessToken;
private String tokenSecret;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final WebView wvAuthorize = (WebView) findViewById(R.id.wvAuthorize);
final EditText etPIN = (EditText) findViewById(R.id.etPIN);
service = new ServiceBuilder().provider(FitbitApi.class).apiKey(apiKey)
.apiSecret(apiSecret).build();
// network operation shouldn't run on main thread
new Thread(new Runnable() {
public void run() {
requestToken = service.getRequestToken();
final String authURL = service
.getAuthorizationUrl(requestToken);
// Webview nagivation should run on main thread again...
wvAuthorize.post(new Runnable() {
#Override
public void run() {
wvAuthorize.loadUrl(authURL);
}
});
}
}).start();
}
public void btnRetrieveData(View view) {
EditText etPIN = (EditText) findViewById(R.id.etPIN);
String gotPIN = etPIN.getText().toString();
final Verifier v = new Verifier(gotPIN);
// network operation shouldn't run on main thread
new Thread(new Runnable() {
public void run() {
Token accessToken = service.getAccessToken(requestToken, v);
OAuthRequest request = new OAuthRequest(Verb.GET,
"http://api.fitbit.com/1/user/-/profile.json");
service.signRequest(accessToken, request); // the access token from step
// 4
final Response response = request.send();
final TextView tvOutput = (TextView) findViewById(R.id.tvOutput);
// Visual output should run on main thread again...
tvOutput.post(new Runnable() {
#Override
public void run() {
tvOutput.setText(response.getBody());
}
});
}
}).start();
}
}
FitBitApi
public class FitbitApi extends DefaultApi10a {
private static final String AUTHORIZE_URL = "https://www.fitbit.com/oauth/authorize?oauth_token=%s";
public String getAccessTokenEndpoint() {
return "https://api.fitbit.com/oauth/access_token";
}
public String getRequestTokenEndpoint() {
return "https://api.fitbit.com/oauth/request_token";
}
public String getAuthorizationUrl(Token token) {
return String.format(AUTHORIZE_URL, token.getToken());
}
}
It sounds like you have two separate questions here. Firstly, in regards to saving credentials, there are a number of ways you can do this, the easiest is probably by saving the user/pin details in Android's SharedPreferences. However, you'll still need to make the request for an access token. You should also save the access token (in a cache or DB) and re-use it until it is expired. You may want to read up on ways to secure these credentials if they're considered private.
Your second question regarding parsing JSON is quite common, and if your intention is to map JSON objects to Java objects, you should consider using Google's GSON or Jackson JSON Processor.
If you're intending for Fitbit's API to be a large part of your app, consider using Spring Social and make a Fitbit endpoint (there is a Spring for Android which uses Spring Social). It might be a bit overkill though, but I generally like the structure.

I can't correctly access cached images while offline

I'm using AQuery to help cache some images. I know for fact the images are being cached correctly, but I'm having trouble accessing them unless I'm online.
I know they are being cached because I can see them on my SD card and if I replace mArtistImageURL in my return statement with the URL from my browser (copy/paste), the image will load while I'm offline, if it's already cached. I realize that I'm requesting to fetch the image URLs only if I'm online, but even so, I don't understand why the cached images won't load offline.
MusicUtils.isOnline() is basically a check to see if there is any connection. The main reason I have the bulk of my doInBackground() method wrapped in this is do to an Exception being thrown if I don't. FATAL EXCEPTION: AsyncTask #1 is all Logcat is putting out for me at the moment. I'm not sure why the stacktrace isn't complete.
I've tried using my AsyncTask in every way I know. I've returned a Bitmap, File, and String and I'm able to successfully load the image in each case, as long as I'm online. So, I have these cached images and I need some help putting them to use.
AsyncTask
public class loadArtistImage extends AsyncTask<String, Integer, Bitmap> {
#Override
protected Bitmap doInBackground(String... arg0) {
// Get artist image
if (MusicUtils.isOnline(mContext)) {
mArtistResults = Artist.getImages("Andrew Bird", 1, 1, key);
mArtistIterator = mArtistResults.getPageResults().iterator();
while (mArtistIterator.hasNext()) {
mArtistImageURL = mArtistIterator.next().getImageURL(
ImageSize.ORIGINAL);
}
}
aq.cache(mArtistImageURL, 60000 * 1440);
return aq.getCachedImage(mArtistImageURL);
}
#Override
protected void onPostExecute(Bitmap result) {
bm = result;
if (bm!= null) {
mArtistImage.postDelayed(new Runnable() {
#Override
public void run() {
mArtistImage.startAnimation(AnimationUtils
.loadAnimation(mContext, android.R.anim.fade_in));
MusicUtils.setArtistBackground(mArtistImage, bm);
}
}, 666);
} else {
// TODO something
}
super.onPostExecute(result);
}
}
Mm. While offline what the value of mArtistImageURL will be? Null? So at this point aq.cache(mArtistImageURL, 60000 * 1440); you are caching null, and at next line you are trying to get cache for null? Am I right?
Update: I suppose you cache images using url hash value for file name or something like this. So, modify your cache method to accept additional parameter for filename, for example aq.cache(mArtistImageURL,mArtistNameWithoutSpaces, 60000 * 1440); and when you want a file offline just call aq.getCachedImage(mArtistNameWithoutSpaces); so it will look at your cache folder and return a right file, or stub image if no file is found.
Update 2: Okay, if you dont want to modify AQuery cache methods you can try (againg) using shared preferences (so they would work) like this (not tested):
public String getUrlForArtist(String artistName) {
SharedPreferences settings = mContext.getSharedPreferences(
"artistImages", 0);
return settings.getString(artistName, "http://default.url");
}
public void setUrlForArtist(String artistName, String url) {
SharedPreferences settings = mContext.getSharedPreferences(
"artistImages", 0);
SharedPreferences.Editor editor = settings.edit();
editor.putString(artistName, url);
editor.commit();
}
So in the end your code will look something like this:
if (MusicUtils.isOnline(mContext)) {
mArtistResults = Artist.getImages("Andrew Bird", 1, 1, key);
mArtistIterator = mArtistResults.getPageResults().iterator();
while (mArtistIterator.hasNext()) {
mArtistImageURL = mArtistIterator.next().getImageURL(
ImageSize.ORIGINAL);
if(mArtistImageURL!=null)
setUrlForArtist("Andrew Bird", mArtistImageURL);
}
} else
mArtistImageURL = getUrlForArtist("Andrew Bird");
Oh, yeah, I dont know how AQuery behaves, but anyway you probably should only cache images while online.

Categories

Resources