I am trying to migrate from LeakCanary version 1.* to version 2.* version one used to have AnalysisResult.leakTraceAsFakeException but now I can't seem to be able to find a similar behavior I am stuck after creating my custom FakeException but I am unable to convert the LeakTrace to a StackTrace
I am following this link but I am not using BugSnag in my application
This capability was removed from LeakCanary 2, but you can recreate it for your own needs. See the code in LeakCanary 1.6: https://github.com/square/leakcanary/blob/v1.6.3/leakcanary-analyzer/src/main/java/com/squareup/leakcanary/AnalysisResult.java#L104-L133
public #NonNull RuntimeException leakTraceAsFakeException() {
if (!leakFound) {
throw new UnsupportedOperationException(
"leakTraceAsFakeException() can only be called when leakFound is true");
}
LeakTraceElement firstElement = leakTrace.elements.get(0);
String rootSimpleName = classSimpleName(firstElement.className);
String leakSimpleName = classSimpleName(className);
String exceptionMessage = leakSimpleName
+ " leak from "
+ rootSimpleName
+ " (holder="
+ firstElement.holder
+ ", type="
+ firstElement.type
+ ")";
RuntimeException exception = new RuntimeException(exceptionMessage);
StackTraceElement[] stackTrace = new StackTraceElement[leakTrace.elements.size()];
int i = 0;
for (LeakTraceElement element : leakTrace.elements) {
String methodName = element.referenceName != null ? element.referenceName : "leaking";
String file = classSimpleName(element.className) + ".java";
stackTrace[i] = new StackTraceElement(element.className, methodName, file, 42);
i++;
}
exception.setStackTrace(stackTrace);
return exception;
}
The biggest difference between 1.6 and 2 is that an analysis result used to have one leaktrace (=> converts to one stacktrace) but now LeakCanary can find many leaks at once so the analysis result will have several leaks, you'll want to create a stacktrace for each.
Related
i have a question to Google Fit.
I am creating a step counter (oh wonder g). This i have already done so far and it not really hard.
But now we come to my problem. I am only reading the steps with the Sensor API. The issue is, i can add new data via for example the Google Fit app and it will be counted in my app too. This introduces cheating and i do not want this.
So i need to have a way to only read "device created" data and not manually added data. Is there a nice way to to this?
From the SDK documentation it is not really clear how to proceed here.
So i need to have a way to only read "device created" data and not
manually added data. Is there a nice way to to this?
You will want to use Private Custom Data Types to achieve that. Read about the different types of Fitness data you can upload to Google Fit here.
1. Public data types
Standard data types provided by the platform, like com.google.step_count.delta. Any app can read and write data of
these types. For more information, see Public Data Types.
2. Private custom data types
Custom data types defined by an specific app. Only the app that defines the data type can read and write data
of this type. For more information, see Custom Data Types.
3. Shareable data types
Custom data types submitted to the platform by an app developer. Once approved, any app can read data of a
shareable type, but only whitelisted apps as specified by the
developer can write data of that shareable type. For more information,
see Shareable Data Types.
I was able to do this with the help of this alogrithm. But remember due to Android fragmentation this code still removes some of the user's data and count it as penalty
private String dumpDataSet(DataSet dataSet, int x) {
List<String> days = new ArrayList<>();
days.add("Monday");
days.add("Tuesday");
days.add("Wednesday");
days.add("Thursday");
days.add("Friday");
days.add("Saturday");
days.add("Sunday");
String day = days.get(Math.round(x / 24));
Log.d(TAG, "\tDay: " + day);
Log.i(TAG, "Data returned for Data type: " + dataSet.getDataType().getName());
DateFormat dateFormat = getTimeInstance();
String text = "";
try {
for (DataPoint dp : dataSet.getDataPoints()) {
Log.i(TAG, "\tStepCount getStreamName: " + dp.getOriginalDataSource().getStreamName());
Log.i(TAG, "\tStepCount getStreamIdentifier: " + dp.getOriginalDataSource().getStreamIdentifier());
Log.i(TAG, "\tStepCount App Type: " + dp.getDataType().getName());
Log.i(TAG, "\tStepCount Type: " + dp.getOriginalDataSource().getType());
for (Field field : dp.getDataType().getFields()) {
Log.i(TAG, "\tField: " + field.getName() + " Value: " + dp.getValue(field));
text += dp.getValue(field);
String si[] = dp.getOriginalDataSource().getStreamIdentifier().toLowerCase().split(":");
if ((((si[si.length - 1].contains("soft")) || (si[si.length - 1].contains("step"))) && si[si.length - 1].contains("counter"))) {
totalSteps += Integer.parseInt(dp.getValue(field).toString());
Log.d(TAG, "\tStepCount" + " Added Steps -> " + dp.getValue(field) + " steps");
text += "\n\n";
} else {
Log.e(TAG, "\tStepCount PENALTY ---------------------------------------------------------------");
Log.e(TAG, "\tDay = " + day + " | Hour Number = " + x + " | StepCount" + " PENALTY DEDUCTED -> " + dp.getValue(field) + " steps");
Log.e(TAG, "\tStepCount PENALTY getStreamIdentifier: " + dp.getOriginalDataSource().getStreamIdentifier());
Log.e(TAG, "\tStepCount PENALTY getStreamName: " + dp.getOriginalDataSource().getStreamName());
Log.e(TAG, "\tStepCount PENALTY App Type: " + dp.getDataType().getName());
Log.e(TAG, "\tStepCount PENALTY Type: " + dp.getOriginalDataSource().getType());
Log.e(TAG, "\tStepCount PENALTY ---------------------------------------------------------------");
}
}
}
} catch (Exception ex) {
ex.getStackTrace();
}
return text;
}
----- UPDATE -----
You can also call
DataPoint.getOriginalDataSource().getAppPackageName()
to filter out smartwatches and other apps.
I tried as suggested by Ali Shah lakhani but
DataPoint.getOriginalDataSource().getAppPackageName();
/*I also tried but could not achieve what I wanted*/
DataPoint.getOriginalDataSource().getStreamName();
DataPoint.getOriginalDataSource().getStreamIdentifier();
did not work at least for me while retrieving data. I ended up using readDailyTotalFromLocalDevice() as shown below in order to capture steps captured by device only.
Fitness.HistoryApi.readDailyTotalFromLocalDevice(mApiClient, DataType.TYPE_STEP_COUNT_DELTA).await(1, TimeUnit.MINUTES)
I cross checked the same with some of the apps that avoids manual entries in their app and the count provided by the function above is exactly the same.
Note: If a user is having multiple devices and is using the app on all of them, readDailyTotalFromLocalDevice() will have different value for each and every device since the function is responsible for returning device specific data only.
I get above mentioned error on some devices (very rarely, 2 times until now only):
android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 3850)
at android.database.sqlite.SQLiteConnection.nativeExecuteForString(Native Method)
at android.database.sqlite.SQLiteConnection.executeForString(SQLiteConnection.java:679)
at android.database.sqlite.SQLiteConnection.setJournalMode(SQLiteConnection.java:361)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:236)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:200)
at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:806)
at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:791)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:669)
at ***.a(***.java:115)
The line 115 in my code is following:
// here the exception occurs
SQLiteDatabase db = SQLiteDatabase.openDatabase(pathApp, null, SQLiteDatabase.OPEN_READONLY);
// ...
db.close();
Story
What I do is following:
app has root rights
it copies the database from another app into it's own directory
it tries to open this database and read some data
it close the file again
That's all. This is working on thousands of devices. My app for sure only accesses the database at on place, I'm completely sure about that
Question
Does anyone know what could cause this problem?
Maybe interesting facts
the 2 devices are a OnePlus2
one guy told me that the problem occurred after updating to Oxygen 2.1
I came across a similar problem recently where my app runs fine on all Android devices except the OnePlus Two running OxygenOS 2.1.
After looking into the issue, it appears this specific combination is very sensitive and crashes on a database lock of some sort. In my case, my app was copying a new database to the device on the first run then checking the database version to see if it requires an update. On the check it crashed on these devices. I realized in my code I am opening a separate instance to the database instead of using the currently opened database when checking the version. After changing the code to avoid opening a separate instance of the database, the app stopped crashing on the OnePlus.
My advice is to Not open multiple instances of the database in your app or try closing the database first before opening it again (perhaps your copy method didn't close the database or is using another instance of the same database).
Just encountered this in development. It triggered when the phone ran out of batteries and shut down. Presumably, you just have to be grabbing the database lock when that happens.
Here's the working solution (already tested in production for 2 months now or so) and this works on the OnePlus2 and on Android 6 as well:
add the sqlite binaries to your app (can be found here: http://forum.xda-developers.com/showthread.php?t=2730422). Just put them into your apps assets folder
Then in your app, try to read an external database like following:
Find out which binary is working. I tried a few methods (via calling /proc/cpuinfofor example) but could not find a reliable solution, so I do it via trial and error like following:
First I copy the binary from my assets folder to my apps files folder
Then I try to dump the desired database via <path_to_SQLite3_bnary> <path_to_database> \.dump '<tablename>'\n and read the result
I check the result, if it contains error: I know the binary is not working, if it starts with INSERT INTO I know it works
I repeat this with my binaries until I found a working binary
Then I just read the result. The result will contain an error or be some csv similar content. I either handle the error or convert every line to valid csv via row.replace("INSERT INTO \"" + tablename + "\" VALUES(", "").replace(");", ""), so that I get the content in a csv format. I use opencsv's CSVReader to parse the data then...
That's it, this works (until know) on all devices without a problem
Code - copied from my sources, adopt it a little bit to fit your needs
public static List<String> readCSVData(String pathDatabase, String tableName) throws InterruptedException, TimeoutException, RootDeniedException, IOException
{
List<String> res = null;
List<String> csvData = null;
final String[] architectures = new String[]{
"armv7",
"armv7-pie",
"armv6",
"armv6-nofpu"
};
for (int i = 0; i < architectures.length; i++)
{
res = readDatabase(architectures[i], pathDatabase, tableName, rootMethod);
if (res.toString().contains("[error:"))
{
L.d(RootNetworkUtil.class, "Trial and Error - ERROR: " + architectures[i]);
}
else
{
int maxLength = (res.toString().length() < 100) ? res.toString().length() : 100;
L.d(RootNetworkUtil.class, "Trial and Error - RESULT: " + res.toString().substring(0, maxLength));
L.d(RootNetworkUtil.class, "Architecture found via trial and error: " + architectures[i]);
csvData = res;
break;
}
}
return csvData;
}
private static List<String> readDatabase(String architecture, String pathDB, String tablename) throws InterruptedException, TimeoutException, RootDeniedException, IOException {
String sqlite = "sqlite3." + architecture;
String pathSQLite3 = getSQLitePath(architecture, tablename);
// OWN class, just copy the file from the assets to the sqlite3 path!!!
AssetUtil.copyAsset(sqlite, pathSQLite3);
String[] cmd = new String[]{
"su\n",
//"chown root.root " + pathSQLite3 + "\n",
"chmod 777 " + pathSQLite3 + "\n",
pathSQLite3 + " " + pathDB + " \".dump '" + tablename + "'\"\n"
};
List<String> res = new ArrayList<>();
List<String> temp = RootUtils.execute(cmd);
for (int i = 0; i < temp.size(); i++)
{
// Fehlerzeilen behalten!!!
if (temp.get(i).contains("error:"))
res.add(temp.get(i));
else if (temp.get(i).startsWith("INSERT INTO \"" + tablename + "\""))
res.add(temp.get(i).replace("INSERT INTO \"" + tablename + "\" VALUES(", "").replace(");", ""));
}
return res;
}
public static String getSQLitePath(String architecture, String addon)
{
String sqlite = "sqlite3." + architecture;
String pathSQLite3 = "/data/data/" + MainApp.get().getPackageName() + "/files/" + sqlite + addon;
return pathSQLite3;
}
I'm trying to finish the 'backbone' of my app in the next 3 weeks, however, one of the few obstacles I stutter at is saving data. I've had a look at saving data internally, but there is limited tutorials from what I can find of reading and writing multiple lines to files in the apps cache directory.
Basically what I'm trying to do is save the values stored inside a fragment. This fragment resets all its values when the user clicks a button and changes text to match a page number. (A number of duplicates that contain various values.) I would do multiple fragments, however, thought it would be beneficial to use just one fragment to minimize storage space needed.
I've only got round to writing to the files, and created two methods to manage this which are then called on the click of a button. One creates these files and the other writes to them. Unfortunately I'm inexperienced using adb and could only find that the files are created, but don't know if they are being correctly written to. Is there any chance someone could review this and possibly assist with re-reading the files? Help is much appreciated.
The two methods (Warning: A great number of lines ahead):
public void createEmptyFiles() {
try {
outputTempExerciseFileE1 = File.createTempFile("temp_exercise_1",
".txt", outputTempExerciseDir);
outputTempExerciseFileE2 = File.createTempFile("temp_exercise_2",
".txt", outputTempExerciseDir);
outputTempExerciseFileE3 = File.createTempFile("temp_exercise_3",
".txt", outputTempExerciseDir);
} catch (IOException e) {
e.printStackTrace();
Log.w("rscReporter", "Encountered an error when creating empty files!");
}
}
public void writeTemporaryFiles() {
try {
if (counterAnotherExercise == 1) {
writerTemp = new FileWriter(outputTempExerciseFileE1);
writerTemp
.write(editTextExerciseName.getText().toString() + "\n"
+ counterNoSets + "\n" + counterRepsPerSet
+ "\n" + counterMeanRepTime + "\n"
+ counterMeanRepTimeRefined + "\n"
+ counterSetInterval);
writerTemp.close();
} else if (counterAnotherExercise == 2) {
writerTemp = new FileWriter(outputTempExerciseFileE2);
writerTemp
.write(editTextExerciseName.getText().toString() + "\n"
+ counterNoSets + "\n" + counterRepsPerSet
+ "\n" + counterMeanRepTime + "\n"
+ counterMeanRepTimeRefined + "\n"
+ counterSetInterval);
writerTemp.close();
} else if (counterAnotherExercise == 3) {
writerTemp = new FileWriter(outputTempExerciseFileE3);
writerTemp
.write(editTextExerciseName.getText().toString() + "\n"
+ counterNoSets + "\n" + counterRepsPerSet
+ "\n" + counterMeanRepTime + "\n"
+ counterMeanRepTimeRefined + "\n"
+ counterSetInterval);
writerTemp.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Any of the text files should look like:
editTextExerciseName
counterNoSets
counterRepsPerSet
counterMeanRepTime
counterMeanRepTimeRefined
counterSetInterval
Where the two methods are called:
// In a switch statement as there are around 15 buttons
case R.id.button_another_exercise_foreground:
// Increases page number in fragment
counterAnotherExercise++;
// This then checks the page number and changes text
checkPageNo();
// Writing to files is called, files were created in onCreateView()
writeTemporaryFiles();
// Resets all the counters, giving the imitation it is a completely new fragment
counterReset();
// default array exercise is then set to the page number which is then displayed as title
// For example: Exercise 1, Exercise 2, Exercise 3...
textViewExerciseTitle.setText(defaultArrayExercise);
break;
I only know the basics of Java and Android, for myself this is ambitious, however, you gotta learn somewhere! Additional suggestion for saving values are welcomed.
You don't really need files as you are only writing and then reading a handful of fixed data. Use SharedPreferences like this:
to write:
PreferenceManager.getDefaultSharedPreferences(YourActivity.this).edit().putString("editTextExerciseName", "my exercise").commit();
to read:|
PreferenceManager.getDefaultSharedPreferences(YourActivity.this).getString("editTextExerciseName");
I am using Android PDF Write(APW) to create a PDF, but it doesn't work with some special characters(portuguese).
mypdf.addText(170, 50, 40,"Coração");
The standard enconding is:
mypdf.setFont(StandardFonts.SUBTYPE, StandardFonts.COURIER, StandardFonts.WIN_ANSI_ENCODING);
outputToFile("helloworld.pdf",pdfcontent,"ISO-8859-1");
I'v tried
outputToFile("helloworld.pdf",pdfcontent,"UTF-8");
outputToFile("helloworld.pdf",pdfcontent,"UTF-16");
outputToFile("helloworld.pdf",pdfcontent,"Cp1252");
and didn't succeed.
Any ideas what should I do?
EDIT
The method outputToFile is defined as:
private void outputToFile(String fileName, String pdfContent, String encoding) {
File newFile = new File(Environment.getExternalStorageDirectory() + "/" + fileName);
try {
newFile.createNewFile();
try {
FileOutputStream pdfFile = new FileOutputStream(newFile);
pdfFile.write(pdfContent.getBytes(encoding));
pdfFile.close();
} catch(FileNotFoundException e) {
//
}
} catch(IOException e) {
//
}
}
The method addText is defined as:
public void addText(int leftPosition, int topPositionFromBottom, int fontSize, String text, String transformation) {
addContent(
"BT\n" +
transformation + " " + Integer.toString(leftPosition) + " " + Integer.toString(topPositionFromBottom) + " Tm\n" +
"/F" + Integer.toString(mPageFonts.size()) + " " + Integer.toString(fontSize) + " Tf\n" +
"(" + text + ") Tj\n" +
"ET\n"
);
}
Besides, I change the font color to white adding the following rawcontent:
mypdf.addRawContent("1 1 1 rg\n");
Then I come back to the black font color:
mypdf.addRawContent("0 0 0 rg\n");
I took all the information provided, wrote the following simple unit test method and ran it.
public void test19192108()
{
PDFWriter mPDFWriter = new PDFWriter(PaperSize.FOLIO_WIDTH, PaperSize.FOLIO_HEIGHT);
mPDFWriter.setFont(StandardFonts.SUBTYPE, StandardFonts.COURIER, StandardFonts.WIN_ANSI_ENCODING);
mPDFWriter.addText(170, 50, 40,"Coração");
String pdfcontent = mPDFWriter.asString();
outputToFile("helloworld19192108.pdf",pdfcontent,"ISO-8859-1");
}
(outputToFilebeing the helper method from the APW PDFWriterDemo class)
The result looks like this:
This seems pretty much to fulfill the expectations.
Thus, in whichever way it doesn't work with some special characters(portuguese) for the OP, some vital information is missing for reproducing the issue.
PS: Depending on the setup of the development environment, there might be an issue with non-ASCII characters in the source code. Thus, it might be a good idea to replace
mPDFWriter.addText(170, 50, 40,"Coração");
with
mPDFWriter.addText(170, 50, 40,"Cora\u00e7\u00e3o");
PPS: Adobe Reader after viewing a file generated like this wants to repair it. The reason is that the cross reference table is broken. The code generating entries for it is this:
public void addObjectXRefInfo(int ByteOffset, int Generation, boolean InUse) {
StringBuilder sb = new StringBuilder();
sb.append(String.format("%010d", ByteOffset));
sb.append(" ");
sb.append(String.format("%05d", Generation));
if (InUse) {
sb.append(" n ");
} else {
sb.append(" f ");
}
sb.append("\r\n");
mList.add(sb.toString());
}
(from CrossReferenceTable.java)
Counting the characters in this entry we get 10 + 1 + 5 + 3 + 2 = 21.
According to the specification, though:
Each entry shall be exactly 20 bytes long, including the end-of-line marker
(from section 7.5.4 Cross-Reference Table of ISO 32000-1)
When using (the current version of) the Android PDF Writer, you should fix this code, too.
I have code like this:
MyLog.d("TAG", "debug string " + aVariable + " more debug string =" + anotherVariable);
And MyLog class is like
public void d(String tag, String message) {
private static final boolean DEBUG = true;
if (DEBUG) {
Log.d(tag, message);
}
}
My question is if I set DEBUG to false, will android java compiler smartly detect that this line of code
MyLog.d("TAG", "debug string " + aVariable + " more debug string =" + anotherVariable);
does nothing
and it won't create temporary string objects for "debug string " + aVariable + " more debug string =" + anotherVariable
You're doing the string concatenation before anything related to the DEBUG matters: I doubt that would be optimized out by ProGuard, although the call to Log.d inside MyLog.d would disappear.
If you check the bytecode, it'd be worth reporting back; I'm curious how far ProGuard will follow a call chain to detect dead code. I'd be surprised if the string concatenation went away.
You also can't declare a variable private like that inside a method.