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;
}
Related
Please tell me that how can I write my own created logs to text file in device ?
I found this code in stackoverflow itself but this code it prints whole logcat, How can I filter the same?
public static void write() {
try {
Process process = Runtime.getRuntime().exec("logcat -d");
// Log.e("","******************---1");
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
log = new StringBuilder();
String line;
// Log.e("","******************---2");
while ((line = bufferedReader.readLine()) != null) {
log.append(line);
}
} catch (IOException exception) {
}
//convert log to string
final String logString = new String(log.toString());
//create text file in SDCard
File sdCard = Environment.getExternalStorageDirectory();
// Log.e("","******************---3");
File dir = new File(sdCard.getAbsolutePath() + "/myLogcat");
dir.mkdirs();
File file = new File(dir, "logcat.txt");
try {
//to write logcat in text file
FileOutputStream fOut = new FileOutputStream(file);
OutputStreamWriter osw = new OutputStreamWriter(fOut);
// Write the string to the file
osw.write(logString);
// Log.e("", "******************---4");
osw.flush();
osw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Try this :
try {
Process process = Runtime.getRuntime().exec("logcat -d");
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
StringBuilder log=new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
log.append(line);
}
appendLog(log.toString());
} catch (IOException e) {
}
Method to write log to file:
public void appendLog(String text) {
File logFile = new File("sdcard/Log.txt");
if (!logFile.exists()) {
try {
logFile.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
// BufferedWriter for performance, true to set append to file flag
BufferedWriter buf = new BufferedWriter(new FileWriter(logFile,
true));
buf.append(text);
buf.newLine();
buf.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Add the following permission in manifest file also :
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
First check out the log locations here: https://android.stackexchange.com/questions/14430/how-can-i-view-and-examine-the-android-log
If that's not good enough, you can always make your own Log.java class which would log directly to the file you want (though you should really reconsider using LogCat as it is working as expected).
As a alternate way you can create your custom logger.
Here is one sample util class that I have created for my project.
import android.os.Environment;
import android.util.Log;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
/**
* TODO: Add a class header comment!
*
* #author Dhaval Patel
* #version 1.0, May 24, 2015
* #since 1.0
*/
public final class Logger {
public static final int MAX_FILE_SIZE = 1024*1024*5;// max file size 5MB, if file size increase, Logger will create new file.
private static final String LOG_PREFIX = "prefix_";
private static final int LOG_PREFIX_LENGTH = LOG_PREFIX.length();
private static final int MAX_LOG_TAG_LENGTH = 23;
private static final Boolean ENABLE_CONSOLE_LOG = true; //Flag to enable or disable console log
private static final Boolean ENABLE_FILE_LOG = true; //Flag to enable or disable file log
private static final LogLevel GLOBAL_LOG_LEVEL = LogLevel.VERBOSE; //Flag indicate log level
private static final String LOG_DIRECTORY = Environment.getExternalStorageDirectory()+"log"+File.separator; //Log directory
public static String makeLogTag(String str) {
if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) {
return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1);
}
return LOG_PREFIX + str;
}
private enum LogLevel{
VERBOSE(Log.VERBOSE),
DEBUG(Log.DEBUG),
INFO(Log.INFO),
WARNING(Log.WARN),
ERROR(Log.ERROR),
ASSERT(Log.ASSERT);
private final int logLevel;
LogLevel(int logLevel) {
this.logLevel = logLevel;
}
public int getLogLevel() {
return logLevel;
}
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
*
*/
public static void v(String tag, String msg) {
write(LogLevel.VERBOSE, tag, msg);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
* #param tr An exception to log
*
*/
public static void v(String tag, String msg, Throwable tr) {
write(LogLevel.VERBOSE, tag, msg, tr);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
*
*/
public static void d(String tag, String msg) {
write(LogLevel.DEBUG, tag, msg);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
* #param tr An exception to log
*
*/
public static void d(String tag, String msg, Throwable tr) {
write(LogLevel.DEBUG, tag, msg, tr);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
*
*/
public static void i(String tag, String msg) {
write(LogLevel.INFO, tag, msg);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
* #param tr An exception to log
*
*/
public static void i(String tag, String msg, Throwable tr) {
write(LogLevel.INFO, tag, msg, tr);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
*
*/
public static void w(String tag, String msg) {
write(LogLevel.WARNING, tag, msg);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
* #param tr An exception to log
*
*/
public static void w(String tag, String msg, Throwable tr) {
write(LogLevel.WARNING, tag, msg, tr);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
*
*/
public static void e(String tag, String msg) {
write(LogLevel.ERROR, tag, msg);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
* #param tr An exception to log
*
*/
public static void e(String tag, String msg, Throwable tr) {
write(LogLevel.ERROR, tag, msg, tr);
}
private static boolean isLogEnable(LogLevel logLevel){
return GLOBAL_LOG_LEVEL.getLogLevel() <= logLevel.getLogLevel();
}
private static void write(LogLevel logLevel, String tag, String log) {
if (isLogEnable(logLevel) && ENABLE_FILE_LOG){
StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[4];
String logPoint = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
String msg = "["+getCurrentDateTime()+"] "+ logLevel.name() +" "+ logPoint +" "+tag+"//:"+log;
write(msg);
}
if (isLogEnable(logLevel) && ENABLE_CONSOLE_LOG){
Log.println(logLevel.getLogLevel(), makeLogTag(tag), log);
}
}
private static void write(LogLevel logLevel, String tag, String log, Throwable tr){
if (isLogEnable(logLevel) && ENABLE_FILE_LOG){
StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[4];
String logPoint = stackTraceElement.getClassName() + "::" + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
String msg = "["+getCurrentDateTime()+"] "+ logLevel.name() +" "+ logPoint+" "+tag+"//:"+log+"\n"+Log.getStackTraceString(tr);
write(msg);
}
if (isLogEnable(logLevel) && ENABLE_CONSOLE_LOG){
Log.println(logLevel.getLogLevel(), makeLogTag(tag), log + "\n" + Log.getStackTraceString(tr));
}
}
private static void write(String text){
BufferedWriter out = null;
String filePath=LOG_DIRECTORY;
try {
SimpleDateFormat df = new SimpleDateFormat("dd_MMM_yyyy", Locale.ENGLISH);
String formattedDate = df.format(System.currentTimeMillis());
if(!new File(LOG_DIRECTORY).exists()) {
new File(LOG_DIRECTORY).mkdirs();
}
filePath = LOG_DIRECTORY +formattedDate+".log";
while (new File(filePath).exists() && new File(filePath).length() > MAX_FILE_SIZE) {
String[] txt1 = filePath.split("\\.log");
int fileNum = 1;
if (txt1.length == 2) {
fileNum = Integer.parseInt(txt1[1].substring(1));
fileNum++;
}
filePath = LOG_DIRECTORY + formattedDate + ".log" + "." + fileNum;
}
if(!new File(filePath).exists()){
new File(filePath).createNewFile();
}
if(new File(filePath).exists()){
FileWriter fStream = new FileWriter(filePath, true);
out = new BufferedWriter(fStream);
out.write(text + "\n" + new File(filePath).length());
out.flush();
}
} catch (IOException e) {
Log.e("Log", "Path:"+filePath);
e.printStackTrace();
} catch (Exception e) {
Log.e("Log", e.getMessage());
e.printStackTrace();
} finally {
try {
if(out!=null)
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static String getCurrentDateTime(){
return new SimpleDateFormat("dd MMM yyyy HH:mm:ss:SSS" , Locale.getDefault()).format(Calendar.getInstance().getTime());
}
}
Use it same like your android Log.
Logger.i("Info", "my sample log");
Here log store in file date-wise, you can modify write method as per your needs.
I'm making an Android app that needs to allow client to maintain the resources from their server which would include strings, drawables etc.
I've already created a mechanism for downloading a zip file with all these files, and they're able to change strings pretty easy, I've also created a mechanism that allows the client to change bg color for UI controls, to change width, height etc but I have a feeling that there must be a better way to create all this.
So I believe the real question is:
What's the best practice to create a custom theme, deploy it on server, make the app download it, and apply it to app afterwards?
I know how to create custom theme and how to deploy it with the app, and how to apply it during runtime, but the problem here is that resources are pre-compiled and once you create APK there's no way for developer to change them which would be required in order to add new themes/drawables/styles/strings.
Do I need to create a custom mechanism for all this (loading images, styles, strings etc from the file system) and to apply them during runtime by creating my own controls that would do that in constructor for example or is there a way to do this properly :)? ( how does Swiftkey do this with all the keyboard themes, and how do similar apps do it allowing the users to download theme and apply it after that )?
I'm sorry if I didn't see similar question, I really tried to find an answer myself during past 2 days, but I failed to find anything useful, so this is my last chance to get a constructive answer :).
The closest to solution i need was this answer: Changing app theme at runtime using using external theme file but I've already made that functionality, and i know i can change colors like that, but the problem is that i would like to be able to change things like borders, on button pressed state etc that require resources other than simple color value :(.
Thanks heaps!
P.S. I've also read about the extension files so is that something i need to consider while thinking about this, or do i need to look elsewhere? The problem with obb files is that they must be deployed over PlayStore and that's not "perfect" for the client because they need to pack it by using jobb, and to deploy it to PlayStore which is too technical for them, so they would prefer creating a zip file, putting it on server, and the app should do the rest :).
I've finally decided to solve this by making a custom system for handling drawables, strings etc so now i have a custom class called "ResourceManager" that handles what needs to be loadad and how, and themes are distributed as a zip file which app downloads, extracts and later uses.
I had to compile nine patch images by myself before putting them in zip file, and I did that using "abrc" from here: http://forum.xda-developers.com/showthread.php?t=785012
I've also created a simple bash script that goes recursively through custom folder and compiles all nine patch images with abrc.
I've also created a simple helper in the ResourceManager that checks and tells me the screen density so i can normally support images in hdpi, xhdpi etc densities, and finally i don't re-create the images every time i need them, i save them in a static list of HashMap so i can reuse the ones I've already created and that way i hope to prevent wasting too much phone's memory :).
OK that's all in short lines, if anyone has any questions please let me know, i'll be glad to share this experience with anyone.
Cheers!
============ EDIT ============
Here's the class I ended up writing for this purpose (it downloads the file, checks for it's version, loads strings from JSON file rather than strings.xml etc)
NOTE: This is not a full class so some parts are missing, but I think it's more than enough to get the idea how I solved all this :)
/**
* Created by bojank on 7/28/2014.
* Class that handles custom resources downloaded from server
*/
public class ResourceManager {
// List of ninePatchImages in the application
private static ArrayList<HashMap<String, NinePatchDrawable>> ninePatchHashMaps;
private static ArrayList<HashMap<String, Drawable>> imagesHashMaps;
private static ImageLoader imageLoader;
// Context for methods
public static Context ctx;
// JSONObject with all strings
private static JSONObject joString;
// JSONObject with all styles
private static JSONObject joStyles;
// String with current active lang code
private static String currentLanguage;
private static String sdcardPath;
// Private consturctor to prevent creating a class instance
private ResourceManager() {
}
/**
* Method that returns a translated string for given key
*
* #param key String
* #return String
*/
public static String getString(String module, String key) {
String output = ""; //String.format("[%s - %s]", module, key);
try {
if (getStringsFile() != null && getStringsFile().getJSONObject(module).has(key))
output = getStringsFile().getJSONObject(module).getString(key);
} catch (Exception e) {
// Force some default language if proper json file is missing for newly added language
currentLanguage = "en-US";
Helper.saveLocale(currentLanguage, ctx);
Helper.logError("ErrorFetchingString", e);
}
return output;
}
/**
* Method that returns JSONObject with string resources
* #return JSONObject
* #throws JSONException
*/
public static JSONObject getStringsFile() throws JSONException {
if (joString == null) {
String stringFileName = getResourcesPath() + "languages/" + getCurrentLanguage() + "/values.json";
String languageFile = Helper.readJsonFile(stringFileName);
if (languageFile != null) {
joString = new JSONObject(Helper.readJsonFile(stringFileName));
} else {
return null;
}
}
return joString.getJSONObject("strings");
}
/**
* Method that returns current language ("sr", "en"...)
* #return String
*/
public static String getCurrentLanguage() {
if (currentLanguage == null)
currentLanguage = Helper.getCurrentLanguage(ctx);
return currentLanguage;
}
/**
* Method that resets joString object and currentLanguage on language change
*/
public static void resetLanguage() {
joString = null;
currentLanguage = null;
}
/**
* Method that resets joStyles object on theme change
*/
public static void resetStyle() {
joStyles = null;
}
/**
* Method that deletes a directory from filesystem
* #param path File
* #return boolean
*/
public static boolean deleteDirectory(File path) {
if( path.exists() ) {
File[] files = path.listFiles();
for(int i=0; i<files.length; i++) {
if(files[i].isDirectory()) {
deleteDirectory(files[i]);
}
else {
files[i].delete();
}
}
}
return(path.delete());
}
/**
* Method that get's the version of assets file
* #param url String
*/
public static String getAssetsVersion(String url) throws IOException {
Helper.logInfo("REQUEST URL:", url);
OkHttpClient client = new OkHttpClient();
// set connection timeut to 5min
client.setConnectTimeout(1, TimeUnit.MINUTES);
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
/**
* Method that downloads assets file from server
* #param url String
* #return String
* #throws IOException
*/
public static String getAssetsFile(String url) throws IOException {
Helper.logInfo("REQUEST URL:", url);
OkHttpClient client = new OkHttpClient();
// set connection timeut to 5min
client.setConnectTimeout(1, TimeUnit.MINUTES);
Request request = new Request.Builder()
.url(url)
.header("User-Agent", MyApplication.USER_AGENT)
.build();
Response response = client.newCall(request).execute();
InputStream inputStreamFile = response.body().byteStream();
try {
// Output stream
String outputFileName = Environment.getExternalStorageDirectory().toString() + "/assets.zip";
File deleteFile = new File(outputFileName);
deleteFile.delete();
OutputStream output = new FileOutputStream(outputFileName);
byte data[] = new byte[1024];
int count;
// writing data to file
while ((count = inputStreamFile.read(data)) != -1)
output.write(data, 0, count);
// flushing output
output.flush();
// closing streams
output.close();
inputStreamFile.close();
return outputFileName;
} catch (Exception e) {
Helper.logError("Download Resursa", e);
return "ERROR";
}
}
public static void setStyle(View v, String styleName) {
try {
if (styleName == null || styleName.equals("")) {
if (v instanceof EditText)
processStyle(v, getStylesFile().getJSONObject("EditText"));
} else
processStyle(v, getStylesFile().getJSONObject(styleName));
} catch (Exception e) {
Helper.logError("Setting Styles", e);
}
}
private static void setBackground(View v, Drawable d) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
v.setBackgroundDrawable(d);
} else {
v.setBackground(d);
}
}
public static JSONObject getStylesFile() throws JSONException {
if (joStyles == null) {
String stylesFileName = getResourcesPath() + "styles/properties.json";
joStyles = new JSONObject(Helper.readJsonFile(stylesFileName));
}
return joStyles;
}
public static void processStyle(View v, JSONObject joStyle) {
if(joStyle != null) {
try {
// used for layout margins
LinearLayout.LayoutParams layoutParams = null;
if (Helper.isValidParameter(joStyle, "backgroundColor"))
v.setBackgroundColor(Color.parseColor(joStyle.getString("backgroundColor")));
if (Helper.isValidParameter(joStyle, "backgroundImage"))
setBackground(v, loadNinePatchFromFilesystem(getImagesPath() + joStyle.getString("backgroundImage")));
if (v instanceof TextView) {
applyTextViewParameters(v, joStyle);
} else if (v instanceof ListView) {
if (Helper.isValidParameter(joStyle, "dividerColor")) {
((ListView) v).setDivider(new ColorDrawable(Color.parseColor(joStyle.getString("dividerColor"))));
((ListView) v).setDividerHeight(Helper.convertDpToPixel(1));
}
if (Helper.isValidParameter(joStyle, "dividerHeight")) {
((ListView) v).setDividerHeight(Helper.convertDpToPixel(joStyle.getInt("dividerHeight")));
}
} else if (v instanceof UnderlinePageIndicator) {
if (Helper.isValidParameter(joStyle, "backgroundColor")) {
v.setBackgroundColor(Color.parseColor(joStyle.getString("backgroundColor")));
}
if (Helper.isValidParameter(joStyle, "selectedColor")) {
((UnderlinePageIndicator) v).setSelectedColor(Color.parseColor(joStyle.getString("selectedColor")));
}
} else if (v instanceof StyleableBackground) {
if (Helper.isValidParameter(joStyle, "backgroundColor")) {
View background = v.findViewById(R.id.llBackground);
if (background != null) {
background.setBackgroundColor(Color.parseColor(joStyle.getString("backgroundColor")));
}
}
if (Helper.isValidParameter(joStyle, "borderTopColor")) {
View topBorder = v.findViewById(R.id.llTopBorder);
if (topBorder != null) {
topBorder.setBackgroundColor(Color.parseColor(joStyle.getString("borderTopColor")));
if (Helper.isValidParameter(joStyle, "borderTopHeight")) {
topBorder.setMinimumHeight(Helper.convertDpToPixel(joStyle.getInt("borderTopHeight")));
}
}
}
if (Helper.isValidParameter(joStyle, "borderBottomColor")) {
View bottomBorder = v.findViewById(R.id.llBottomBorder);
if (bottomBorder != null) {
bottomBorder.setBackgroundColor(Color.parseColor(joStyle.getString("borderBottomColor")));
if (Helper.isValidParameter(joStyle, "borderBottomHeight")) {
bottomBorder.setMinimumHeight(Helper.convertDpToPixel(joStyle.getInt("borderBottomHeight")));
}
}
}
if (Helper.isValidParameter(joStyle, "backgroundImage")) {
ImageView ivBackgroundImage = (ImageView) v.findViewById(R.id.ivBackgroundImage);
if (ivBackgroundImage != null) {
BitmapDrawable d = (BitmapDrawable) ResourceManager.loadImageFromFilesystem(ResourceManager.getImagesPath() + joStyle.getString("backgroundImage"));
d.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
d.setGravity(Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL);
setBackground(ivBackgroundImage, d);
}
}
}
if(Helper.isValidParameter(joStyle, "width"))
v.setMinimumWidth(joStyle.getInt("width"));
if(Helper.isValidParameter(joStyle, "height"))
v.setMinimumHeight(joStyle.getInt("height"));
if(Helper.isValidParameter(joStyle, "padding"))
v.setPadding(joStyle.getInt("padding"), joStyle.getInt("padding"), joStyle.getInt("padding"), joStyle.getInt("padding"));
if(Helper.isValidParameter(joStyle, "paddingLeft"))
v.setPadding(joStyle.getInt("paddingLeft"), v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom());
if(Helper.isValidParameter(joStyle, "paddingTop"))
v.setPadding(v.getPaddingLeft(), joStyle.getInt("paddingTop"), v.getPaddingRight(), v.getPaddingBottom());
if(Helper.isValidParameter(joStyle, "paddingRight"))
v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), joStyle.getInt("paddingRight"), v.getPaddingBottom());
if(Helper.isValidParameter(joStyle, "paddingBottom"))
v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), joStyle.getInt("paddingBottom"));
if(Helper.isValidParameter(joStyle, "margin")) {
layoutParams = new LinearLayout.LayoutParams(v.getLayoutParams());
layoutParams.setMargins(joStyle.getInt("margin"), joStyle.getInt("margin"), joStyle.getInt("margin"), joStyle.getInt("margin"));
}
if(Helper.isValidParameter(joStyle, "marginLeft")) {
layoutParams = new LinearLayout.LayoutParams(v.getLayoutParams());
layoutParams.setMargins(joStyle.getInt("marginLeft"), layoutParams.topMargin, layoutParams.rightMargin, layoutParams.bottomMargin);
}
if(Helper.isValidParameter(joStyle, "marginTop")) {
layoutParams = new LinearLayout.LayoutParams(v.getLayoutParams());
layoutParams.setMargins(layoutParams.leftMargin, joStyle.getInt("marginTop"), layoutParams.rightMargin, layoutParams.bottomMargin);
}
if(Helper.isValidParameter(joStyle, "marginRight")) {
layoutParams = new LinearLayout.LayoutParams(v.getLayoutParams());
layoutParams.setMargins(layoutParams.leftMargin, layoutParams.topMargin, joStyle.getInt("marginRight"), layoutParams.bottomMargin);
}
if(layoutParams != null)
v.setLayoutParams(layoutParams);
RelativeLayout.LayoutParams relativeLayoutParams = null;
if (Helper.isValidParameter(joStyle, "alignParentTop") && joStyle.getBoolean("alignParentTop")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
}
if (Helper.isValidParameter(joStyle, "alignParentLeft") && joStyle.getBoolean("alignParentLeft")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
}
if (Helper.isValidParameter(joStyle, "alignParentBottom") && joStyle.getBoolean("alignParentBottom")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
}
if (Helper.isValidParameter(joStyle, "alignParentRight") && joStyle.getBoolean("alignParentRight")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
}
if(Helper.isValidParameter(joStyle, "marginLeft")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.setMargins(joStyle.getInt("marginLeft"), relativeLayoutParams.topMargin, relativeLayoutParams.rightMargin, relativeLayoutParams.bottomMargin);
}
if(Helper.isValidParameter(joStyle, "marginTop")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.setMargins(relativeLayoutParams.leftMargin, joStyle.getInt("marginTop"), relativeLayoutParams.rightMargin, relativeLayoutParams.bottomMargin);
}
if(Helper.isValidParameter(joStyle, "marginRight")) {
relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
relativeLayoutParams.setMargins(relativeLayoutParams.leftMargin, relativeLayoutParams.topMargin, joStyle.getInt("marginRight"), relativeLayoutParams.bottomMargin);
}
if (relativeLayoutParams != null) {
v.setLayoutParams(relativeLayoutParams);
}
} catch (Exception e) {
Helper.logError("", e);
}
}
}
public static String getSdcardPath() {
if(sdcardPath == null)
sdcardPath = ctx.getApplicationInfo().dataDir;
return sdcardPath;
}
public static String getResourcesPath() {
return getSdcardPath() + "/resources/";
}
public static String getCSSPath() {
return getResourcesPath() + "default.css";
}
public static String getImagesPath() {
return getResourcesPath() + "images/" + ResourceConstants.getScreenDPI(ctx) + "/";
}
public static String getImagesPathNoDpi() {
return getResourcesPath() + "images/";
}
public static NinePatchDrawable loadNinePatchFromFilesystem(String filename) {
if(ninePatchHashMaps == null)
ninePatchHashMaps = new ArrayList<HashMap<String, NinePatchDrawable>>();
// check if we already have this filename so we can reuse it
for (int i = 0; i < ninePatchHashMaps.size(); i++) {
HashMap<String, NinePatchDrawable> row = ninePatchHashMaps.get(i);
if(row.containsKey(filename))
return row.get(filename);
}
NinePatchDrawable patchy = null;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeFile(filename, options);
byte[] chunk = bitmap.getNinePatchChunk();
boolean result = NinePatch.isNinePatchChunk(chunk);
if (result)
patchy = new NinePatchDrawable(bitmap, chunk, new Rect(), null);
} catch (Exception e){
Helper.logError("NinePatchLoading",e);
}
if(patchy != null) {
HashMap<String, NinePatchDrawable> drawableImage = new HashMap<String, NinePatchDrawable>();
drawableImage.put(filename, patchy);
ninePatchHashMaps.add(drawableImage);
}
return patchy;
}
public static Drawable loadImageFromFilesystem(String filename) {
if(imagesHashMaps == null)
imagesHashMaps = new ArrayList<HashMap<String, Drawable>>();
// check if we already have this filename so we can reuse it
for (int i = 0; i < imagesHashMaps.size(); i++) {
HashMap<String, Drawable> row = imagesHashMaps.get(i);
if(row.containsKey(filename))
return row.get(filename);
}
Drawable image = null;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeFile(filename, options);
if(bitmap == null)
bitmap = BitmapFactory.decodeFile(filename.replace(ResourceConstants.getScreenDPI(ctx) + "/", ""), options);
image = new BitmapDrawable(bitmap);
} catch (Exception e){
Helper.logError("ImageLoadingError",e);
}
if(image != null) {
HashMap<String, Drawable> drawableImage = new HashMap<String, Drawable>();
drawableImage.put(filename, image);
imagesHashMaps.add(drawableImage);
}
return image;
}
}
I want to read and react to logcat logs within my application.
I found the following code:
try {
Process process = Runtime.getRuntime().exec("logcat -d");
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
StringBuilder log=new StringBuilder();
String line = "";
while ((line = bufferedReader.readLine()) != null) {
log.append(line);
}
TextView tv = (TextView)findViewById(R.id.textView1);
tv.setText(log.toString());
}
catch (IOException e) {}
This code indeed returns the logcat logs that made until the application was started -
But is it possible to continuously listen to even new logcat logs?
You can keep reading the logs, just by removing the "-d" flag in your code above.
The "-d" flag instruct to logcat to show log content and exit. If you remove the flag, logcat will not terminate and keeps sending any new line added to it.
Just have in mind that this may block your application if not correctly designed.
good luck.
With coroutines and the official lifecycle-livedata-ktx and lifecycle-viewmodel-ktx libraries it's simple like that:
class LogCatViewModel : ViewModel() {
fun logCatOutput() = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
Runtime.getRuntime().exec("logcat -c")
Runtime.getRuntime().exec("logcat")
.inputStream
.bufferedReader()
.useLines { lines -> lines.forEach { line -> emit(line) }
}
}
}
Usage
val logCatViewModel by viewModels<LogCatViewModel>()
logCatViewModel.logCatOutput().observe(this, Observer{ logMessage ->
logMessageTextView.append("$logMessage\n")
})
You can clear your logcat with this method i'm using to clear after writing logcat to a file to avoid duplicated lines:
public void clearLog(){
try {
Process process = new ProcessBuilder()
.command("logcat", "-c")
.redirectErrorStream(true)
.start();
} catch (IOException e) {
}
}
Here is a quick put-together/drop-in that can be used for capturing all current, or all new (since a last request) log items.
You should modify/extend this, because you might want to return a continuous-stream rather than a LogCapture.
The Android LogCat "Manual": https://developer.android.com/studio/command-line/logcat.html
import android.util.Log;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Stack;
/**
* Created by triston on 6/30/17.
*/
public class Logger {
// http://www.java2s.com/Tutorial/Java/0040__Data-Type/SimpleDateFormat.htm
private static final String ANDROID_LOG_TIME_FORMAT = "MM-dd kk:mm:ss.SSS";
private static SimpleDateFormat logCatDate = new SimpleDateFormat(ANDROID_LOG_TIME_FORMAT);
public static String lineEnding = "\n";
private final String logKey;
private static List<String> logKeys = new ArrayList<String>();
Logger(String tag) {
logKey = tag;
if (! logKeys.contains(tag)) logKeys.add(logKey);
}
public static class LogCapture {
private String lastLogTime = null;
public final String buffer;
public final List<String> log, keys;
LogCapture(String oLogBuffer, List<String>oLogKeys) {
this.buffer = oLogBuffer;
this.keys = oLogKeys;
this.log = new ArrayList<>();
}
private void close() {
if (isEmpty()) return;
String[] out = log.get(log.size() - 1).split(" ");
lastLogTime = (out[0]+" "+out[1]);
}
private boolean isEmpty() {
return log.size() == 0;
}
public LogCapture getNextCapture() {
LogCapture capture = getLogCat(buffer, lastLogTime, keys);
if (capture == null || capture.isEmpty()) return null;
return capture;
}
public String toString() {
StringBuilder output = new StringBuilder();
for (String data : log) {
output.append(data+lineEnding);
}
return output.toString();
}
}
/**
* Get a list of the known log keys
* #return copy only
*/
public static List<String> getLogKeys() {
return logKeys.subList(0, logKeys.size() - 1);
}
/**
* Platform: Android
* Get the logcat output in time format from a buffer for this set of static logKeys.
* #param oLogBuffer logcat buffer ring
* #return A log capture which can be used to make further captures.
*/
public static LogCapture getLogCat(String oLogBuffer) { return getLogCat(oLogBuffer, null, getLogKeys()); }
/**
* Platform: Android
* Get the logcat output in time format from a buffer for a set of log-keys; since a specified time.
* #param oLogBuffer logcat buffer ring
* #param oLogTime time at which to start capturing log data, or null for all data
* #param oLogKeys logcat tags to capture
* #return A log capture; which can be used to make further captures.
*/
public static LogCapture getLogCat(String oLogBuffer, String oLogTime, List<String> oLogKeys) {
try {
List<String>sCommand = new ArrayList<String>();
sCommand.add("logcat");
sCommand.add("-bmain");
sCommand.add("-vtime");
sCommand.add("-s");
sCommand.add("-d");
sCommand.add("-T"+oLogTime);
for (String item : oLogKeys) sCommand.add(item+":V"); // log level: ALL
sCommand.add("*:S"); // ignore logs which are not selected
Process process = new ProcessBuilder().command(sCommand).start();
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
LogCapture mLogCapture = new LogCapture(oLogBuffer, oLogKeys);
String line = "";
long lLogTime = logCatDate.parse(oLogTime).getTime();
if (lLogTime > 0) {
// Synchronize with "NO YEAR CLOCK" # unix epoch-year: 1970
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date(oLogTime));
calendar.set(Calendar.YEAR, 1970);
Date calDate = calendar.getTime();
lLogTime = calDate.getTime();
}
while ((line = bufferedReader.readLine()) != null) {
long when = logCatDate.parse(line).getTime();
if (when > lLogTime) {
mLogCapture.log.add(line);
break; // stop checking for date matching
}
}
// continue collecting
while ((line = bufferedReader.readLine()) != null) mLogCapture.log.add(line);
mLogCapture.close();
return mLogCapture;
} catch (Exception e) {
// since this is a log reader, there is nowhere to go and nothing useful to do
return null;
}
}
/**
* "Error"
* #param e
*/
public void failure(Exception e) {
Log.e(logKey, Log.getStackTraceString(e));
}
/**
* "Error"
* #param message
* #param e
*/
public void failure(String message, Exception e) {
Log.e(logKey, message, e);
}
public void warning(String message) {
Log.w(logKey, message);
}
public void warning(String message, Exception e) {
Log.w(logKey, message, e);
}
/**
* "Information"
* #param message
*/
public void message(String message) {
Log.i(logKey, message);
}
/**
* "Debug"
* #param message a Message
*/
public void examination(String message) {
Log.d(logKey, message);
}
/**
* "Debug"
* #param message a Message
* #param e An failure
*/
public void examination(String message, Exception e) {
Log.d(logKey, message, e);
}
}
In your project which performs activity logging:
Logger log = new Logger("SuperLog");
// perform logging methods
When you want to capture everything you logged through "Logger"
LogCapture capture = Logger.getLogCat("main");
When you get hungry and you want to snack on more logs
LogCapture nextCapture = capture.getNextCapture();
You can get the capture as a string with
String captureString = capture.toString();
Or you can get the log items of the capture with
String logItem = capture.log.get(itemNumber);
There is no exact static method to capture foreign log keys but there is a way none the less
LogCapture foreignCapture = Logger.getLogCat("main", null, foreignCaptureKeyList);
Using the above will also permit you to call Logger.this.nextCapture on the foreign capture.
Based on #user1185087's answer, a simple solution without ViewModel could be:
Start the job on an IO thread:
// Custom scope for collecting logs on IO threads.
val scope = CoroutineScope(Job() + Dispatchers.IO)
val job = scope.launch {
Runtime.getRuntime().exec("logcat -c") // Clear logs
Runtime.getRuntime().exec("logcat") // Start to capture new logs
.inputStream
.bufferedReader()
.useLines { lines ->
// Note that this forEach loop is an infinite loop until this job is cancelled.
lines.forEach { newLine ->
// Check whether this job is cancelled, since a coroutine must
// cooperate to be cancellable.
ensureActive()
// TODO: Write newLine into a file or buffer or anywhere appropriate
}
}
}
Cancel the job from the main thread:
MainScope().launch {
// Cancel the job and wait for its completion on main thread.
job.cancelAndJoin()
job = null // May be necessary
// TODO: Anything else you may want to clean up
}
This solution should suffice if you want to collect your app's new logs continuously on a background thread.
The "-c" flag clears the buffer.
-c Clears (flushes) the entire log and exits.
//CLEAR LOGS
Runtime.getRuntime().exec("logcat -c");
//LISTEN TO NEW LOGS
Process pq=Runtime.getRuntime().exec("logcat v main");
BufferedReader brq = new BufferedReader(new InputStreamReader(pq.getInputStream()));
String sq="";
while ((sq = brq.readLine()) != null)
{
//CHECK YOUR MSG HERE
if(sq.contains("send MMS with param"))
{
}
}
I am using this in my app and it works .
And you can use above code in Timer Task so that it wont stop your main thread
Timer t;
this.t.schedule(new TimerTask()
{
public void run()
{
try
{
ReadMessageResponse.this.startRecord();//ABOVE METHOD HERE
}
catch (IOException ex)
{
//NEED TO CHECK SOME VARIABLE TO STOP MONITORING LOGS
System.err.println("Record Stopped");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
ReadMessageResponse.this.t.cancel();
}
}
}, 0L);
}
Try to add this permission into the mainfest:
<uses-permission android:name="android.permission.READ_LOGS"/>
Goal
Collect periodic updates of the LogCat and save (append) those chunks of text to a file on the SDcard
Problem
The Log class doesn't provide updates since a specific time-stamp
Possible solution
My plan is to periodically run code that is similar to: http://www.helloandroid.com/tutorials/reading-logs-programatically
or https://stackoverflow.com/a/9039352/550471
However, with one notable difference: use the -v time parameter to ensure that each line is time-stamped.
After each time the LogCat data is collected, the app will store the time-stamp of the last Log entry. The next time the LogCat data is collected the app will search through the text to find the time-stamp and then save the chunk of data to sdcard that was added to the Log since the specified time-stamp.
Possible problem
If the LogCat data is collected at too short periods then the CPU is busy processing a lot of 'old' data.
If the Logcat data is collected at too long periods then some data could be missed.
Is there a better way ?
This is what I came up with - it works very well when it doesn't freeze up.
As you might know, Runtime.getRuntime().exec("") has a pretty good chance of causing an ANR in Android earlier than Jelly Bean. If someone has a solution to overcome the ANR, then please share.
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import android.os.Environment;
import android.util.Log;
/*
* For (compressed) buffer sizes, see: http://elinux.org/Android_Logging_System
* buffer:main = 64KB
* buffer:radio = 64KB
* buffer:system = 64KB
* buffer:event = 256KB
*
* NOTE: the 'command' must include "-d -v time" !!
* to switch buffers, use "-b <buffer>"
*/
public class LogCatReader {
// constants
private static final String CR = "\r\n";
private static final String END_OF_DATE_TIME = "): ";
private static final int DEFAULT_SEARCH_START_INDEX = 0;
// member variables
private StringBuilder mLog;
private LogThread mLogThread = null;
private String mLastLogReadToken = "";
private String mLogCommand = "";
private int mStringCapacity;
private File mFileTarget = null;
// constructor
public LogCatReader(String command, int capacity) {
mLogCommand = command;
mStringCapacity = capacity;
}
// returns complete logcat buffer
// note: takes about 1.5sec to finish
synchronized public StringBuilder getLogComplete() {
try {
// capacity should be about 25% bigger than buffer size since the
// buffer is compressed
mLog = new StringBuilder(mStringCapacity);
// command to capture log
Process process = Runtime.getRuntime().exec(mLogCommand);
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = bufferedReader.readLine()) != null) {
// append() is costly if capacity needs to be increased, be sure
// to reserve enough in the first place
mLog.append(line + CR);
}
} catch (IOException e) {
}
return mLog;
}
public String getLogUpdatesOnly() {
String strReturn = "";
StringBuilder sbLog = getLogComplete();
try {
int iStartindex = DEFAULT_SEARCH_START_INDEX;
// if there exists a token from a previous search then use that
if (mLastLogReadToken.length() > 0) {
iStartindex = sbLog.indexOf(mLastLogReadToken);
// if string not found then start at beginning
if (iStartindex == -1) {
// start search at beginning of log
iStartindex = DEFAULT_SEARCH_START_INDEX;
}
}
int iEndindex = sbLog.length();
// if token is found then move index to the next line
if (iStartindex > DEFAULT_SEARCH_START_INDEX) {
iStartindex = sbLog.indexOf(CR, iStartindex);
if (iStartindex != -1) {
iStartindex += CR.length();
} else {
// return an empty string
iStartindex = iEndindex;
}
}
// grab the data between the start and end indices
strReturn = sbLog.substring(iStartindex, iEndindex);
// grab date/time token for next search
iStartindex = sbLog.lastIndexOf(END_OF_DATE_TIME);
if (iStartindex != -1) {
iEndindex = iStartindex;
iStartindex = sbLog.lastIndexOf(CR, iEndindex);
iStartindex += CR.length();
if (iStartindex == -1) {
// read from beginning
iStartindex = 0;
}
mLastLogReadToken = sbLog.substring(iStartindex, iEndindex);
}
} catch (Exception e) {
strReturn = "";
}
return strReturn;
}
public void startPeriodicLogCatReader(int timePeriod, String logfilename) {
if (mLogThread == null) {
mLogThread = new LogThread(timePeriod, logfilename);
mLogThread.start();
}
}
public void stopPeriodicLogCatReader() {
if (mLogThread != null) {
mLogThread.interrupt();
mLogThread = null;
}
}
private class LogThread extends Thread {
private boolean mInterrupted;
private int mTimePeriod;// in seconds
private String mLogref;
private BufferedWriter mBuffWriter = null;
public boolean mPauseLogCollection = false;
// constructor: logfilename is optional - pass null to not use
public LogThread(int timePeriod, String logfilename) {
mTimePeriod = timePeriod;
if (logfilename != null) {
File fLogFolder = new File(
Environment.getExternalStorageDirectory() + "/logfiles");
if (fLogFolder.exists() == false) {
if (fLogFolder.mkdirs() == false) {
Log.e("LogCatReader",
"Could not create "
+ fLogFolder.getAbsolutePath());
}
}
mFileTarget = new File(
Environment.getExternalStorageDirectory() + "/logfiles",
logfilename);
if (mFileTarget.exists() == false) {
try {
// file doesn't yet exist - create a fresh one !
mFileTarget.createNewFile();
} catch (IOException e) {
e.printStackTrace();
mFileTarget = null;
}
}
}
}
#Override
public void interrupt() {
mInterrupted = true;
super.interrupt();
}
#Override
public void run() {
super.run();
// initialization
mInterrupted = false;
// set up storage
if (mFileTarget != null) {
try {
mBuffWriter = new BufferedWriter(new FileWriter(
mFileTarget, true), 10240);
} catch (IOException e) {
e.printStackTrace();
}
}
while ((mInterrupted == false) && (mBuffWriter != null)) {
if (mPauseLogCollection == false) {
// read log updates
mLogref = getLogUpdatesOnly();
// save log updates to file
try {
mBuffWriter.append(mLogref);
mBuffWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
if (!mInterrupted) {
try {
sleep(mTimePeriod * 1000);
} catch (InterruptedException e) {
}
}
}
if (mBuffWriter != null) {
try {
mBuffWriter.close();
mBuffWriter = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}// end of inner class
}// end of outer class
The procedure I used to find only the updates is to capture the date and time of the very last line of a logcat and use that as the search token next time around.
To use this class, the following is an example:
LogCatReader logcatPeriodicReader = new LogCatReader("logcat -b main -d -v time", 80 * 1024);//collect "main" buffer, exit after reading logcat
logcatPeriodicReader.startPeriodicLogReader(90, "log.txt");//read logcat every 90 secs
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.