- 浏览: 1896687 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
龙宝宝吱吱:
我现在知道是在部署的项目里找,可是找不到啊,一般这个文件会在什 ...
webservice浏览器远程调用测试 -
龙宝宝吱吱:
这个webconfig文件在哪里啊,大神
webservice浏览器远程调用测试 -
yuer1218:
正在学习这块,能把源码发我一份吗,12045464@qq.co ...
Android时钟的widget【安卓进化三十七】 -
hongshanguo:
为嘛没有任务管理器的,求案例啊
近百android程序源码贡献 -
难得糊涂CN2010:
最近一个项目要用到widget,可以送下源码不?dz.bita ...
Android时钟的widget【安卓进化三十七】
Android的全局键(home键/长按耳机键)详解【android源码解析八】
如果想在Android手机要想扩展一个实体键,就我知道而言有两种方法,基于Android4.0的源码来分析的和2.3的源码有点区别,区别不大,下面分享给大家:
转载请标明出处:
(一)可以在frameworks层的KeyEvent.java这个类中定义一个值,在PhoneWindowManager.java这个类中做处理就可以了。(Home键就是这么实现的)。效果图如下:
(二)可以利用广播的形式,frameworks层PhoneWindow.java这个类的onKeyDown( )对这个实体键发广播,上层接受这个广播来处理也可以达到这个效果。耳机键就是利用广播来接受的。无论在哪个界面长按耳机键,都会进入到音乐的界面。(长按耳机键的)效果图如下:
下面我详细展开来说明一下:
一、先说Home键的实现的大致流程,即---->为什么点击Home键,都进入到launcher的待机界面;
(1)Home键的定义在
step1: frameworks/base/core/java/android/view/KeyEvent.java这个类中,在KeyEvent.java这个类中有个static的静态块:
static { populateKeycodeSymbolicNames(); }
step2: 这个populateKeycodeSymbolicNames()方法其实就是加载了许多键的定义,把这些键对应的值都放到Array数组中。
private static void populateKeycodeSymbolicNames() {
SparseArray<String> names = KEYCODE_SYMBOLIC_NAMES;
names.append(KEYCODE_UNKNOWN, "KEYCODE_UNKNOWN");
names.append(KEYCODE_SOFT_LEFT, "KEYCODE_SOFT_LEFT");
names.append(KEYCODE_SOFT_RIGHT, "KEYCODE_SOFT_RIGHT");
names.append(KEYCODE_HOME, "KEYCODE_HOME");
names.append(KEYCODE_BACK, "KEYCODE_BACK");
names.append(KEYCODE_CALL, "KEYCODE_CALL");
names.append(KEYCODE_ENDCALL, "KEYCODE_ENDCALL");
names.append(KEYCODE_0, "KEYCODE_0");
names.append(KEYCODE_1, "KEYCODE_1");
names.append(KEYCODE_2, "KEYCODE_2");
names.append(KEYCODE_3, "KEYCODE_3");
names.append(KEYCODE_4, "KEYCODE_4");
names.append(KEYCODE_5, "KEYCODE_5");
names.append(KEYCODE_6, "KEYCODE_6");
names.append(KEYCODE_7, "KEYCODE_7");
names.append(KEYCODE_8, "KEYCODE_8");
names.append(KEYCODE_9, "KEYCODE_9");
step3: 而Home键对应的值如下:
/** Key code constant: Home key.
* This key is handled by the framework and is never delivered to applications. */
public static final int KEYCODE_HOME = 3;
(2)Home键的处理如下:在
step1: frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java这个类中:
在这个方法interceptKeyBeforeDispatching(... ... ...)中处理有对Home,Search,menu,音量大小键等等:
/** {@inheritDoc} */
@Override
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
final boolean keyguardOn = keyguardOn();
final int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
final int metaState = event.getMetaState();
final int flags = event.getFlags();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final boolean canceled = event.isCanceled();
if (false) {
Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
+ repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed);
}
// If we think we might have a volume down & power key chord on the way
// but we're not sure, then tell the dispatcher to wait a little while and
// try again later before dispatching.
if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
if (mVolumeDownKeyTriggered && !mPowerKeyTriggered) {
final long now = SystemClock.uptimeMillis();
final long timeoutTime = mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS;
if (now < timeoutTime) {
return timeoutTime - now;
}
}
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
&& mVolumeDownKeyConsumedByScreenshotChord) {
if (!down) {
mVolumeDownKeyConsumedByScreenshotChord = false;
}
return -1;
}
}
// First we always handle the home key here, so applications
// can never break it, although if keyguard is on, we do let
// it handle it, because that gives us the correct 5 second
// timeout.
if (keyCode == KeyEvent.KEYCODE_HOME) {
// If we have released the home key, and didn't do anything else
// while it was pressed, then it is time to go home!
if (mHomePressed && !down) {
mHomePressed = false;
if (!canceled) {
// If an incoming call is ringing, HOME is totally disabled.
// (The user is already on the InCallScreen at this point,
// and his ONLY options are to answer or reject the call.)
boolean incomingRinging = false;
try {
ITelephony telephonyService = getTelephonyService();
if (telephonyService != null) {
incomingRinging = telephonyService.isRinging();
}
} catch (RemoteException ex) {
Log.w(TAG, "RemoteException from getPhoneInterface()", ex);
}
if (incomingRinging) {
Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");
} else {
launchHomeFromHotKey();
}
} else {
Log.i(TAG, "Ignoring HOME; event canceled.");
}
return -1;
}
// If a system window has focus, then it doesn't make sense
// right now to interact with applications.
WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
if (attrs != null) {
final int type = attrs.type;
if (type == WindowManager.LayoutParams.TYPE_KEYGUARD
|| type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
// the "app" is keyguard, so give it the key
return 0;
}
final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
for (int i=0; i<typeCount; i++) {
if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
// don't do anything, but also don't pass it to the app
return -1;
}
}
}
if (down) {
if (repeatCount == 0) {
mHomePressed = true;
} else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
if (!keyguardOn) {
handleLongPressOnHome();
}
}
}
return -1;
} else if (keyCode == KeyEvent.KEYCODE_MENU) {
........
Step2: 插曲《网上有例子说怎么在自己的应用中屏蔽Home键》--->原理:是在你的应用的Activity中加入了锁屏的type,因为系统对锁屏界面,点击Home键失效!网摘代码如下:
public class DMActivity extends Activity { private boolean flag = true;//true位屏蔽,false位不屏蔽 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override public void onAttachedToWindow() { if(flag) { this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD); } super.onAttachedToWindow(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if(keyCode == KeyEvent.KEYCODE_HOME){ return true; } return super.onKeyDown(keyCode, event); } }
Step3: 真正的原因如下,对锁屏模式的处理:
// If a system window has focus, then it doesn't make sense
// right now to interact with applications.
WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
if (attrs != null) {
final int type = attrs.type;
if (type == WindowManager.LayoutParams.TYPE_KEYGUARD
|| type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
// the "app" is keyguard, so give it the key
return 0;
}
final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
for (int i=0; i<typeCount; i++) {
if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
// don't do anything, but also don't pass it to the app
return -1;
}
}
}
Step4: 我们来看点击home键,为什么进入到launcher的待机界面:
// If we have released the home key, and didn't do anything else
// while it was pressed, then it is time to go home!
if (mHomePressed && !down) {
mHomePressed = false;
if (!canceled) {
// If an incoming call is ringing, HOME is totally disabled.
// (The user is already on the InCallScreen at this point,
// and his ONLY options are to answer or reject the call.)
boolean incomingRinging = false;
try {
ITelephony telephonyService = getTelephonyService();
if (telephonyService != null) {
incomingRinging = telephonyService.isRinging();
}
} catch (RemoteException ex) {
Log.w(TAG, "RemoteException from getPhoneInterface()", ex);
}
if (incomingRinging) {
Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");
} else {
launchHomeFromHotKey();
}
} else {
Log.i(TAG, "Ignoring HOME; event canceled.");
}
return -1;
}
Step5: 系统会判断,当前点击Home键并且没有电话打入的情况,才对Home键进行处理---->launchHomeFromHotKey();进入到----->launchHomeFromHotKey()方法中:
/** * A home key -> launch home action was detected. Take the appropriate action * given the situation with the keyguard. */ void launchHomeFromHotKey() { if (mKeyguardMediator.isShowingAndNotHidden()) { // don't launch home if keyguard showing } else if (!mHideLockScreen && mKeyguardMediator.isInputRestricted()) { // when in keyguard restricted mode, must first verify unlock // before launching home mKeyguardMediator.verifyUnlock(new OnKeyguardExitResult() { public void onKeyguardExitResult(boolean success) { if (success) { try { ActivityManagerNative.getDefault().stopAppSwitches(); } catch (RemoteException e) { } sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); startDockOrHome(); } } }); } else { // no keyguard stuff to worry about, just launch home! try { ActivityManagerNative.getDefault().stopAppSwitches(); } catch (RemoteException e) { } sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); startDockOrHome(); } }
Step6: 也是对锁屏模式有个判断,如果不在锁屏模式,就launch Home---->startDockOrHome(),进入——>
void startDockOrHome() {
Intent dock = createHomeDockIntent();
if (dock != null) {
try {
mContext.startActivity(dock);
return;
} catch (ActivityNotFoundException e) {
}
}
mContext.startActivity(mHomeIntent);
}
Step 7: 其实这个createHomeDockIntent()方法就是对android手机的几种模式进行判断,
The device is not in either car mode or desk mode
The device is in car mode but ENABLE_CAR_DOCK_HOME_CAPTURE is false
The device is in desk mode but ENABLE_DESK_DOCK_HOME_CAPTURE is false
The device is in car mode but there's no CAR_DOCK app with METADATA_DOCK_HOME
The device is in desk mode but there's no DESK_DOCK app with METADATA_DOCK_HOME
如果是以上模式,车载模式或者桌面模式,就返回dock不为空,否则为空。启动这个mHomeIntent。----->mHomeIntent定义如下:
mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
mHomeIntent.addCategory(Intent.CATEGORY_HOME);
mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Step 8: 这个mHomeIntent就是启动activity中配置Category属性的值为CATEGORY_HOME,启动的时候新启动一个任务,不是在当前的这个任务中启动launcherHome,而是新建一个task。
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记进入前台时(典型的操作是用户在主画面重启它),这个Activity和它之上的都将关闭,以至于用户不能再返回到它们,但是可以回到之前的Activity。
到这为止,Home键的流程已经分析完了。
二 、下面看看长按耳机键接受的广播的处理方式:
(1)这个长按耳机键捕获是在PhoneWindow.java类的onKeyDown()中,然后发送有序的广播---->如下:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
getContext().sendOrderedBroadcast(intent, null);
return true;
}
接受这个长按耳机键的广播是在Music的app中的----->
publicclassMediaButtonIntentReceiverextendsBroadcastReceiver{......}
, 需要在Manifest.xml中注册这个广播<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>。---->注册MediaButtonReceiver这个广播,
这个类中onReceive()方法定义的:代码如下--->
@Override
public void onReceive(Context context, Intent intent) {
String intentAction = intent.getAction();
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intentAction)) {
Intent i = new Intent(MediaPlaybackService.SERVICECMD);
i.putExtra(MediaPlaybackService.CMDNAME, MediaPlaybackService.CMDPAUSE);
context.sendBroadcast(i);
} else if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
KeyEvent event = (KeyEvent)
intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event == null) {
return;
}
int keycode = event.getKeyCode();
int action = event.getAction();
long eventtime = event.getEventTime();
// single quick press: pause/resume.
// double press: next track
// long press: start auto-shuffle mode.
String command = null;
switch (keycode) {
case KeyEvent.KEYCODE_MEDIA_STOP:
command = MediaPlaybackService.CMDSTOP;
break;
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
command = MediaPlaybackService.CMDTOGGLEPAUSE;
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
command = MediaPlaybackService.CMDNEXT;
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
command = MediaPlaybackService.CMDPREVIOUS;
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
command = MediaPlaybackService.CMDPAUSE;
break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
command = MediaPlaybackService.CMDPLAY;
break;
}
if (command != null) {
if (action == KeyEvent.ACTION_DOWN) {
if (mDown) {
if ((MediaPlaybackService.CMDTOGGLEPAUSE.equals(command) ||
MediaPlaybackService.CMDPLAY.equals(command))
&& mLastClickTime != 0
&& eventtime - mLastClickTime > LONG_PRESS_DELAY) {
mHandler.sendMessage(
mHandler.obtainMessage(MSG_LONGPRESS_TIMEOUT, context));
}
} else if (event.getRepeatCount() == 0) {
// only consider the first event in a sequence, not the repeat events,
// so that we don't trigger in cases where the first event went to
// a different app (e.g. when the user ends a phone call by
// long pressing the headset button)
// The service may or may not be running, but we need to send it
// a command.
Intent i = new Intent(context, MediaPlaybackService.class);
i.setAction(MediaPlaybackService.SERVICECMD);
if (keycode == KeyEvent.KEYCODE_HEADSETHOOK &&
eventtime - mLastClickTime < 300) {
i.putExtra(MediaPlaybackService.CMDNAME, MediaPlaybackService.CMDNEXT);
context.startService(i);
mLastClickTime = 0;
} else {
i.putExtra(MediaPlaybackService.CMDNAME, command);
context.startService(i);
mLastClickTime = eventtime;
}
mLaunched = false;
mDown = true;
}
} else {
mHandler.removeMessages(MSG_LONGPRESS_TIMEOUT);
mDown = false;
}
if (isOrderedBroadcast()) {
abortBroadcast();
}
}
}
}
step1: 在方法if (action == KeyEvent.ACTION_DOWN) { ... ... }做的处理,event.getRepeatCount() == 0这个判断的意思是“是否长按耳机键?”,如果长按耳机键event.getRepeatCount() 的值就一直增加。
step 2:短按耳机键:播放/暂停 --->音乐;短按启动MediaPlaybackService.java这个类,并且传入参数---->在这个服务类中有个接受广播的内部类:如下-->
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
String cmd = intent.getStringExtra("command");
MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);
if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
next(true);
} else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
prev();
} else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
if (isPlaying()) {
pause();
mInternalPause = false;
} else {
play();
}
} else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
pause();
mInternalPause = false;
} else if (CMDPLAY.equals(cmd)) {
play();
} else if (CMDSTOP.equals(cmd)) {
pause();
mInternalPause = false;
seek(0);
} else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
// Someone asked us to refresh a set of specific widgets, probably
// because they were just added.
int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
}
}
};
通过:如下方法来控制点击播放音乐,再次点击暂停,如此循环。
else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
if (isPlaying()) {
pause();
mInternalPause = false;
} else {
play();
}
}
内部类的广播是在启动MediaPlaybackService.java中注册的,解除注册在onDestroy()的方法中。
IntentFilter commandFilter = new IntentFilter(); commandFilter.addAction(SERVICECMD); commandFilter.addAction(TOGGLEPAUSE_ACTION); commandFilter.addAction(PAUSE_ACTION); commandFilter.addAction(NEXT_ACTION); commandFilter.addAction(PREVIOUS_ACTION); commandFilter.addAction(PLAYSTATUS_REQUEST); registerReceiver(mIntentReceiver, commandFilter);
Step 3: 长按耳机键--->发消息给mHandler,
mHandler.removeMessages(MSG_LONGPRESS_TIMEOUT);
在MediaButtonIntentReceiver.java中有个内部类Handler()如下——>
private static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_LONGPRESS_TIMEOUT:
if (!mLaunched) {
Context context = (Context)msg.obj;
Intent i = new Intent();
i.putExtra("autoshuffle", "true");
i.setClass(context, MusicBrowserActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(i);
mLaunched = true;
}
break;
}
}
};
长按耳机后就启动MusicBrowserActivity.java这个音乐播放类。并且传入参数“autoshuffle==true”,这个启动和launcher的启动相似,也是启动一个新的任务task,但是这个后面的标志有不同的地方Intent.FLAG_ACTIVITY_CLEAR_TOP。
到这为止就是实现了在任何界面长按耳机键都能进入到music的主界面。
总结如下:
其实启动的时候,要注意当前activity的launcherMode是什么,如果是SingleTask,就要小心一下。比如说:想要长按耳机键,进入到launcher的Mainmenu界面,这时候如果单纯的用以上方法套,返回键点击的时候不会回到上个activity中,会有问题。因为launcher是一直启动的运行于每个task之中的,你再次启动Launcher的时候,无论是否设置属性“i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)”,都会把前一个activity给finish掉,launcher会在栈顶也是栈底。因为launcher的launcherMode=singleTask。我们可以做个实验;
三、例子写两个app,一个属性为singletask,一个为standard。应singletask的启动standard的activity,然后在再次基础上启动singletask的activity,看standard的activity是否会destory掉。
step1:先看截图:
launchMode="singleTask"launchMode="standard"
Step 2:先启动launchMode="singleTask"的activity---->点击调用第二个App2的launchMode="standard"---->
点击按钮调用第一个App1的launchMode="singleTask"---->点击返回键。看log分析:
(1)点击启动launchMode="singleTask"的activity的log如下:
(2)点击调用第二个App2的launchMode="standard"的activity的log如下图:
(3)点击调用第一个App1launchMode="singleTask"的activity的log如下:
分析如下:看到这时候App2Activity--22已经执行了onStop()和onDestroy()方法了。验证了我以上的说法。
(4)点击返回键---->直接回到了launcher界面。log如下:
备注:要想解决以上问题也是可以的。就是在以上第(2)步:点击调用第二个App2的launchMode="standard"的activity的时候设置flag。Intent.FLAG_ACTIVITY_NEW_TASK。就可以解决以上问题了,每次启动一个新的任务,这样就能返回到App2Activity了。
(1)点击启动launchMode="singleTask"的activity的log如下:
(2)点击调用第二个App2的launchMode="standard"的activity的log如下图:
(3)点击调用第一个App1launchMode="singleTask"的activity的log如下:
看到如上图:App2Activity--22--->只是onStop()了,没有onDestroy掉。
(4)点击返回键---->直接回到了launcher界面---->log如下:
(5)通过log,我们可以看出返回到App2Activity了,我们再次点击返回键,--->返回到Launcher界面的log如下:
通过以上验证说明我的结论是正确的。launcherMode一直是android的核心技术,通过这次我会更加注意到activity的LauncherMode的。
欢迎各界人士留言,讨论,拍砖!转载请标明出处!
相关推荐
android 7.0/8.0/9.0/10.0全局手势事件无效,由于设置状态栏或者导航栏高度为0时,会导致全局手势事件滑不出来
android 后台监听按键事件方法及demoandroid 后台监听按键事件方法及demo
从底层到应用层详细讲解了android中如何实现全局快捷键的
<uses android:name=android.permission.SYSTEM_ALERT_WINDOW> 2、创建Dialog AlertDialog.Builder builder=new AlertDialog.Builder(this); builder.setIcon(R.drawable.logo_mini); builder.setTi
按键检测代码,实现短按,长按,连续按下检测功能。全程通畅无阻塞。 详细说明见博客: http://t.csdn.cn/DP43e 2. 使用说明: 通过修改宏定义可以修改开关时间; 通过查看头文件可以方便移植到不同硬件上; 3. ...
Android 媒体键监听以及模拟媒体键盘的实现 ;
android全局悬浮窗口可拖动android全局悬浮窗口可拖动android全局悬浮窗口可拖动android全局悬浮窗口可拖动
键盘事件往往是全局监听,设监听的函数为keyboard()。 keyup事件类型。该类型触发条件为按键按下去并松开。 //长按并松开只触发一次 document.addEventListener(‘keyup’, keyboard); [removed] = keyboard; //...
android 全局异常处理 继承java.lang中的UncaughtExceptionHandler 的构造方法uncaughtException 来捕获异常
Android 全局异常错误或崩溃捕捉。当出现崩溃,软件不会闪退,会出现弹出一个对话框,异常错误信息保存在sd卡一个文件夹下。后续需要还可以发送到服务器的。我博客网址: http://blog.csdn.net/qq_16064871。我写了...
Android全局异常捕获CrashHandler
共有两种方式实现的,一种是调用ProgressBar控件实现,一种是调用AVLoadingIndicatorView控件实现的,前一种转速慢,视觉效果不太好,后一种样式多,效果也很棒。
全局Toast工具类,作用:连续显示toast提示时取消上一个toast 通过ToastUtil.toast(Context(), "文本")调用
android studio 源码AndroidViewModel(让ViewModel访问全局资源);视图绑定;
android 全局dialog 兼容android 8.0、内置service模拟来自后台消息推送后自动弹出全局dialog
Android例子源码捕获应用全局异常避免强制退出并写出日志到sd卡
该资源包含了一个android全局异常捕获的代码,当应用发生异常的时候,会将该异常存放到sdcard上。
通过自定义style,从主题中动态设置app全局字体的Demo。
本实例配套博文《Android 异常捕获》http://www.cnblogs.com/scetopcsa/p/4237595.html