android全局異常
1. Android許可權機制
我們知道 Android 應用程序是沙箱隔離的,每個應用都有一個只有自己具有讀寫許可權的專用數據目錄。但是如果應用要訪問別人的組件或者一些設備上全局可訪問的資源,這時候許可權機制就能系統化地規范並強制各類應用程序的行為准則。
Android 安全性概覽
在 Android 中,一個許可權,本質上是一個字元串,一個可以表示執行特定操作的能力的字元串。比如說:訪問 SD 卡的能力,訪問通訊錄的能力,啟動或訪問一個第三方應用中的組件的能力。 許可權被授予了之後,首先會在內存和本地中有記錄,這在調用系統binder服務和其他應用組件時做鑒權依據,比如調用系統binder服務時會通過Binder.getCallingUid()拿到調用者的Uid,而Uid一般都是與應用包名一一對應的,再拿這個Uid到PMS里去查這個應用對應的許可權。 其次會按被授予的許可權將應用分到某個組。 可以參考 https://www.jianshu.com/p/a17c8bed79d9
自定義許可權的應用場景在於限制其它應用對本應用四大組件的訪問。具體用法可以參考 https://www.cnblogs.com/aimqqroad-13/p/8927179.html
pm list permissions -f 命令可以詳細查看 Android 所有預定義的許可權。
更詳細的許可權信息參考 https://developer.android.com/reference/android/Manifest.permission?hl=zh-cn#WRITE_EXTERNAL_STORAGE
可以看到一個許可權的信息包括:定義的包名、標簽、描述、 許可權組 和 保護級別 。
許可權根據設備的功能或特性分為多個組。如果應用已在相同許可權組中被授予另一危險許可權,系統將立即授予該許可權,如READ_CONTACTS和WRITE_CONTACTS。
SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 由於其特殊性,其申請方式與其它許可權都不同。
其授予流程如下:
(關於 AppOpsManager 是什麼可以參考: https://segmentfault.com/a/1190000009214983 )
這里簡要分析下ActivityCompat#requestPermissions的流程:
更詳細的許可權授予流程源碼分析可以參考: https://segmentfault.com/a/1190000009214983
普通許可權: 清單文件中聲明即可。
危險許可權: 方式一: pm grant application_package android.permission.CHANGE_CONFIGURATION 方式二:appops set application_package permission_num 0/1
appops可以授予的許可權參考 android.app.AppOpsManager 中的聲明
系統簽名許可權: 方式一:將app遷移到system/priv-app目錄中。 方式二:看不懂,參考 https://blog.csdn.net/abcd_3344_abcd/article/details/50698759
android 4.4 訪問sd卡需要申請許可權。 您的應用在 Android 4.4 上運行時無法讀取外部存儲空間上的共享文件,除非您的應用具有 READ_EXTERNAL_STORAGE 許可權。也就是說,沒有此許可權,您無法再訪問 () 返回的目錄中的文件。但是,如果您僅需要訪問 getExternalFilesDir() 提供的您的應用特有目錄,那麼,您不需要 READ_EXTERNAL_STORAGE `許可權。
android 6.0 運行時許可權。 此版本引入了一種新的許可權模式,如今,用戶可直接在運行時管理應用許可權。這種模式讓用戶能夠更好地了解和控制許可權,同時為應用開發者精簡了安裝和自動更新過程。用戶可為所安裝的各個應用分別授予或撤銷許可權。 對於以 Android 6.0(API 級別 23)或更高版本為目標平台的應用,請務必在運行時檢查和請求許可權。要確定您的應用是否已被授予許可權,請調用新增的 checkSelfPermission() 方法。要請求許可權,請調用新增的 requestPermissions() 方法。即使您的應用並不以 Android 6.0(API 級別 23)為目標平台,您也應該在新許可權模式下測試您的應用。 如需了解有關在您的應用中支持新許可權模式的詳情,請參閱 使用系統許可權 。如需了解有關如何評估新模式對應用的影響的提示,請參閱 許可權最佳做法 。
android 7.+ 應用間共享文件要使用FileProvider。 對於面向 Android 7.0 的應用,Android 框架執行的 StrictMode API 政策禁止在您的應用外部公開 file://URI。如果一項包含文件 URI 的 intent 離開您的應用,則應用出現故障,並出現 FileUriExposedException 異常。 要在應用間共享文件,您應發送一項 content:// URI,並授予 URI 臨時訪問許可權。進行此授權的最簡單方式是使用 FileProvider `類。如需了解有關許可權和共享文件的詳細信息,請參閱 共享文件 。
android 8.+
同一許可權組的許可權在被授予了之後也需要顯式的再申請一次。
在 Android 8.0 之前,如果應用在運行時請求許可權並且被授予該許可權,系統會錯誤地將屬於同一許可權組並且在清單中注冊的其他許可權也一起授予應用。 對於針對 Android 8.0 的應用,此行為已被糾正。系統只會授予應用明確請求的許可權。然而,一旦用戶為應用授予某個許可權,則所有後續對該許可權組中許可權的請求都將被自動批准。 例如,假設某個應用在其清單中列出 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 。應用請求 READ_EXTERNAL_STORAGE ,並且用戶授予了該許可權。如果該應用針對的是 API 級別 24 或更低級別,系統還會同時授予 WRITE_EXTERNAL_STORAGE ,因為該許可權也屬於同一 STORAGE 許可權組並且也在清單中注冊過。如果該應用針對的是 Android 8.0,則系統此時僅會授予 READ_EXTERNAL_STORAGE ;不過,如果該應用後來又請求 WRITE_EXTERNAL_STORAGE ,則系統會立即授予該許可權,而不會提示用戶。
android 9
隱私許可權變更。
為了增強用戶隱私,Android 9 引入了若干行為變更,如限制後台應用訪問設備感測器、限制通過 Wi-Fi 掃描檢索到的信息,以及與通話、手機狀態和 Wi-Fi 掃描相關的新許可權規則和許可權組。
android 10
隱私權變更。
外部存儲訪問許可權范圍限定為應用文件和媒體,在後台運行時訪問設備位置信息需要許可權,針對從後台啟動 Activity 的限制等。
android 11
隱私許可權變更。
更詳細的版本變更請參考 https://developer.android.com/preview/privacy?hl=zh-cn
2. Android 捕獲全局異常CrashHandler,防止異常閃退,記錄異常日誌
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Looper;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* UncaughtException handler class
*
*/
public class CrashHandler implements UncaughtExceptionHandler {
public static final String TAG = "CrashHandler";
public static final String PROGRAM_BROKEN_ACTION = "com.teligen.wccp.PROGRAM_BROKEN";
private UncaughtExceptionHandler mDefaultHandler;
private static CrashHandler instance = new CrashHandler();
private Context mContext;
private Class<?> mainActivityClass;
private Map<String, String> infos = new HashMap<String, String>();
private CrashHandler() {
}
public static CrashHandler getInstance() {
return instance;
}
public void init(Context context, Class<?> activityClass) {
mContext = context;
this.setMainActivityClass(activityClass);
mDefaultHandler = Thread.();
Thread.(this);
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
mDefaultHandler.uncaughtException(thread, ex);
} else {
System.out.println("uncaughtException--->" + ex.getMessage());
// Log.e(TAG, ex.getMessage());
logError(ex);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// Log.e("debug", "error:", e);
}
exitApp();
}
}
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext.getApplicationContext(),
"unknown exception and exiting...Please checking logs in sd card!", Toast.LENGTH_LONG).show();
Looper.loop();
}
}).start();
collectDeviceInfo(mContext.getApplicationContext());
logError(ex);
return true;
}
private void exitApp() {
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
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 == null ? "null"
: pi.versionName;
String versionCode = pi.versionCode + "";
infos.put("versionName", versionName);
infos.put("versionCode", versionCode);
}
} catch (NameNotFoundException e) {
e.printStackTrace();
}
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
infos.put(field.getName(), field.get(null).toString());
} catch (Exception e) {
}
}
}
private void logError(Throwable ex) {
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : infos.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key + "=" + value + "\n");
}
int num = ex.getStackTrace().length;
for (int i=0;i<num;i++){
sb.append(ex.getStackTrace()[i].toString());
sb.append("\n");
}
File file = new File(filePath+"/log.txt");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
fos.write((sb.toString()+"exception:"+ex.getLocalizedMessage()).getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public Class<?> getMainActivityClass() {
return mainActivityClass;
}
public void setMainActivityClass(Class<?> mainActivityClass) {
this.mainActivityClass = mainActivityClass;
}
}
filePath是記錄日誌的路徑
在Applicaton中初始化
@Override
public void onCreate() {
super.onCreate();
CrashHandler mCrashHandler = CrashHandler.getInstance();
mCrashHandler.init(getApplicationContext(), getClass());
initFile();
}
3. android如何讓系統庫成為全局
一、通過Settings.System進行讀寫
//其中"getXXX"代表對應的類似方法,如getInt()、getBoolean、putString()等。
//通過變數名稱獲取值,如果變數不存在,資料庫中沒有設置過初始值或者該值類型不對,將拋出SettingNotFoundException異常
Settings.System.getXXX(ContentResolver cr, String name);
//通過變數名稱獲取值,如果發生上面方法中導致異常的情況,將返給定的默認值
Settings.System.getXXX(ContentResolver cr, String name, XXX def);
//將指定名稱的值寫入資料庫
Settings.System.putXXX(ContentResolver cr,String name, XXX Value);
非系統許可權,需要在App項目的AndroidMainfes.xml文件中添加如下許可權:
<uses-permission android:name="android.permission.READ_SETTINGS" /><uses-permission android:name="android.permission.WRITE_SETTINGS" />
二、在Settings.System添加一個自定義的全局變數
Settings.java文件位於frameworks\base\core\java\android\provider下,打開該文件,搜索關鍵詞 SETTINGS_TO_BACKUP ,共有兩處,一處是在Settings裡面,另一處在內部類Settings.System裡面,在SETTINGS_TO_BACKUP數組上面添加自定義變數,同時在該數組裡面添加自定義變數名稱,Settting和內部類System都需要添加(共四個地方),比如自定義系統變數SYSTEM_ZWH:
public static final String SYSTEM_ZWH = "system_zwh"; Public static final String[] SETTINGS_TO_BACKUP = { ... SYSTEM_ZWH, ... }
在代碼中我們就可以通過對於的get和put方法對該值進行讀取和寫入操作了。
4. Android實現錄屏MediaProjection以及相關異常解決
需要實現一個手機的錄屏功能,於是從網上找了些相關資料和源碼,發現跑不起來,於是開始bug,發現坑還是很多的,這里記錄一下實現過程和一些些遇到的異常以及一個我調整完可以跑的Demo。
首先在AndroidManifest中靜態配置許可權:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
然後在Activity中動態申請
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_REQUEST_CODE);
}
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[] {Manifest.permission.RECORD_AUDIO}, AUDIO_REQUEST_CODE);
}
因為項目中需要用到一個自定義的Application,所以要需要配置一個全局的Application,同樣在AndroidManiest中在application添加自定義的類名,如果在裡面啟動服務了也要一並配置。
<application
android:name=".RecordApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</application>
然後可以使用封裝好的實現其錄屏功能的service,這個封裝類是網上找的,看很多人在用,我解決了一些異常,並根據自己需求修改了一下。
其中主要異常有:
1.mediaRecorder報空指針,解決方案,在聲明的時候聲明為靜態
private static MediaRecorder mediaRecorder;
2.mediaRecorder.start()方法異常,在每次調用stop時要先調用
mediaRecorder.stop();
mediaRecorder.release();
兩個方法,並將
mediaRecorder = null。
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)異常,這里是設置音頻源,可嘗試將參數改為
MediaRecorder.AudioSource.DEFAULT
4.stop方法異常,如果是running狀態不正常,可能是其狀態丟失,需要將聲明的running也改為靜態的
0.增加需求,在生成視頻時大部分人都會根據mediaRecorder.setVideoSize(width, height);方法來定死視頻大小,導致一些手機會解析不了,或者是視頻比屏幕小,這里提供一種根據屏幕大小動態設置視頻大小的方法。
這里就要用到我們之前定義的全局的Application,然後調用getInstance()獲取其實例,
然後通過
DisplayMetrics dm = RecordApplication.getInstance().getResources().getDisplayMetrics();
private int width = dm.widthPixels;
private int height = dm.heightPixels;
private int dpi = dm.densityDpi;
來獲取屏幕的長、寬和dpi的值,這里不用WindowsManager方法是因為我是在非Activity去獲取屏幕長寬的,所以用了getDisplayMetrics();
這樣這個功能基本就是實現了。
Demo地址: https://github.com/han103070/Screencap
5. 安卓app,使用中黑屏返回首頁,為什麼會黑屏且返回首頁啊
閃黑屏的原因主要是我們啟動Activity的時候,需要跑完onCreate和onResume才會顯示界面 前幾天Boss就反應說,機器每次啟動程序都會閃一下黑屏,這個客戶不接受。沒辦法,只能想想怎麼解決,最後找到了下面的方法。閃黑屏的原因主要是 我們啟動Activity的時候,需要跑完onCreate和onResume才會顯示界面。也就是說需要處理一些數據後,才會顯示。按照這種思路,是不 是我把初始化的工作盡量減少就可以避免黑屏?事實是,就算你onCreate啥都不做,仍然會閃一下黑屏,因為初始化解析界面時需要一定時間。下面是解決 辦法: 1、自定義Theme 復制代碼 代碼如下: 設置背景圖Theme <style name="Theme.AppStartLoad" parent="android:Theme"> <item name="android:windowBackground">@drawable/ipod_bg</item> <item name="android:windowNoTitle">true</item> </style> //2、設置透明Theme <style name="Theme.AppStartLoadTranslucent" parent="android:Theme"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowNoTitle">true</item> </style> 上面我定義了兩種Theme,第一種Theme就是設置一張背景圖。當程序啟動時,首先顯示這張背景圖,避免出現黑屏。第二種Theme是把樣式設置為透明,程序啟動後不會黑屏而是整個透明了,等到界面初始化完才一次性顯示出來。下面說說兩種方式的優缺點: •Theme1 程序啟動快,界面先顯示背景圖,然後再刷新其他界面控制項。給人刷新不同步感覺。 •Theme2 給人程序啟動慢感覺,界面一次性刷出來,刷新同步。 2、修改AndroidManifest.xml 為了使上面Theme生效,我們需要設置一些Activity的Theme 復制代碼 代碼如下: <application android:allowBackup="true" android:icon="@drawable/ipod_icon" android:label="@string/app_name" android:launchMode="singleTask"> <!-- iPod主界面 --> <activity android:name="com.apical.apicalipod.IPodMainActivity" <!-- 使用上面定義的樣式 mythou--> android:theme="@style/Theme.AppStartLoad" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> //...... </application> •可以在Activity裡面增加上面自定義的樣式。另外在Application裡面增加也是可以的,而且是全局效果。 •自定義Theme放在 /res/values/styles.xml 裡面。如果沒有這個文件,自己添加一個即可。 •如果存在多個Activity切換,中間也可能會存在短暫黑屏問題。原因也是Activity啟動的時候需要初始化載入數據,如果想避免這種情況,可以在你切換的Activity裡面增加上面的樣式。 •上面兩種樣式都可以避免黑屏。可以實際測試一下你的程序選擇一種效果。 •這個只是把黑屏避免了,但是如果你程序初始化啟動慢,還是會給人程序啟動慢的感覺。需要自行優化程序初始化過程。 3、Theme屬性詳解 復制代碼 代碼如下: android:theme="@android:style/Theme.Dialog" //Activity顯示為對話框模式 android:theme="@android:style/Theme.NoTitleBar" //不顯示應用程序標題欄 android:theme="@android:style/Theme.NoTitleBar.Fullscreen" //不顯示應用程序標題欄,並全屏 android:theme="Theme.Light " //背景為白色 android:theme="Theme.Light.NoTitleBar" //白色背景並無標題欄 android:theme="Theme.Light.NoTitleBar.Fullscreen" //白色背景,無標題欄,全屏 android:theme="Theme.Black" //背景黑色 android:theme="Theme.Black.NoTitleBar" //黑色背景並無標題欄 android:theme="Theme.Black.NoTitleBar.Fullscreen" //黑色背景,無標題欄,全屏 android:theme="Theme.Wallpaper" //用系統桌面為應用程序背景 android:theme="Theme.Wallpaper.NoTitleBar" //用系統桌面為應用程序背景,且無標題欄 android:theme="Theme.Wallpaper.NoTitleBar.Fullscreen" //用系統桌面為應用程序背景,無標題欄,全屏 android:theme="Theme.Translucent" //透明背景 android:theme="Theme.Translucent.NoTitleBar" //透明背景並無標題 android:theme="Theme.Translucent.NoTitleBar.Fullscreen" //透明背景並無標題,全屏 android:theme="Theme.Panel " //面板風格顯示 android:theme="Theme.Light.Panel" //平板風格顯示 4、Theme和Style Android裡面除了Theme外還有Style,例如下面是Launcher裡面配置workspace的一個Style 復制代碼 代碼如下: <style name="WorkspaceIcon"> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">match_parent</item> <item name="android:layout_gravity">center</item> <item name="android:gravity">center_horizontal</item> <item name="android:singleLine">true</item> <item name="android:ellipsize">marquee</item> <item name="android:textSize">12sp</item> <item name="android:textColor">#FFF</item> <item name="android:shadowRadius">2.0</item> <item name="android:shadowColor">#B0000000</item> </style> Style可以理解為一組屬性集合,方便不同的View設置使用,我們在View裡面使用Style的時候,跟使用Theme是一樣的應用方法。那麼Style和Theme有什麼區別? 下面列出兩者區別: •樣式用在單獨的View,如:Button、TextView等 •主題通過AndroidManifest.xml中的<application>和<activity>用在整個應用或者某個 Activity,主題對整個應用或某個Activity存在全局性影響。 •如果一個應用使用了主題,同時應用下的view也使用了樣式,那麼當主題與樣式屬性發生沖突時,樣式的優先順序高於主題。 上面就是通過Theme解決程序啟動閃黑屏問題,並且講解了Theme和Style,通過Theme配置,其實還可以做個歡迎頁面。不過我們都希望程序啟動速度越快越好,因此還是需要多多優化自己的程序。
6. Android 全局彈窗(Dialog)快速實現
項目中 雲信IM同一帳號,在多處登錄時,要實現互踢功能。
在收到被踢通知時,彈窗提示用戶被踢,點擊繼續跳轉到登錄界面,取消則關閉App
由於不知道用戶會在哪個界面操作時被踢,接受通知的那個Activity有可能已經失去上下文,導致空指針異常!從而無法 Toast 或 彈窗。
能實現 全局Dialog 就好了!
由於退出App的時候,要清除之前所有的Activity,其實就是退出登錄功能。
我採用 集合法 來實現的退出登錄 ,在BaseActivity中記錄所有打開過的Activity,然後遍歷清除。
然後在 BaseActivity 中調用 addActivity() ,不在贅述!
如果能獲取到用戶當前觀看的界面,然後將上下文傳過去,就可以實現全局Dialog
當前頁面也就是棧頂Activity,也就是最後添加的那個Activity。如下:
然後在使用的地方獲取
即:
注意:之所以返回AppCompatActivity,而不是Activity,
是因為彈出Dialog 需要 supportFragmentManager ,Activity 獲取不到
以上就可以實現全局Dialog了。
另外,上述Dialog是用DialogFragment寫的,感興趣可閱 《DialogFragment 去除內容區稜角背景(不規則圓角)》 ,附有工具類及用法。
7. android 程序怎樣捕捉全局異常
Android系統的「程序異常退出」,給應用的用戶體驗造成不良影響。為了捕獲應用運行時異常並給出友好提示,便可繼承UncaughtExceptionHandler類來處理。通過Thread.()方法將異常處理類設置到線程上即可。
1、異常處理類,代碼如下:
[java] view plain
public class CrashHandler implements UncaughtExceptionHandler {
public static final String TAG = "CrashHandler";
private static CrashHandler INSTANCE = new CrashHandler();
private Context mContext;
private Thread.UncaughtExceptionHandler mDefaultHandler;
private CrashHandler() {
}
public static CrashHandler getInstance() {
return INSTANCE;
}
public void init(Context ctx) {
mContext = ctx;
mDefaultHandler = Thread.();
Thread.(this);
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
// if (!handleException(ex) && mDefaultHandler != null) {
// mDefaultHandler.uncaughtException(thread, ex);
// } else {
// android.os.Process.killProcess(android.os.Process.myPid());
// System.exit(10);
// }
System.out.println("uncaughtException");
new Thread() {
@Override
public void run() {
Looper.prepare();
new AlertDialog.Builder(mContext).setTitle("提示").setCancelable(false)
.setMessage("程序崩潰了...").setNeutralButton("我知道了", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
System.exit(0);
}
})
.create().show();
Looper.loop();
}
}.start();
}
/**
* 自定義錯誤處理,收集錯誤信息 發送錯誤報告等操作均在此完成. 開發者可以根據自己的情況來自定義異常處理邏輯
*
* @param ex
* @return true:如果處理了該異常信息;否則返回false
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return true;
}
// new Handler(Looper.getMainLooper()).post(new Runnable() {
// @Override
// public void run() {
// new AlertDialog.Builder(mContext).setTitle("提示")
// .setMessage("程序崩潰了...").setNeutralButton("我知道了", null)
// .create().show();
// }
// });
return true;
}
}
2、線程綁定異常處理類
[java] view plain
public class CrashHandlerActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(this); //傳入參數必須為Activity,否則AlertDialog將不顯示。
// 創建錯誤
throw new NullPointerException();
}
}