Android全域性捕獲crash並儲存日誌到本地
阿新 • • 發佈:2018-12-31
大家都知道崩潰時,無法檢視崩潰資訊,對於某些不易復現的bug,我們需要把崩潰log儲存在本地,適當時還可以上傳到伺服器,本節實現的工具可收集收集裝置、崩潰時間、崩潰日誌,儲存在本地。崩潰採集工具依賴Application和Thread.UncaughtExceptionHandler實現。
Application是android的一個系統元件,當android程式啟動時系統會建立一個 application物件,用來儲存系統的一些資訊。通常我們是不需要指定一個Application的,這時系統會自動幫我們建立,如果需要建立自己 的Application,也很簡單建立一個類繼承 Application並在manifest的application標籤中進行註冊,只需要給Application標籤增加個name屬性,把自己的 Application的名字定入即可。android系統會為每個程式建立一個Application物件且僅建立一個,所以Application可以說是一個單例類.且application物件的生命週期是整個程式中最長的,它的生命週期就等於這個程式的生命週期。因為它是全域性的單例,所以在不同的Activity,Service中獲得的物件都是同一個物件。自定義application如下:
package com.xi.liuliu.topnews.utils;
import android.app.Application;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
CrashHandler.getInstance().init(this);
}
}
當某一執行緒因未捕獲的異常即將終止時,Java 虛擬機器將使用 Thread.getUncaughtExceptionHandler() 查詢獲得 UncaughtExceptionHandler 的執行緒,並uncaughtException 方法,將執行緒和異常作為引數傳遞。如果某一執行緒沒有明確設定其UncaughtExceptionHandler,則將它的ThreadGroup 物件作為其UncaughtExceptionHandler。如果ThreadGroup 物件對處理異常沒有什麼特殊要求,那麼它可以將呼叫轉發給預設的未捕獲異常處理程式。我們需要實現此介面,並註冊為程式中預設未捕獲異常處理。這樣當未捕獲異常發生時,就可以做一些個性化的異常處理操作。
package com.xi.liuliu.topnews.utils; import java.io.File; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.os.Environment; import android.os.Looper; import android.os.SystemClock; import android.util.Log; import android.widget.Toast; /** * <h3>全域性捕獲異常</h3> * <br> * 當程式發生Uncaught異常的時候,有該類來接管程式,並記錄錯誤日誌 */ @SuppressLint("SimpleDateFormat") public class CrashHandler implements UncaughtExceptionHandler { public static String TAG = "MyCrash"; // 系統預設的UncaughtException處理類 private Thread.UncaughtExceptionHandler mDefaultHandler; private static CrashHandler instance = new CrashHandler(); private Context mContext; // 用來儲存裝置資訊和異常資訊 private Map<String, String> infos = new HashMap<String, String>(); // 用於格式化日期,作為日誌檔名的一部分 private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); /** * 保證只有一個CrashHandler例項 */ private CrashHandler() { } /** * 獲取CrashHandler例項 ,單例模式 */ public static CrashHandler getInstance() { return instance; } /** * 初始化 * * @param context */ public void init(Context context) { mContext = context; // 獲取系統預設的UncaughtException處理器 mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); // 設定該CrashHandler為程式的預設處理器 Thread.setDefaultUncaughtExceptionHandler(this); autoClear(5); } /** * 當UncaughtException發生時會轉入該函式來處理 */ @Override public void uncaughtException(Thread thread, Throwable ex) { if (!handleException(ex) && mDefaultHandler != null) { // 如果使用者沒有處理則讓系統預設的異常處理器來處理 mDefaultHandler.uncaughtException(thread, ex); } else { SystemClock.sleep(3000); // 退出程式 android.os.Process.killProcess(android.os.Process.myPid()); System.exit(1); } } /** * 自定義錯誤處理,收集錯誤資訊 傳送錯誤報告等操作均在此完成. * * @param ex * @return true:如果處理了該異常資訊; 否則返回false. */ private boolean handleException(Throwable ex) { if (ex == null) return false; try { // 使用Toast來顯示異常資訊 new Thread() { @Override public void run() { Looper.prepare(); Toast.makeText(mContext, "很抱歉,程式出現異常,即將重啟.", Toast.LENGTH_LONG).show(); Looper.loop(); } }.start(); // 收集裝置引數資訊 collectDeviceInfo(mContext); // 儲存日誌檔案 saveCrashInfoFile(ex); SystemClock.sleep(3000); } catch (Exception e) { e.printStackTrace(); } return true; } /** * 收集裝置引數資訊 * * @param ctx */ public void collectDeviceInfo(Context ctx) { try { PackageManager pm = ctx.getPackageManager(); PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES); if (pi != null) { String versionName = pi.versionName + ""; String versionCode = pi.versionCode + ""; infos.put("versionName", versionName); infos.put("versionCode", versionCode); } } catch (NameNotFoundException e) { Log.e(TAG, "an error occured when collect package info", e); } Field[] fields = Build.class.getDeclaredFields(); for (Field field : fields) { try { field.setAccessible(true); infos.put(field.getName(), field.get(null).toString()); } catch (Exception e) { Log.e(TAG, "an error occured when collect crash info", e); } } } /** * 儲存錯誤資訊到檔案中 * * @param ex * @return 返回檔名稱, 便於將檔案傳送到伺服器 * @throws Exception */ private String saveCrashInfoFile(Throwable ex) throws Exception { StringBuffer sb = new StringBuffer(); try { SimpleDateFormat sDateFormat = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); String date = sDateFormat.format(new java.util.Date()); sb.append("\r\n" + date + "\n"); for (Map.Entry<String, String> entry : infos.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); sb.append(key + "=" + value + "\n"); } Writer writer = new StringWriter(); PrintWriter printWriter = new PrintWriter(writer); ex.printStackTrace(printWriter); Throwable cause = ex.getCause(); while (cause != null) { cause.printStackTrace(printWriter); cause = cause.getCause(); } printWriter.flush(); printWriter.close(); String result = writer.toString(); sb.append(result); String fileName = writeFile(sb.toString()); return fileName; } catch (Exception e) { Log.e(TAG, "an error occured while writing file...", e); sb.append("an error occured while writing file...\r\n"); writeFile(sb.toString()); } return null; } private String writeFile(String sb) throws Exception { String time = formatter.format(new Date()); String fileName = "crash-" + time + ".log"; if (FileUtils.hasSdcard()) { String path = getGlobalpath(); File dir = new File(path); if (!dir.exists()) dir.mkdirs(); FileOutputStream fos = new FileOutputStream(path + fileName, true); fos.write(sb.getBytes()); fos.flush(); fos.close(); } return fileName; } public static String getGlobalpath() { return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "crash" + File.separator; } public static void setTag(String tag) { TAG = tag; } /** * 檔案刪除 * * @param */ public void autoClear(final int autoClearDay) { FileUtils.delete(getGlobalpath(), new FilenameFilter() { @Override public boolean accept(File file, String filename) { String s = FileUtils.getFileNameWithoutExtension(filename); int day = autoClearDay < 0 ? autoClearDay : -1 * autoClearDay; String date = "crash-" + DateUtil.getOtherDay(day); return date.compareTo(s) >= 0; } }); } }
輔助工具類:
日期類:package com.xi.liuliu.topnews.utils; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import android.os.Environment; import android.text.TextUtils; public final class FileUtils { private FileUtils() { throw new Error("error"); } /** * 分隔符. */ public final static String FILE_EXTENSION_SEPARATOR = "."; /** * "/" */ public final static String SEP = File.separator; /** * SD卡根目錄 */ public static final String SDPATH = Environment .getExternalStorageDirectory() + File.separator; /** * 判斷SD卡是否可用 * * @return SD卡可用返回true */ public static boolean hasSdcard() { String status = Environment.getExternalStorageState(); return Environment.MEDIA_MOUNTED.equals(status); } /** * 讀取檔案的內容 * <br> * 預設utf-8編碼 * * @param filePath 檔案路徑 * @return 字串 * @throws IOException */ public static String readFile(String filePath) throws IOException { return readFile(filePath, "utf-8"); } /** * 讀取檔案的內容 * * @param filePath 檔案目錄 * @param charsetName 字元編碼 * @return String字串 */ public static String readFile(String filePath, String charsetName) throws IOException { if (TextUtils.isEmpty(filePath)) return null; if (TextUtils.isEmpty(charsetName)) charsetName = "utf-8"; File file = new File(filePath); StringBuilder fileContent = new StringBuilder(""); if (file == null || !file.isFile()) return null; BufferedReader reader = null; try { InputStreamReader is = new InputStreamReader(new FileInputStream( file), charsetName); reader = new BufferedReader(is); String line = null; while ((line = reader.readLine()) != null) { if (!fileContent.toString().equals("")) { fileContent.append("\r\n"); } fileContent.append(line); } return fileContent.toString(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 讀取文字檔案到List字串集合中(預設utf-8編碼) * * @param filePath 檔案目錄 * @return 檔案不存在返回null,否則返回字串集合 * @throws IOException */ public static List<String> readFileToList(String filePath) throws IOException { return readFileToList(filePath, "utf-8"); } /** * 讀取文字檔案到List字串集合中 * * @param filePath 檔案目錄 * @param charsetName 字元編碼 * @return 檔案不存在返回null,否則返回字串集合 */ public static List<String> readFileToList(String filePath, String charsetName) throws IOException { if (TextUtils.isEmpty(filePath)) return null; if (TextUtils.isEmpty(charsetName)) charsetName = "utf-8"; File file = new File(filePath); List<String> fileContent = new ArrayList<String>(); if (file == null || !file.isFile()) { return null; } BufferedReader reader = null; try { InputStreamReader is = new InputStreamReader(new FileInputStream( file), charsetName); reader = new BufferedReader(is); String line = null; while ((line = reader.readLine()) != null) { fileContent.add(line); } return fileContent; } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 向檔案中寫入資料 * * @param filePath 檔案目錄 * @param content 要寫入的內容 * @param append 如果為 true,則將資料寫入檔案末尾處,而不是寫入檔案開始處 * @return 寫入成功返回true, 寫入失敗返回false * @throws IOException */ public static boolean writeFile(String filePath, String content, boolean append) throws IOException { if (TextUtils.isEmpty(filePath)) return false; if (TextUtils.isEmpty(content)) return false; FileWriter fileWriter = null; try { createFile(filePath); fileWriter = new FileWriter(filePath, append); fileWriter.write(content); fileWriter.flush(); return true; } finally { if (fileWriter != null) { try { fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 向檔案中寫入資料<br> * 預設在檔案開始處重新寫入資料 * * @param filePath 檔案目錄 * @param stream 位元組輸入流 * @return 寫入成功返回true,否則返回false * @throws IOException */ public static boolean writeFile(String filePath, InputStream stream) throws IOException { return writeFile(filePath, stream, false); } /** * 向檔案中寫入資料 * * @param filePath 檔案目錄 * @param stream 位元組輸入流 * @param append 如果為 true,則將資料寫入檔案末尾處; * 為false時,清空原來的資料,從頭開始寫 * @return 寫入成功返回true,否則返回false * @throws IOException */ public static boolean writeFile(String filePath, InputStream stream, boolean append) throws IOException { if (TextUtils.isEmpty(filePath)) throw new NullPointerException("filePath is Empty"); if (stream == null) throw new NullPointerException("InputStream is null"); return writeFile(new File(filePath), stream, append); } /** * 向檔案中寫入資料 * 預設在檔案開始處重新寫入資料 * * @param file 指定檔案 * @param stream 位元組輸入流 * @return 寫入成功返回true,否則返回false * @throws IOException */ public static boolean writeFile(File file, InputStream stream) throws IOException { return writeFile(file, stream, false); } /** * 向檔案中寫入資料 * * @param file 指定檔案 * @param stream 位元組輸入流 * @param append 為true時,在檔案開始處重新寫入資料; * 為false時,清空原來的資料,從頭開始寫 * @return 寫入成功返回true,否則返回false * @throws IOException */ public static boolean writeFile(File file, InputStream stream, boolean append) throws IOException { if (file == null) throw new NullPointerException("file = null"); OutputStream out = null; try { createFile(file.getAbsolutePath()); out = new FileOutputStream(file, append); byte data[] = new byte[1024]; int length = -1; while ((length = stream.read(data)) != -1) { out.write(data, 0, length); } out.flush(); return true; } finally { if (out != null) { try { out.close(); stream.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 複製檔案 * * @param sourceFilePath 原始檔目錄(要複製的檔案目錄) * @param destFilePath 目標檔案目錄(複製後的檔案目錄) * @return 複製檔案成功返回true,否則返回false * @throws IOException */ public static boolean copyFile(String sourceFilePath, String destFilePath) throws IOException { InputStream inputStream = null; inputStream = new FileInputStream(sourceFilePath); return writeFile(destFilePath, inputStream); } /** * 獲取某個目錄下的檔名 * * @param dirPath 目錄 * @param fileFilter 過濾器 * @return 某個目錄下的所有檔名 */ public static List<String> getFileNameList(String dirPath, FilenameFilter fileFilter) { if (fileFilter == null) return getFileNameList(dirPath); if (TextUtils.isEmpty(dirPath)) return Collections.emptyList(); File dir = new File(dirPath); File[] files = dir.listFiles(fileFilter); if (files == null) return Collections.emptyList(); List<String> conList = new ArrayList<String>(); for (File file : files) { if (file.isFile()) conList.add(file.getName()); } return conList; } /** * 獲取某個目錄下的檔名 * * @param dirPath 目錄 * @return 某個目錄下的所有檔名 */ public static List<String> getFileNameList(String dirPath) { if (TextUtils.isEmpty(dirPath)) return Collections.emptyList(); File dir = new File(dirPath); File[] files = dir.listFiles(); if (files == null) return Collections.emptyList(); List<String> conList = new ArrayList<String>(); for (File file : files) { if (file.isFile()) conList.add(file.getName()); } return conList; } /** * 獲取某個目錄下的指定副檔名的檔名稱 * * @param dirPath 目錄 * @return 某個目錄下的所有檔名 */ public static List<String> getFileNameList(String dirPath, final String extension) { if (TextUtils.isEmpty(dirPath)) return Collections.emptyList(); File dir = new File(dirPath); File[] files = dir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String filename) { if (filename.indexOf("." + extension) > 0) return true; return false; } }); if (files == null) return Collections.emptyList(); List<String> conList = new ArrayList<String>(); for (File file : files) { if (file.isFile()) conList.add(file.getName()); } return conList; } /** * 獲得檔案的副檔名 * * @param filePath 檔案路徑 * @return 如果沒有副檔名,返回"" */ public static String getFileExtension(String filePath) { if (TextUtils.isEmpty(filePath)) { return filePath; } int extenPosi = filePath.lastIndexOf(FILE_EXTENSION_SEPARATOR); int filePosi = filePath.lastIndexOf(File.separator); if (extenPosi == -1) { return ""; } return (filePosi >= extenPosi) ? "" : filePath.substring(extenPosi + 1); } /** * 建立檔案 * * @param path 檔案的絕對路徑 * @return */ public static boolean createFile(String path) { if (TextUtils.isEmpty(path)) return false; return createFile(new File(path)); } /** * 建立檔案 * * @param file * @return 建立成功返回true */ public static boolean createFile(File file) { if (file == null || !makeDirs(getFolderName(file.getAbsolutePath()))) return false; if (!file.exists()) try { return file.createNewFile(); } catch (IOException e) { e.printStackTrace(); return false; } return false; } /** * 建立目錄(可以是多個) * * @param filePath 目錄路徑 * @return 如果路徑為空時,返回false;如果目錄建立成功,則返回true,否則返回false */ public static boolean makeDirs(String filePath) { if (TextUtils.isEmpty(filePath)) { return false; } File folder = new File(filePath); return (folder.exists() && folder.isDirectory()) ? true : folder .mkdirs(); } /** * 建立目錄(可以是多個) * * @param dir 目錄 * @return 如果目錄建立成功,則返回true,否則返回false */ public static boolean makeDirs(File dir) { if (dir == null) return false; return (dir.exists() && dir.isDirectory()) ? true : dir.mkdirs(); } /** * 判斷檔案是否存在 * * @param filePath 檔案路徑 * @return 如果路徑為空或者為空白字串,就返回false;如果檔案存在,且是檔案, * 就返回true;如果不是檔案或者不存在,則返回false */ public static boolean isFileExist(String filePath) { if (TextUtils.isEmpty(filePath)) { return false; } File file = new File(filePath); return (file.exists() && file.isFile()); } /** * 獲得不帶副檔名的檔名稱 * * @param filePath 檔案路徑 * @return */ public static String getFileNameWithoutExtension(String filePath) { if (TextUtils.isEmpty(filePath)) { return filePath; } int extenPosi = filePath.lastIndexOf(FILE_EXTENSION_SEPARATOR); int filePosi = filePath.lastIndexOf(File.separator); if (filePosi == -1) { return (extenPosi == -1 ? filePath : filePath.substring(0, extenPosi)); } if (extenPosi == -1) { return filePath.substring(filePosi + 1); } return (filePosi < extenPosi ? filePath.substring(filePosi + 1, extenPosi) : filePath.substring(filePosi + 1)); } /** * 獲得檔名 * * @param filePath 檔案路徑 * @return 如果路徑為空或空串,返回路徑名;不為空時,返回檔名 */ public static String getFileName(String filePath) { if (TextUtils.isEmpty(filePath)) { return filePath; } int filePosi = filePath.lastIndexOf(File.separator); return (filePosi == -1) ? filePath : filePath.substring(filePosi + 1); } /** * 獲得所在目錄名稱 * * @param filePath 檔案的絕對路徑 * @return 如果路徑為空或空串,返回路徑名;不為空時,如果為根目錄,返回""; * 如果不是根目錄,返回所在目錄名稱,格式如:C:/Windows/Boot */ public static String getFolderName(String filePath) { if (TextUtils.isEmpty(filePath)) { return filePath; } int filePosi = filePath.lastIndexOf(File.separator); return (filePosi == -1) ? "" : filePath.substring(0, filePosi); } /** * 判斷目錄是否存在 * * @param * @return 如果路徑為空或空白字串,返回false;如果目錄存在且,確實是目錄資料夾, * 返回true;如果不是資料夾或者不存在,則返回false */ public static boolean isFolderExist(String directoryPath) { if (TextUtils.isEmpty(directoryPath)) { return false; } File dire = new File(directoryPath); return (dire.exists() && dire.isDirectory()); } /** * 刪除指定檔案或指定目錄內的所有檔案 * * @param path 檔案或目錄的絕對路徑 * @return 路徑為空或空白字串,返回true;檔案不存在,返回true;檔案刪除返回true; * 檔案刪除異常返回false */ public static boolean deleteFile(String path) { if (TextUtils.isEmpty(path)) { return true; } return deleteFile(new File(path)); } /** * 刪除指定檔案或指定目錄內的所有檔案 * * @param file * @return 路徑為空或空白字串,返回true;檔案不存在,返回true;檔案刪除返回true; * 檔案刪除異常返回false */ public static boolean deleteFile(File file) { if (file == null) throw new NullPointerException("file is null"); if (!file.exists()) { return true; } if (file.isFile()) { return file.delete(); } if (!file.isDirectory()) { return false; } File[] files = file.listFiles(); if (files == null) return true; for (File f : files) { if (f.isFile()) { f.delete(); } else if (f.isDirectory()) { deleteFile(f.getAbsolutePath()); } } return file.delete(); } /** * 刪除指定目錄中特定的檔案 * * @param dir * @param filter */ public static void delete(String dir, FilenameFilter filter) { if (TextUtils.isEmpty(dir)) return; File file = new File(dir); if (!file.exists()) return; if (file.isFile()) file.delete(); if (!file.isDirectory()) return; File[] lists = null; if (filter != null) lists = file.listFiles(filter); else lists = file.listFiles(); if (lists == null) return; for (File f : lists) { if (f.isFile()) { f.delete(); } } } /** * 獲得檔案或資料夾的大小 * * @param path 檔案或目錄的絕對路徑 * @return 返回當前目錄的大小 ,注:當檔案不存在,為空,或者為空白字串,返回 -1 */ public static long getFileSize(String path) { if (TextUtils.isEmpty(path)) { return -1; } File file = new File(path); return (file.exists() && file.isFile() ? file.length() : -1); } }
package com.xi.liuliu.topnews.utils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import android.annotation.SuppressLint;
/**
* <h3>日期工具類</h3>
* <p>主要實現了日期的常用操作
*/
@SuppressLint("SimpleDateFormat")
public final class DateUtil {
/**
* yyyy-MM-dd HH:mm:ss字串
*/
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* yyyy-MM-dd字串
*/
public static final String DEFAULT_FORMAT_DATE = "yyyy-MM-dd";
/**
* HH:mm:ss字串
*/
public static final String DEFAULT_FORMAT_TIME = "HH:mm:ss";
/**
* yyyy-MM-dd HH:mm:ss格式
*/
public static final ThreadLocal<SimpleDateFormat> defaultDateTimeFormat = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
}
};
/**
* yyyy-MM-dd格式
*/
public static final ThreadLocal<SimpleDateFormat> defaultDateFormat = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(DEFAULT_FORMAT_DATE);
}
};
/**
* HH:mm:ss格式
*/
public static final ThreadLocal<SimpleDateFormat> defaultTimeFormat = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(DEFAULT_FORMAT_TIME);
}
};
private DateUtil() {
throw new RuntimeException(" ̄ 3 ̄");
}
/**
* 將long時間轉成yyyy-MM-dd HH:mm:ss字串<br>
*
* @param timeInMillis 時間long值
* @return yyyy-MM-dd HH:mm:ss
*/
public static String getDateTimeFromMillis(long timeInMillis) {
return getDateTimeFormat(new Date(timeInMillis));
}
/**
* 將long時間轉成yyyy-MM-dd字串<br>
*
* @param timeInMillis
* @return yyyy-MM-dd
*/
public static String getDateFromMillis(long timeInMillis) {
return getDateFormat(new Date(timeInMillis));
}
/**
* 將date轉成yyyy-MM-dd HH:mm:ss字串
* <br>
*
* @param date Date物件
* @return yyyy-MM-dd HH:mm:ss
*/
public static String getDateTimeFormat(Date date) {
return dateSimpleFormat(date, defaultDateTimeFormat.get());
}
/**
* 將年月日的int轉成yyyy-MM-dd的字串
*
* @param year 年
* @param month 月 1-12
* @param day 日
* 注:月表示Calendar的月,比實際小1
* 對輸入項未做判斷
*/
public static String getDateFormat(int year, int month, int day) {
return getDateFormat(getDate(year, month, day));
}
/**
* 將date轉成yyyy-MM-dd字串<br>
*
* @param date Date物件
* @return yyyy-MM-dd
*/
public static String getDateFormat(Date date) {
return dateSimpleFormat(date, defaultDateFormat.get());
}
/**
* 獲得HH:mm:ss的時間
*
* @param date
* @return
*/
public static String getTimeFormat(Date date) {
return dateSimpleFormat(date, defaultTimeFormat.get());
}
/**
* 格式化日期顯示格式
*
* @param sdate 原始日期格式 "yyyy-MM-dd"
* @param format 格式化後日期格式
* @return 格式化後的日期顯示
*/
public static String dateFormat(String sdate, String format) {
SimpleDateFormat formatter = new SimpleDateFormat(format);
java.sql.Date date = java.sql.Date.valueOf(sdate);
return dateSimpleFormat(date, formatter);
}
/**
* 格式化日期顯示格式
*
* @param date Date物件
* @param format 格式化後日期格式
* @return 格式化後的日期顯示
*/
public static String dateFormat(Date date, String format) {
SimpleDateFormat formatter = new SimpleDateFormat(format);
return dateSimpleFormat(date, formatter);
}
/**
* 將date轉成字串
*
* @param date Date
* @param format SimpleDateFormat
* <br>
* 注: SimpleDateFormat為空時,採用預設的yyyy-MM-dd HH:mm:ss格式
* @return yyyy-MM-dd HH:mm:ss
*/
public static String dateSimpleFormat(Date date, SimpleDateFormat format) {
if (format == null)
format = defaultDateTimeFormat.get();
return (date == null ? "" : format.format(date));
}
/**
* 將"yyyy-MM-dd HH:mm:ss" 格式的字串轉成Date
*
* @param strDate 時間字串
* @return Date
*/
public static Date getDateByDateTimeFormat(String strDate) {
return getDateByFormat(strDate, defaultDateTimeFormat.get());
}
/**
* 將"yyyy-MM-dd" 格式的字串轉成Date
*
* @param strDate
* @return Date
*/
public static Date getDateByDateFormat(String strDate) {
return getDateByFormat(strDate, defaultDateFormat.get());
}
/**
* 將指定格式的時間字串轉成Date物件
*
* @param strDate 時間字串
* @param format 格式化字串
* @return Date
*/
public static Date getDateByFormat(String strDate, String format) {
return getDateByFormat(strDate, new SimpleDateFormat(format));
}
/**
* 將String字串按照一定格式轉成Date<br>
* 注: SimpleDateFormat為空時,採用預設的yyyy-MM-dd HH:mm:ss格式
*
* @param strDate 時間字串
* @param format SimpleDateFormat物件
* @throws ParseException 日期格式轉換出錯
*/
private static Date getDateByFormat(String strDate, SimpleDateFormat format) {
if (format == null)
format = defaultDateTimeFormat.get();
try {
return format.parse(strDate);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
/**
* 將年月日的int轉成date
*
* @param year 年
* @param month 月 1-12
* @param day 日
* 注:月表示Calendar的月,比實際小1
*/
public static Date getDate(int year, int month, int day) {
Calendar mCalendar = Calendar.getInstance();
mCalendar.set(year, month - 1, day);
return mCalendar.getTime();
}
/**
* 求兩個日期相差天數
*
* @param strat 起始日期,格式yyyy-MM-dd
* @param end 終止日期,格式yyyy-MM-dd
* @return 兩個日期相差天數
*/
public static long getIntervalDays(String strat, String end) {
return ((java.sql.Date.valueOf(end)).getTime() - (java.sql.Date
.valueOf(strat)).getTime()) / (3600 * 24 * 1000);
}
/**
* 獲得當前年份
*
* @return year(int)
*/
public static int getCurrentYear() {
Calendar mCalendar = Calendar.getInstance();
return mCalendar.get(Calendar.YEAR);
}
/**
* 獲得當前月份
*
* @return month(int) 1-12
*/
public static int getCurrentMonth() {
Calendar mCalendar = Calendar.getInstance();
return mCalendar.get(Calendar.MONTH) + 1;
}
/**
* 獲得當月幾號
*
* @return day(int)
*/
public static int getDayOfMonth() {
Calendar mCalendar = Calendar.getInstance();
return mCalendar.get(Calendar.DAY_OF_MONTH);
}
/**
* 獲得今天的日期(格式:yyyy-MM-dd)
*
* @return yyyy-MM-dd
*/
public static String getToday() {
Calendar mCalendar = Calendar.getInstance();
return getDateFormat(mCalendar.getTime());
}
/**
* 獲得昨天的日期(格式:yyyy-MM-dd)
*
* @return yyyy-MM-dd
*/
public static String getYesterday() {
Calendar mCalendar = Calendar.getInstance();
mCalendar.add(Calendar.DATE, -1);
return getDateFormat(mCalendar.getTime());
}
/**
* 獲得前天的日期(格式:yyyy-MM-dd)
*
* @return yyyy-MM-dd
*/
public static String getBeforeYesterday() {
Calendar mCalendar = Calendar.getInstance();
mCalendar.add(Calendar.DATE, -2);
return getDateFormat(mCalendar.getTime());
}
/**
* 獲得幾天之前或者幾天之後的日期
*
* @param diff 差值:正的往後推,負的往前推
* @return
*/
public static String getOtherDay(int diff) {
Calendar mCalendar = Calendar.getInstance();
mCalendar.add(Calendar.DATE, diff);
return getDateFormat(mCalendar.getTime());
}
/**
* 取得給定日期加上一定天數後的日期物件.
*
* @param
* @param amount 需要新增的天數,如果是向前的天數,使用負數就可以.
* @return Date 加上一定天數以後的Date物件.
*/
public static String getCalcDateFormat(String sDate, int amount) {
Date date = getCalcDate(getDateByDateFormat(sDate), amount);
return getDateFormat(date);
}
/**
* 取得給定日期加上一定天數後的日期物件.
*
* @param date 給定的日期物件
* @param amount 需要新增的天數,如果是向前的天數,使用負數就可以.
* @return Date 加上一定天數以後的Date物件.
*/
public static Date getCalcDate(Date date, int amount) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DATE, amount);
return cal.getTime();
}
/**
* 獲得一個計算十分秒之後的日期物件
*
* @param date
* @param hOffset 時偏移量,可為負
* @param mOffset 分偏移量,可為負
* @param sOffset 秒偏移量,可為負
* @return
*/
public static Date getCalcTime(Date date, int hOffset, int mOffset, int sOffset) {
Calendar cal = Calendar.getInstance();
if (date != null)
cal.setTime(date);
cal.add(Calendar.HOUR_OF_DAY, hOffset);
cal.add(Calendar.MINUTE, mOffset);
cal.add(Calendar.SECOND, sOffset);
return cal.getTime();
}
/**
* 根據指定的年月日小時分秒,返回一個java.Util.Date物件。
*
* @param year 年
* @param month 月 0-11
* @param date 日
* @param hourOfDay 小時 0-23
* @param minute 分 0-59
* @param second 秒 0-59
* @return 一個Date物件
*/
public static Date getDate(int year, int month, int date, int hourOfDay,
int minute, int second) {
Calendar cal = Calendar.getInstance();
cal.set(year, month, date, hourOfDay, minute, second);
return cal.getTime();
}
/**
* 獲得年月日資料
*
* @param sDate yyyy-MM-dd格式
* @return arr[0]:年, arr[1]:月 0-11 , arr[2]日
*/
public static int[] getYearMonthAndDayFrom(String sDate) {
return getYearMonthAndDayFromDate(getDateByDateFormat(sDate));
}
/**
* 獲得年月日資料
*
* @return arr[0]:年, arr[1]:月 0-11 , arr[2]日
*/
public static int[] getYearMonthAndDayFromDate(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
int[] arr = new int[3];
arr[0] = calendar.get(Calendar.YEAR);
arr[1] = calendar.get(Calendar.MONTH);
arr[2] = calendar.get(Calendar.DAY_OF_MONTH);
return arr;
}
}
manifest中註冊Application,並申請寫sdcard許可權:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xi.liuliu.topnews">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".utils.MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
陣列越界時,我的測試機儲存的crash log: