I've been asking questions regarding my Android project that continually plots Bluetooth data in real-time.
Basically what I've already done is create a first version of my app by cobbling together some open source code Blueterm and OrientationSensorExample
It's been suggested that I add a thread, a handler, a Service, or use Async Task, or AIDL, etc. But I don't know how to use any of these and would appreciate an explanation.
Here's a description of the Blueterm open source code I started with (see link above). Blueterm is basically a terminal emulator program that communicates over Bluetooth. It consists of several activities with Blueterm being the most important. It discovers, pairs, and connects with a remote Bluetooth device that supports SPP/RfComm. When connected I can use Blueterm to configure the remote device by sending it commands to turn on sampling, change the number of channels to sample (to one channel), change to format of the incoming data (I like comma separated data), etc
Here's a description of the OrientationSensorExample open source code I started with (see link above). It's basically an example application of the AnroidPlot library. The OrientationSensor activity implements SensorEventListener. This includes overriding onSenorChanged() which is called whenever new orientation sensor data is taken, and it redraws the graph.
Having cobbled together these two open source projects (Blueterm and OrientationSensorExample) into one application (Blueterm) here's a description of how the overall application (Blueterm) works. When I start Blueterm the whole screen emulates a nice blue terminal. From the Options Menu I discover, pair with, connect to, and configure a remote bluetooth device as described above. Once I have configured the remote device, I go again to the Options Menu and select "Plot data" which launches the Plot activity. The terminal emulator goes away, and a nice scrolling real-time plot from the Plot activity shows up.
As far as I can tell there is a background thread that calls an update() method as follows:
/**
* Look for new input from the ptty, send it to the terminal emulator.
*/
private void update() {
int bytesAvailable = mByteQueue.getBytesAvailable();
int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length);
try {
int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead);
append(mReceiveBuffer, 0, bytesRead);
//VTR use existing handler that calls update() to get data into plotting activity
Plot.plotData(mReceiveBuffer, 0, bytesRead);
} catch (InterruptedException e) {
//VTR OMG their swallowing this exception
}
}
In the update() method I found it convenient to call my Plot.plotData() method and pass it the same date that is passed to the append() method to plot the data. NOTE: This only works if plotData() is a static method. No one has been able to explain why.
Anyway plotData() is a static method and here's how it and it's helper methods look now:
private static StringBuffer strData = new StringBuffer("");
public static void plotData(byte[] buffer, int base, int length) {
Log.i("Entering: ", "plotData()");
/*
byte[] buffer = (byte[]) msg.obj;
int base = msg.arg1;
int length = msg.arg2;
*/
for (int i = 0; i < length; i++) {
byte b = buffer[base + i];
try {
if (true) {
char printableB = (char) b;
if (b < 32 || b > 126) {
printableB = ' ';
}
Log.w("Log_plotData", "'" + Character.toString(printableB)
+ "' (" + Integer.toString(b) + ")");
strData.append(Character.toString(printableB));
if (b == 10)
{
Log.i("End of line: ", "processBlueData()");
Log.i("strData", strData.toString());
splitData(strData);
strData = new StringBuffer("");
}
}
} catch (Exception e) {
Log.e("Log_plotData_exception", "Exception while processing character "
+ Integer.toString(i) + " code "
+ Integer.toString(b), e);
}
}
Log.i("Leaving: ", "plotData()");
}
private static void splitData(StringBuffer strBuf) {
String strDash = strBuf.toString().trim();
String[] strDashSplit = strDash.split("-");
for (int ndx = 0; ndx < strDashSplit.length; ndx++)
{
if (strDashSplit[ndx].length() > 0)
Log.i("strDashSplit", ndx + ":" + strDashSplit[ndx]);
String strComma = strDashSplit[ndx].trim();
String[] strCommaSplit = strComma.split(",");
for (int mdx = 0; mdx < strCommaSplit.length; mdx++)
{
if (strCommaSplit[mdx].length() > 0)
Log.i("strCommaSplit", mdx + ":" + strCommaSplit[mdx]);
if (mdx == 1)
{
int raw = Integer.parseInt(strCommaSplit[1],16);
Log.i("raw", Integer.toString(raw));
float rawFloat = raw;
Log.i("rawFloat", Float.toString(rawFloat));
float ratio = (float) (rawFloat/65535.0);
Log.i("ratio", Float.toString(ratio));
float voltage = (float) (5.0*ratio);
Log.i("voltage", Float.toString(voltage));
nowPlotData(voltage);
}
}
}
}
public static void nowPlotData(float data) {
// get rid the oldest sample in history:
if (plotHistory.size() > HISTORY_SIZE) {
plotHistory.removeFirst();
}
// add the latest history sample:
plotHistory.addLast(data);
// update the plot with the updated history Lists:
plotHistorySeries.setModel(plotHistory, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
//VTR null pointer exception?
if (plotHistoryPlot == null)
Log.i("aprHistoryPlot", "null pointer exception");
// redraw the Plots:
plotHistoryPlot.redraw();
}
If it is strongly recommended that plotData() not be a static method and that I should do something else please explain here and how. Thanks!
This might be a question much better suited for Code Review, rather than here. Perhaps you can reformulate to post it there, or trim it a lot to repost it here.
Furthermore, to answer: "It's been suggested that I add a thread, a handler, a Service, or use Async Task, or AIDL, etc. But I don't know how to use any of these and would appreciate an explanation.", the best advise would be to link you to a book about android, such as: http://commonsware.com/Android/ . Chapters 35 and 36 deal with services, while chapter 20 is about threads. You will never get an answer as complete as those chapters here.
Related
I am using in my app Digital Persona SDK for fingerprint identification.
When i use the identify function on less then 250 fmds it works fine.
Engine.Candidate candidates[] = m_engine.Identify(searchedFmd, 0, fmdArray, DEFAULT_THRESHOLD, 1); //fmdArray < 250
But with fmdArray > 250 it gives me a native runtime error:
A/art: art/runtime/indirect_reference_table.cc:132] JNI ERROR (app bug): local reference table overflow (max=512)
Now i runned this app on couple of android devices and came to conclusion that my app crushes with fmdArray > 250 when its running on android 7. But android 8 works fine. In 8 i can preform a check on even 4000 fmds and it works fine.
But i need to run this code in a specific device, that running android 7.
I tried to run it in couple of threads of 250 fmds only. But after single run there is another problem with the SDK. On the second run it doesnt works.
This is what i do:
First i get a fingerprint capture that i want to identify:
Reader.CaptureResult capture = m_reader.Capture(fidFormat, UrUSDK.DefaultImageProcessing, m_DPI, timeout);
// In second run, code after this line is not executed.
// My guees its not coming back from native. No exeptions. No errors.
...
Fmd scannedFmd = m_engine.CreateFmd(capture.image, fmdFormat);
...
int index = identifyFinger(fmds, scannedFmd);
...
private int identifyFinger(List<Fmd> fmdSearchArray, Fmd scannedFmd) {
List<List<Fmd>> lists = splitToChunks(fmdSearchArray);
AtomicInteger index = new AtomicInteger(-1);
List<Callable<Void>> threads = new ArrayList<>(lists.size());
AtomicInteger iteratorIndex = new AtomicInteger(0);
for (int i = 0; i < lists.size(); i++) {
int currentChunk = i;
Callable<Void> thread = () -> {
System.out.println(Thread.currentThread().getName() + " with chunk: " + iteratorIndex.getAndIncrement());
Fmd[] fmds = lists.get(currentChunk).toArray(new Fmd[IDENTIFY_BOUNDARY]);
try {
Engine.Candidate[] candidates = m_engine.Identify(scannedFmd, 0, fmds, threshold, 1);
if (candidates.length > 0) {
index.set(candidates[0].fmd_index + (currentChunk * IDENTIFY_BOUNDARY));
}
} catch (UareUException e) {
}
System.out.println(Thread.currentThread().getName() + " with chunk: " + currentChunk + " finished!");
return null;
};
threads.add(thread);
}
try {
List<Future<Void>> futures = executorService.invokeAll(threads);
System.out.println("All threads finished: " + index.get());
return index.get();
} catch (InterruptedException e) {
e.printStackTrace();
return -1;
}
}
...
private List<List<Fmd>> splitToChunks(List<Fmd> fmdSearchArray) {
int size = fmdSearchArray.size();
List<List<Fmd>> lists;
if (size > IDENTIFY_BOUNDARY) {
int chunks = size / IDENTIFY_BOUNDARY;
if (size % IDENTIFY_BOUNDARY > 0) {
chunks++;
}
lists = new ArrayList<>(chunks);
for (int i = 0; i < chunks; i++) {
if (i + 1 == chunks) {
lists.add(new ArrayList<>(fmdSearchArray.subList(i * IDENTIFY_BOUNDARY, size)));
break;
}
lists.add(new ArrayList<>(fmdSearchArray.subList(i * IDENTIFY_BOUNDARY, (i + 1) * IDENTIFY_BOUNDARY)));
}
} else {
lists = new ArrayList<>(1);
lists.add(fmdSearchArray);
}
return lists;
}
The problem with this code is that it runs once. But at another try it doesnt come back from the native code of Caprture call.
So my question is:
How i can overcome this and make it work from my java code?
Or at least what is the direction of the solution?
The root cause is that this Identify function holds on to at least two references per returned Candidate after pushing it to the result array. It should instead release the references after pushing, so its use of the (limited) local reference table remain constant. You should file a bug about that.
The simplest workaround for now is to cut your fmdArray into 250-sized chunks and call Identify for each chunk.
I want to send the log lines to email in every 10 minutes.
To do that, I have used a Timer and inside of timer I send the logs via email.
However I loose some log lines between 2 emails.
For example my first email contains no lines which is normal according to my algorithm.
My second email contains log lines between 15.37 and 15.38 seconds.
My third email contains logs in between 15.44 and 15.48 time intervals.
My fourth email contains logs in between 15.55 and 15.58 time intervals.
As you can see I loose some of my logs but I could not find a way to avoid that.
Following is my code in my service class:
#Override
public void onCreate() {
super.onCreate();
mTimer = new Timer();
mTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
sendLogFile();
}
}, 0, 1000 * 60 * 10 );
}
Inside of sendSupport method the second parameter is sent as a content of the log lines where logs is a static string variable.
private void sendLogFile() {
mInteractor.sendSupport("LOG FILE", "MSG"+logs, "SUBJECT"+ System.currentTimeMillis(), "",
result -> {
Timber.log(Log.DEBUG, "sendSupport Thread.currentThread().getName() " + Thread.currentThread().getName());
if (result.isSuccess) {
Timber.d("is sent");
writeLogFile();
} else {
Timber.d("is NOT sent");
}
}
);
}
private void writeLogFile()
{
try {
StringBuilder logBuilder = new StringBuilder();
process = Runtime.getRuntime().exec( "logcat -d");
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = bufferedReader.readLine()) != null) {
logBuilder.append(line + "\n");
}
logs = logBuilder.toString();
} catch (IOException e) {
e.printStackTrace();
}
}
As a result I could not figure out how am I going to be able to get all logs in periodically in my email.
Thanks.
While the #Knossos answer is pointing the reason why some logs are missing, it doesn't suggest how to use that knowledge to reliably get the logs from users' phones, when you don't have an access to their devices to run some adb commands.
Here is what I suggest to do instead:
As you do already use Timber, just add some more power to it. Namely, add an additional LoggingTree that will save the logs into a file, instead of just posting them to Logcat. You can have many Timber Trees working simultaneously, so you can have both Logcat and File logs if needed.
Use the same timer to send an email message when and where needed. But, instead of using access to logcat -d, simply use the file where Timber have already written the logs. Don't forget to flush the stream before sending the email. In order not to send the same logs again and again, configure the FileTree in a way that it creates a new file every time when a previous one is sent (seta a new file name via a method call, for example).
Profit :)
To log into two different systems (Trees), you need to simply add one more to Timber:
Timber.plant(new Timber.DebugTree());
Timber.plant(new FileLoggingTree());
And here is an example of FileLoggingTree(source):
public class FileLoggingTree extends Timber.DebugTree {
private static Logger mLogger = LoggerFactory.getLogger(FileLoggingTree.class);
private static final String LOG_PREFIX = "my-log";
public FileLoggingTree(Context context) {
final String logDirectory = context.getFilesDir() + "/logs";
configureLogger(logDirectory);
}
private void configureLogger(String logDirectory) {
// reset the default context (which may already have been initialized)
// since we want to reconfigure it
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.reset();
RollingFileAppender<ILoggingEvent> rollingFileAppender = new RollingFileAppender<>();
rollingFileAppender.setContext(loggerContext);
rollingFileAppender.setAppend(true);
rollingFileAppender.setFile(logDirectory + "/" + LOG_PREFIX + "-latest.html");
SizeAndTimeBasedFNATP<ILoggingEvent> fileNamingPolicy = new SizeAndTimeBasedFNATP<>();
fileNamingPolicy.setContext(loggerContext);
fileNamingPolicy.setMaxFileSize("1MB");
TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new TimeBasedRollingPolicy<>();
rollingPolicy.setContext(loggerContext);
rollingPolicy.setFileNamePattern(logDirectory + "/" + LOG_PREFIX + ".%d{yyyy-MM-dd}.%i.html");
rollingPolicy.setMaxHistory(5);
rollingPolicy.setTimeBasedFileNamingAndTriggeringPolicy(fileNamingPolicy);
rollingPolicy.setParent(rollingFileAppender); // parent and context required!
rollingPolicy.start();
HTMLLayout htmlLayout = new HTMLLayout();
htmlLayout.setContext(loggerContext);
htmlLayout.setPattern("%d{HH:mm:ss.SSS}%level%thread%msg");
htmlLayout.start();
LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<>();
encoder.setContext(loggerContext);
encoder.setLayout(htmlLayout);
encoder.start();
// Alternative text encoder - very clean pattern, takes up less space
// PatternLayoutEncoder encoder = new PatternLayoutEncoder();
// encoder.setContext(loggerContext);
// encoder.setCharset(Charset.forName("UTF-8"));
// encoder.setPattern("%date %level [%thread] %msg%n");
// encoder.start();
rollingFileAppender.setRollingPolicy(rollingPolicy);
rollingFileAppender.setEncoder(encoder);
rollingFileAppender.start();
// add the newly created appenders to the root logger;
// qualify Logger to disambiguate from org.slf4j.Logger
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
root.setLevel(Level.DEBUG);
root.addAppender(rollingFileAppender);
// print any status messages (warnings, etc) encountered in logback config
StatusPrinter.print(loggerContext);
}
#Override
protected void log(int priority, String tag, String message, Throwable t) {
if (priority == Log.VERBOSE) {
return;
}
String logMessage = tag + ": " + message;
switch (priority) {
case Log.DEBUG:
mLogger.debug(logMessage);
break;
case Log.INFO:
mLogger.info(logMessage);
break;
case Log.WARN:
mLogger.warn(logMessage);
break;
case Log.ERROR:
mLogger.error(logMessage);
break;
}
}
}
The problem is that logcat -d is only delivering you the latest X bytes of data from the stream. You aren't guaranteed to get everything between 10 minute intervals.
In the best case, you get what you want. In the worst cases, you miss log data or log sections overlap (you get some from the previous dump too).
You can see this here: adb logcat -d | dd
...
03-27 11:36:27.474 791 22420 E ResolverController: No valid NAT64 prefix (147, <unspecified>/0)
03-27 11:36:27.612 3466 3521 I PlayCommon: [657] alsu.c(187): Successfully uploaded logs.
453+111 records in
499+1 records out
255863 bytes (256 kB, 250 KiB) copied, 0,136016 s, 1,9 MB/s
As you can see, it is clearly a 256 kB chunk that is pulled through logcat -d.
On the plus side, you can change that! If you look at adb logcat --help you can see options.
For example, if you use adb logcat -d -t '100000' | dd (all logs in the last 100000 seconds. I now have the following:
...
03-27 11:45:41.687 791 1106 I netd : bandwidthSetGlobalAlert(2097152) <0.90ms>
03-27 11:45:42.098 21897 23376 V FA : Inactivity, disconnecting from the service
2237+1558 records in
2879+1 records out
1474408 bytes (1,5 MB, 1,4 MiB) copied, 1,20785 s, 1,2 MB/s
1.5 MB of logs. You should be able to get all logs with this.
You log the timestamp of each logcat pull, then use that each time to determine the seconds since the last pull.
I hope that helps!
You cannot send an email from the device without user interaction or implementing the email function yourself that allows to do this without user interaction.
Most apps have some api endpoint to send the logs to.
For some reason HttpURLConnection appears to be buffering the upload data no matter what I try. I can show the progress percentage of the data, but it is clear that the progress advances way too fast while the data is not flowing at that high rate.
The receiving server is not in the intranet, but hosted somewhere. The edge router is throttling the upload bandwidth to 2mbit in order to simulate a slow network, and in the bandwidth graph of the router I can see the data rate graph for the development device. The WiFi AP also allows me to see a graph of the data rate, and it looks just like the one of the edge router, so no device in the intranet is buffering the data. It is definitely the development device (Nexus 5X)
The following is the code that is being used:
HttpURLConnection hucConnection = (HttpURLConnection) url.openConnection();
//hucConnection.setUseCaches(false); // does not solve the issue
//hucConnection.setDefaultUseCaches(false); // does not solve the issue
//hucConnection.setAllowUserInteraction(true); // does not solve the issue
hucConnection.setConnectTimeout(6 * 1000);
hucConnection.setReadTimeout(30 * 1000);
hucConnection.setRequestProperty("content-type", "application/json; charset=UTF-8");
hucConnection.setRequestMethod("POST");
hucConnection.setDoInput(true);
hucConnection.setDoOutput(true);
// Data to transfer
byte[] bData = joTransfer.toString().getBytes("UTF-8");
int iDataLength = bData.length;
//hucConnection.setRequestProperty("content-transfer-encoding", "binary"); // does not solve the issue
// use compression
hucConnection.setRequestProperty("content-encoding", "deflate");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION);
DeflaterOutputStream zip = new DeflaterOutputStream(stream, deflater);
zip.write(bData);
zip.close();
deflater.end();
byte[] bZippedData = stream.toByteArray();
Integer iZippedDataLength = bZippedData.length;
int iChunk = 1000;
hucConnection.setChunkedStreamingMode(iChunk);
//hucConnection.setFixedLengthStreamingMode(iZippedDataLength); // does not solve the issue
hucConnection.connect();
OutputStream osOutputStream = hucConnection.getOutputStream();
// FROM HERE ---->>>
int iUploadedLength;
for (iUploadedLength = 0; iUploadedLength < iZippedDataLength - iChunk; iUploadedLength += iChunk) {
LogWrapper.e(TAG, "l -> f:" + iUploadedLength + " t:" + (iUploadedLength+iChunk));
osOutputStream.write(Arrays.copyOfRange(bZippedData, iUploadedLength , iUploadedLength+iChunk));
osOutputStream.flush();
}
LogWrapper.e(TAG, "r -> f:" + iUploadedLength + " t:" + iZippedDataLength);
osOutputStream.write(Arrays.copyOfRange(bZippedData, iUploadedLength, iZippedDataLength));
osOutputStream.flush();
osOutputStream.close();
// <<<---- TO HERE ---- XXXXXXXXX max 1 second XXXXXXXXX
// FROM HERE ---->>>
int iResponseCode = hucConnection.getResponseCode();
// <<<---- TO HERE ---- XXXXXXXXX about 10 seconds XXXXXXXXX
if (iResponseCode != HttpURLConnection.HTTP_OK) {
...
I expected the calls to osOutputStream.flush(); to force the HttpURLConnection to send the data to the server, but for some reason that isn't happening.
It appears to get buffered somewhere, because after the osOutputStream.close(); and before the hucConnection.getResponseCode(); the data is getting uploaded to the server.
All the transfers (upload and download) are working properly, no data is damaged.
Is there a way to fix this, or an alternative to using HttpURLConnection? I've read that the Socket class does not have this problem, but I'm not sure if it handles redirects and stuff like that properly. I don't need to use cookies or some other stuff.
The aprox. 10 seconds it takes for hucConnection.getResponseCode(); to finish is when about 3MB are uploaded (3MB*8b/B = 24Mb, 24Mb/2Mb/s = 12s), the data that is downloaded is getting sent after that call. The progress of the downloaded data is precise.
Is it possible that a 3rd party library is altering HttpURLConnection's behavior and doing some proxying? Like Firebase or something? I already disabled Crashlytics, but I think that Firebase also does some kind of stats gathering (response time). I think I had some strange issues about 1-2 months ago in another app, where I was getting a Proxy error issue in the domain name resolution, as if something inside of Android was proxying network traffic.
I'm about to give OkHttp a try, one of their recipies has a Post Streaming example (https://github.com/square/okhttp/wiki/Recipes)
Update: I implemented it using okhttp3, following the above mentioned recipie. I have the exact same problem there.
This is on Android 8.1
The server is an nginx instance.
I also ran the app on a Genymotion emulator instance, same OS, and it looks like it's better there, yet the problem still seems to be present, a bit. While radical throttling on the edge router has no effect on the Nexus 5X, it does have an effect on the emulator. But nonetheless, even the emulator upload tracking precision leaves much to be desired.
Would it make sense to use a WebSocket connection for that? That would be my last resort.
The logic is for downloading used in AsyncTask, but I think, that it should be the same (just a switching input>output and so on)
InputStream inputStream = null;
try {
try {
OutputStream outputStream = new FileOutputStream(documentFile, false);
try {
inputStream = httpConn.getInputStream();
byte[] buffer = new byte[4 * 1024]; // or other buffer size
long downloaded = 0;
long target = dataLength;
int readed;
long updateSize = target / 10;
long updateHelp = 0;
while ((readed = inputStream.read(buffer)) != -1) {
downloaded += readed;
updateHelp += readed;
if (updateHelp >= updateSize) {
updateHelp = 0;
publishProgress(downloaded, target);
}
outputStream.write(buffer, 0, readed);
if (isCancelled()) {
return false;
}
}
outputStream.flush();
outputStream.close();
return true;
} catch (Exception e) {
return false;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
I am trying to get the app code and display it, for an example if button X starts a new activity then a textView displays the whole method
I reached only how can I display code in HTML format from this question
But is there is a way to get the code of my app out, I think that there are 2 ways
An Internal one by getting it by the app itself
An External one by reading the java file then filtering it and getting the text of the method
Is there are any ideas about that?
Thanks in advance
The above is not currently possible as mentioned by others is the comments. What i can suggest is shipping your application with the source code in the assets folder and using a helper function to extract a certain methods from the source at runtime (your second proposed approach). I have written example code but it is in pure java and needs to be ported to android (a few lines).
NB: You may need to reformat the code after extraction depending on your use case.
Hope it helps :)
The code for the helper method:
static String getTheCode(String classname ,String methodSignature ) throws FileNotFoundException {
//**********************A few lines of code below need changing when porting ***********//
// open file, your will be in the assets folder not in the home dir of user, don't forget the .java extension when porting
File file = new File(System.getProperty("user.home") +"/"+ classname +".java");
// get the source, you can use FileInputReader or some reader supported by android
Scanner scanner = new Scanner(file);
String source = "";
while(scanner.hasNext()) {
source += " "+ scanner.next();
}
//**********************The above code needs changing when porting **********//
// extract code using the method signature
methodSignature = methodSignature.trim();
source = source.trim();
//appending { to differentiate from argument as it can be matched also if in the same file
methodSignature = methodSignature+"{";
//making sure we find what we are looking for
methodSignature = methodSignature.replaceAll("\\s*[(]\\s*", "(");
methodSignature = methodSignature.replaceAll("\\s*[)]\\s*", ")");
methodSignature = methodSignature.replaceAll("\\s*[,]\\s*", ",");
methodSignature = methodSignature.replaceAll("\\s+", " ");
source =source.replaceAll("\\s*[(]\\s*", "(");
source = source.replaceAll("\\s*[)]\\s*", ")");
source = source.replaceAll("\\s*[,]\\s*", ",");
source = source.replaceAll("\\s+", " ");
if(!source.contains(methodSignature)) return null;
// trimming all text b4 method signature
source = source.substring(source.indexOf(methodSignature));
//getting last index, a methods ends when there are matching pairs of these {}
int lastIndex = 0;
int rightBraceCount = 0;
int leftBraceCount = 0;
char [] remainingSource = source.toCharArray();
for (int i = 0; i < remainingSource.length ; i++
) {
if(remainingSource[i] == '}'){
rightBraceCount++;
if(rightBraceCount == leftBraceCount){
lastIndex = (i + 1);
break;
}
}else if(remainingSource[i] == '{'){
leftBraceCount++;
}
}
return source.substring(0 ,lastIndex);
}
Example usage (getTheCode methods is static and in a class called GetTheCode):
public static void main(String... s) throws FileNotFoundException {
System.out.println(GetTheCode.getTheCode("Main", "private static void shoutOut()"));
System.out.println(GetTheCode.getTheCode("Main", "private static void shoutOut(String word)"));
}
Output:
private static void shoutOut(){ // nothing to here }
private static void shoutOut(String word){ // nothing to here }
NB: When starting your new activity create a method eg
private void myStartActivty(){
Intent intent = new Intent(MyActivity.this, AnotherActivity.class);
startActivity(intent);
}
Then in your onClick:
#Override
public void onClick(View v) {
myStartActivity();
myTextView.setText(GetTheCode.getTheCode("MyActivity","private void myStartActivity()"));
}
Update: Ported the Code for android:
import android.content.Context;
import java.io.IOException;
import java.util.Scanner;
public class GetTheCode {
static String getTheCode(Context context, String classname , String methodSignature ) {
Scanner scanner = null;
String source = "";
try {
scanner = new Scanner(context.getAssets().open(classname+".java"));
while(scanner.hasNext()) {
source += " "+ scanner.next();
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
scanner.close();
// extract code using the method signature
methodSignature = methodSignature.trim();
source = source.trim();
//appending { to differentiate from argument as it can be matched also if in the same file
methodSignature = methodSignature+"{";
//making sure we find what we are looking for
methodSignature = methodSignature.replaceAll("\\s*[(]\\s*", "(");
methodSignature = methodSignature.replaceAll("\\s*[)]\\s*", ")");
methodSignature = methodSignature.replaceAll("\\s*[,]\\s*", ",");
methodSignature = methodSignature.replaceAll("\\s+", " ");
source =source.replaceAll("\\s*[(]\\s*", "(");
source = source.replaceAll("\\s*[)]\\s*", ")");
source = source.replaceAll("\\s*[,]\\s*", ",");
source = source.replaceAll("\\s+", " ");
if(!source.contains(methodSignature)) return null;
// trimming all text b4 method signature
source = source.substring(source.indexOf(methodSignature));
//getting last index, a methods ends when there are matching pairs of these {}
int lastIndex = 0;
int rightBraceCount = 0;
int leftBraceCount = 0;
char [] remainingSource = source.toCharArray();
for (int i = 0; i < remainingSource.length ; i++
) {
if(remainingSource[i] == '}'){
rightBraceCount++;
if(rightBraceCount == leftBraceCount){
lastIndex = (i + 1);
break;
}
}else if(remainingSource[i] == '{'){
leftBraceCount++;
}
}
return source.substring(0,lastIndex);
}
}
Usage:
// the method now takes in context as the first parameter, the line below was in an Activity
Log.d("tag",GetTheCode.getTheCode(this,"MapsActivity","protected void onCreate(Bundle savedInstanceState)"));
Let's start with a broader overview of the problem:
Display App code
Press X button
Open new activity with a textview which displays the method
The goal is to do the following:
Viewing app method by extracting it and then building & running it.
There are some methods we can use to run Java/Android code dynamically. The way I would personally do it is DexClassLoader and with Reflection.
If you need more details, let me know. Here is what it'd do though:
View app method
Upon pressing X, launch intent with extra to new Activity
Parse and compile code dynamically and then run it with DexClassLoader and Reflection
Sources:
Sample file loading Java method from TerminalIDE Android App
Android Library I made for Auto-Updating Android Applications without needing the Play Store on non-root devices
Well in app I'm trying to pull the data from sever for every 4 sec,and update the app.
I'm using handler,in that I'm calling AsynTask to fetch the data from server for every 4 sec.
Just I'm worried about the instance created for AsynTask every 4'sec causes any problem ?
This is what I'm doing.
private static final int DELAY = 1000 * 4;
final Handler printHandler = new Handler();
private boolean keepLooping = true;
printHandler.postDelayed(printStuff, DELAY);
Runnable printStuff = new Runnable(){
#Override
public void run(){
// call AsynTask to perform network operation on separate thread
new DownloadMainScore().execute("http://server/root/score.php");
if(keepLooping)
printHandler.postDelayed(this, DELAY);
}
};
On your choice of concurrency tool:
You are right that this is not so good. AsyncTasks are designed to be useful helpers when designing occasional asynchronous calls that then need to update a UI. As such, in old (< 1.6) versions of Android the maximum thread pool size was 10!
It would be better to go straight to the very robust Java out of which AsyncTask is built. Given you want to do this repeatedly, try a ScheduledExecutorService. I see they've even made a nice example for you.
Or, given that you seem to be getting a score down, best might be to maintain a persistent connection over a protocol like XMPP, for which there are many Java server and clients.
Finally, you might like to look at gcm.
On design issues in general
I see you want to print a score frequently. Once every four seconds in fact. But what's the point is the score hasn't changed? Furthermore, what if you've got a slow internet connection, and eight seconds later the one for four seconds ago hasn't finished? Right now you will set off yet another download request, even though the other one when it comes back will be up to date!
The solution is to decouple the download mechanism and the UI update mechanism. One way to do it is to have your scheduled download on a single threaded executor- not something you can control in an AsyncTask, which when finishes causes the UI to update and show the score.
Wishing you the best of luck!
Code sketch
Don't have environment set up right now, but in a very rough code sketch (check syntax), using a scheduled executor would look like:
In class:
private final ScheduledExecutorService downloadScheduler = Executors.newSingleThreadScheduledExecutor(1);
Then elsewhere, wherever you start doing this
final Runnable scoreHttpRunnable = new Runnable() {
#Override public void run() {
...
//do Http Syncronously here- I guess whatever is in the doInBackground(...) part of that Async task you wrote!
...
final int newScoreResult = ... (do whatever you do here)
...
runOnUiThread(new Runnable() { #Override public void run() { yourView.updateHoweverYouLike(newScoreResult); } })
...
};
downloadScheduler.scheduleAtFixedRate(scoreHttpRunnable, 0, 4, TimeUnit.SECONDS);
Going one of the other two routes is really too much to post in a single answer to a question. That'd be a another SO question if there isn't already one.
Be sure that next call send to asyc class only after once its done for that make a variable(IsLoadRunning) and make it true in on preExecute() and false in onPOstExecute and add a condition if(!IsLoadRunning){new DownloadMainScore().execute();}
As official documentation states
AsyncTasks should ideally be used for short operations (a few seconds at the most.)
Services can serve better in you case. Have a look at the accepted answer here
#Override
protected String doInBackground(String... params) {
Log.d(TAG, "type - " + params[0] + ", url = " + params[1] + ", name = " + params[2]);
downloadFile(params[1], params[2]);
return null;
}
here is download method
URL url = new URI(Url.replace(" ", "%20")).toURL();
URLConnection connection = url.openConnection();
connection.setConnectTimeout(1000);
int fileLength = connection.getContentLength();
mSavePath = CommonUtilities.getFileSavePath(mContext, fileName, fileLength);
Log.d(TAG, "*** saveFilePath - " + mSavePath);
InputStream inputStream = connection.getInputStream();
if (inputStream != null) {
File file = new File(mSavePath);
BufferedOutputStream bufferOutputStream = new BufferedOutputStream(new FileOutputStream(file));
byte byteArray[] = new byte[1024];
int len = 0;
long total = 0;
while ((len = inputStream.read(byteArray)) != -1) {
bufferOutputStream.write(byteArray, 0, len);
total += len;
}
bufferOutputStream.flush();
bufferOutputStream.close();
inputStream.close();
} else {
Log.d(TAG, "*** inputStream is null");
}