概况

Android在4.3的版本号中(即API 18)增加了NotificationListenerService,依据SDK的描写叙述(AndroidDeveloper)能够知道,当系统收到新的通知或者通知被删除时,会触发NotificationListenerService的回调方法。同一时候在Android
4.4 中新增了Notification.extras 字段,也就是说能够使用NotificationListenerService获取系统通知详细信息,这在曾经是须要用反射来实现的。

转载请务必注明出处:http://blog.csdn.net/yihongyuelan

重要关系

对于系统通知,三方APP使用NotificationListenerService主要目的是为了获取系统通知相关信息,主要包含:通知的新增和删除,获取当前通知数量,通知内容相关信息等。这些信息能够通过NotificationListenerService类提供的方法以及StatusBarNotification类对象来获取。

NotificationListenerService主要方法(成员变量):

cancelAllNotifications() :删除系统中全部可被清除的通知; 

cancelNotification(String pkg, String tag, int id) :删除详细某一个通知;

getActiveNotifications() :返回当前系统全部通知到StatusBarNotification[];

onNotificationPosted(StatusBarNotification sbn) :当系统收到新的通知后出发回调; 

onNotificationRemoved(StatusBarNotification sbn) :当系统通知被删掉后出发回调;

以上是NotificationListenerService的主要方法,通过这些方法就能够在应用中操作系统通知,在NotificationListenerService中除了对通知的操作之外,还能够获取到通知的StatusBarNotification对象,通过该对象能够获取通知更具体的数据。

StatusBarNotification主要方法(成员变量):

getId():返回通知相应的id;

getNotification():返回通知对象;

getPackageName():返回通知相应的包名;

getPostTime():返回通知发起的时间;

getTag():返回通知的Tag,假设没有设置返回null;

getUserId():返回UserId,用于多用户场景;

isClearable():返回该通知是否可被清楚,FLAG_ONGOING_EVENT、FLAG_NO_CLEAR;

isOngoing():检查该通知的flag是否为FLAG_ONGOING_EVENT;

使用简单介绍

正确使用NotificationListenerService须要注意三点:

(1). 新建一个类并继承自NotificationListenerService,override当中重要的两个方法;

public class NotificationMonitor extends NotificationListenerService {
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
Log.i("SevenNLS","Notification posted");
} @Override
public void onNotificationRemoved(StatusBarNotification sbn) {
Log.i("SevenNLS","Notification removed");
}
}

(2). 在AndroidManifest.xml中注冊Service并声明相关权限;

 <service android:name=".NotificationMonitor"
android:label="@string/service_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>

(3). 开启NotificationMonitor的监听功能;

完毕以上两步之后,将程序编译并安装到手机上,但此时该程序是无法监听到新增通知和删除通知的,还须要在"Settings > Security > Notification access"中,勾选NotificationMonitor。此时假设系统收到新的通知或者通知被删除就会打印出对应的log了。

这里须要注意,假设手机上没有安装使用NotificationListenerService类的APP,Notification access是不会显示出来的。能够在源代码/packages/apps/Settings/src/com/android/settings/SecuritySettings.java中看到,假设没有使用NotificationListenerService的APK,直接就不显示这一项了。

mNotificationAccess = findPreference(KEY_NOTIFICATION_ACCESS);
if (mNotificationAccess != null) {
final int total = NotificationAccessSettings.getListenersCount(mPM);
if (total == 0) {
if (deviceAdminCategory != null) {
deviceAdminCategory.removePreference(mNotificationAccess);
}
} else {
final int n = getNumEnabledNotificationListeners();
if (n == 0) {
mNotificationAccess.setSummary(getResources().getString(
R.string.manage_notification_access_summary_zero));
} else {
mNotificationAccess.setSummary(String.format(getResources().getQuantityString(
R.plurals.manage_notification_access_summary_nonzero,
n, n)));
}
}
}

使用具体解释

通过前面的解说(实际上就是对AndroidDeveloper的解释),已经能够正常使用NotificationListenerService了,但对于实际应用中,须要考虑的事情还比較多。比方:

1. 怎样检測应用已开启Notification access监听功能?

假设检測到应用没有激活Notification access监听功能,须要提示用户开启;

2. 能不能主动跳转到Notification access监听页面?

假设可以依据第1步的推断自己主动跳转到相应的页面,那可以省掉非常多操作;

3. 怎样与NotificationListenerService交互?

涉及到与Service的交互,但又与普通的Service不同,这里后文解释;

4. NotificationListenerService使用过程中有哪些注意事项?

在使用NotificationListenerService过程中自己遇到了一些坑,后文会通过分析给出对应的解决方式;

程序执行截图

 

图 1 程序执行截图

演示样例介绍

NotificationListenerDemo主要用于获取系统当前通知信息,并可手动创建"可清除通知",逐条删除"可清除通知",一次性删除"可清除通知",以及显示系统当前活动的通知信息。实际上该演示样例回答了前面使用具体解释中提出的各项疑问,在实际使用过程中相信大部分人都会遇到,因此这里逐条展开与大家分享。

图 2 主界面

功能分析

1. 怎样检測应用已开启Notification access监听功能?

在程序启动时,运行Notification access的检測,查看是否訪问Notification的权限。假设用户没有Enable Notification access,则弹出提示对话框,点击OK跳转到Notification access设置页面。

图 3 首次启动 isEnable

使用NotificationListenerService的应用假设开启了Notification access,系统会将包名等相关信息写入SettingsProver数据库中,因此能够从数据库中获取相关信息并过滤,从而推断应用是否开启了Notification access,代码例如以下:

private static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
private boolean isEnabled() {
String pkgName = getPackageName();
final String flat = Settings.Secure.getString(getContentResolver(),
ENABLED_NOTIFICATION_LISTENERS);
if (!TextUtils.isEmpty(flat)) {
final String[] names = flat.split(":");
for (int i = 0; i < names.length; i++) {
final ComponentName cn = ComponentName.unflattenFromString(names[i]);
if (cn != null) {
if (TextUtils.equals(pkgName, cn.getPackageName())) {
return true;
}
}
}
}
return false;
}

在返回值flat中假设包括了应用的包名,就可以确定应用已开启Notification access,反之则表示没有开启。

2. 能不能主动跳转到Notification access监听页面?

通过查看能够知道,Notification access界面接收action为"android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"的intent启动,因此使用startActivity能够非常easy的跳转到该页面,从而避免用户在Settings中查找。代码例如以下:

private static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
private void openNotificationAccess() {
startActivity(new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS));
}

3. 怎样与NotificationListenerService交互?

由于NotificationListenerService中包括了四个重要的方法,各自是:onNotificationPosted、onNotificationRemoved、cancelNotification、cancelAllNotifications。通过这些方法我们才干实现诸如通知信息的获取以及删除等功能,尽管这些方法是public的,那是不是意味着我们仅仅要拿到NotificationListenerService的对象就能够直接调用这些方法了呢?那怎样拿到Service的对象呢?在之前的博文中,曾有提到与Service的交互(
详细可參考拙作《Android中程序与Service交互的方式——交互方式》),能够看到与Service的交互有非常多种方法,但假设要拿到Service的对象,归根究竟还是须要Binder。

也就是说得使用bindService的办法,将onServiceConnected回调中的IBinder对象转型成NotificationListenerService的对象。測试代码例如以下:

//在MainActivity.java的onCreate方法中使用bindService帮顶NotificationMonitor服务
bindService(new Intent(this,NotificationMonitor.class ), new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName arg0) {
} @Override
public void onServiceConnected(ComponentName arg0, IBinder arg1) {
NotificationMonitor.MyBinder localBinder = (MyBinder)arg1;
NotificationMonitor mMonitor = localBinder.getService();
}
}, BIND_AUTO_CREATE);
//NotificationMonitor的onBind方法返回构造的Binder对象
public class NotificationMonitor extends NotificationListenerService {
private MyBinder mBinder = new MyBinder();
public class MyBinder extends Binder{
public NotificationMonitor getService(){
return NotificationMonitor.this;
}
} @Override
public IBinder onBind(Intent arg0) {
return mBinder;
} @Override
public void onNotificationPosted(StatusBarNotification sbn) {
getActiveNotifications();
cancelAllNotifications();
} @Override
public void onNotificationRemoved(StatusBarNotification sbn) {
}
}

那这样操作之后是不是就意味着能够拿到NotificationMonitor的对象并直接调用getActiveNotifications()方法,用于获取当前系统通知的信息了呢?非常抱歉,事实证明这样是不行的。这里简单的分析下,在后面的NotificationListenerService原理分析中再具体解说。在NotificationListenerService的源代码中能够看到:

@Override
public IBinder onBind(Intent intent) {
if (mWrapper == null) {
mWrapper = new INotificationListenerWrapper();
}
return mWrapper;
}

这里的INotificationListenerWrapper是NotificationListenerService的一个内部类:

private class INotificationListenerWrapper extends INotificationListener.Stub

而NotificationMonitor继承自NotificationListenerService,默认的onBind方法却是:

@Override
public IBinder onBind(Intent intent) {
return super.onBind(intent);
}

这里注意,普通情况下service的onBind方法返回要么是null要么是Binder对象,可这里直接调用父类NotificationListenerService的onBind方法,而父类返回的是INotificationListenerWrapper的对象。这说明Binder对象已经被指定了,不能再给NotificationMonitor指定其他的Binder对象。假设你非要给NotificationMonitor指定其他的Binder对象,那么就无法使用INotificationListenerWrapper提供的方法。也就是说要么就用系统NotificationListenerService提供的方法,要么就把NotificationMonitor当一个普通的Service来用,系统提供的方法都不能使用。

那应该怎样使用NotificationListenerService中的方法呢?在拙作《Android中程序与Service交互的方式——交互方式》中,已经提供了非常多的样例,这里仅以广播的方式为例。

既然NotificationMonitor能够使用NotificationListenerService的方法,那通过NotificationMonitor把通知状态的改变以及数据获取到,并使用static数据进行存储,之后再在MainActivity中直接使用就可以。在MainActivity中控制通知的单个删除和所有删除,则使用广播的方式发送给NotificationMonitor进行处理。MainActivity与NotificationMonitor的关系类图例如以下:

图 4 结构类图

NotificationMonitor和MainActivity关键代码例如以下:

public class NotificationMonitor extends NotificationListenerService {
private static final String TAG = "SevenNLS";
private static final String TAG_PRE = "[" + NotificationMonitor.class.getSimpleName() + "] ";
private static final int EVENT_UPDATE_CURRENT_NOS = 0;
public static final String ACTION_NLS_CONTROL = "com.seven.notificationlistenerdemo.NLSCONTROL";
//用于存储当前全部的Notification的StatusBarNotification对象数组
public static List<StatusBarNotification[]> mCurrentNotifications = new ArrayList<StatusBarNotification[]>();
public static int mCurrentNotificationsCounts = 0;
//收到新通知后将通知的StatusBarNotification对象赋值给mPostedNotification
public static StatusBarNotification mPostedNotification;
//删除一个通知后将通知的StatusBarNotification对象赋值给mRemovedNotification
public static StatusBarNotification mRemovedNotification;
private CancelNotificationReceiver mReceiver = new CancelNotificationReceiver();
// String a;
private Handler mMonitorHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_UPDATE_CURRENT_NOS:
updateCurrentNotifications();
break;
default:
break;
}
}
}; class CancelNotificationReceiver extends BroadcastReceiver { @Override
public void onReceive(Context context, Intent intent) {
String action;
if (intent != null && intent.getAction() != null) {
action = intent.getAction();
if (action.equals(ACTION_NLS_CONTROL)) {
String command = intent.getStringExtra("command");
if (TextUtils.equals(command, "cancel_last")) {
if (mCurrentNotifications != null && mCurrentNotificationsCounts >= 1) {
//每次删除通知最后一个
StatusBarNotification sbnn = getCurrentNotifications()[mCurrentNotificationsCounts - 1];
cancelNotification(sbnn.getPackageName(), sbnn.getTag(), sbnn.getId());
}
} else if (TextUtils.equals(command, "cancel_all")) {
//删除全部通知
cancelAllNotifications();
}
}
}
} } @Override
public void onCreate() {
super.onCreate();
logNLS("onCreate...");
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_NLS_CONTROL);
registerReceiver(mReceiver, filter);
//在onCreate时第一次调用getActiveNotifications()
mMonitorHandler.sendMessage(mMonitorHandler.obtainMessage(EVENT_UPDATE_CURRENT_NOS));
} @Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver);
} @Override
public IBinder onBind(Intent intent) {
// a.equals("b");
logNLS("onBind...");
return super.onBind(intent);
} @Override
public void onNotificationPosted(StatusBarNotification sbn) {
//当系统收到新的通知后,更新mCurrentNotifications列表
updateCurrentNotifications();
logNLS("onNotificationPosted...");
logNLS("have " + mCurrentNotificationsCounts + " active notifications");
mPostedNotification = sbn;
//通过下面方式能够获取Notification的具体信息
/*
* Bundle extras = sbn.getNotification().extras; String
* notificationTitle = extras.getString(Notification.EXTRA_TITLE);
* Bitmap notificationLargeIcon = ((Bitmap)
* extras.getParcelable(Notification.EXTRA_LARGE_ICON)); Bitmap
* notificationSmallIcon = ((Bitmap)
* extras.getParcelable(Notification.EXTRA_SMALL_ICON)); CharSequence
* notificationText = extras.getCharSequence(Notification.EXTRA_TEXT);
* CharSequence notificationSubText =
* extras.getCharSequence(Notification.EXTRA_SUB_TEXT);
* Log.i("SevenNLS", "notificationTitle:"+notificationTitle);
* Log.i("SevenNLS", "notificationText:"+notificationText);
* Log.i("SevenNLS", "notificationSubText:"+notificationSubText);
* Log.i("SevenNLS",
* "notificationLargeIcon is null:"+(notificationLargeIcon == null));
* Log.i("SevenNLS",
* "notificationSmallIcon is null:"+(notificationSmallIcon == null));
*/
} @Override
public void onNotificationRemoved(StatusBarNotification sbn) {
//当有通知被删除后,更新mCurrentNotifications列表
updateCurrentNotifications();
logNLS("removed...");
logNLS("have " + mCurrentNotificationsCounts + " active notifications");
mRemovedNotification = sbn;
} private void updateCurrentNotifications() {
try {
StatusBarNotification[] activeNos = getActiveNotifications();
if (mCurrentNotifications.size() == 0) {
mCurrentNotifications.add(null);
}
mCurrentNotifications.set(0, activeNos);
mCurrentNotificationsCounts = activeNos.length;
} catch (Exception e) {
logNLS("Should not be here!!");
e.printStackTrace();
}
} //获取当前状态栏显示通知总数
public static StatusBarNotification[] getCurrentNotifications() {
if (mCurrentNotifications.size() == 0) {
logNLS("mCurrentNotifications size is ZERO!!");
return null;
}
return mCurrentNotifications.get(0);
} private static void logNLS(Object object) {
Log.i(TAG, TAG_PRE + object);
} }

而MainActivity主要负责界面显示与交互,关键代码例如以下:

public class MainActivity extends Activity {

    private static final String TAG = "SevenNLS";
private static final String TAG_PRE = "["+MainActivity.class.getSimpleName()+"] ";
private static final int EVENT_SHOW_CREATE_NOS = 0;
private static final int EVENT_LIST_CURRENT_NOS = 1;
private static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
private static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
private boolean isEnabledNLS = false;
private TextView mTextView; private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_SHOW_CREATE_NOS:
//显示创建的Notification相应的pkgName、Tag、Id
showCreateNotification();
break;
case EVENT_LIST_CURRENT_NOS:
//显示当前全部的Notification数量及其包名
listCurrentNotification();
break; default:
break;
}
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.textView);
} @Override
protected void onResume() {
super.onResume();
//推断是否有开启Notification access
isEnabledNLS = isEnabled();
logNLS("isEnabledNLS = " + isEnabledNLS);
if (!isEnabledNLS) {
//假设没有开启则显示确认对话框
showConfirmDialog();
}
} public void buttonOnClicked(View view) {
mTextView.setTextColor(Color.BLACK);
switch (view.getId()) {
case R.id.btnCreateNotify:
logNLS("Create notifications...");
//创建可清除的Notification
createNotification(this);
//显示当前状态栏中全部Notification数量及其包名
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_SHOW_CREATE_NOS), 50);
break;
case R.id.btnClearLastNotify:
logNLS("Clear Last notification...");
//清除最后一个Notification
clearLastNotification();
//显示当前状态栏中全部Notification数量及其包名
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_LIST_CURRENT_NOS), 50);
break;
case R.id.btnClearAllNotify:
logNLS("Clear All notifications...");
//清除全部"可被清除"的Notification
clearAllNotifications();
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_LIST_CURRENT_NOS), 50);
break;
case R.id.btnListNotify:
logNLS("List notifications...");
listCurrentNotification();
break;
case R.id.btnEnableUnEnableNotify:
logNLS("Enable/UnEnable notification...");
//打开Notification access启动/取消界面
openNotificationAccess();
break;
default:
break;
}
} //......省略
}

4. NotificationListenerService使用过程中有哪些注意事项?

假设细心察看代码的童鞋,一定发现代码中有使用Handler,以及一些奇怪但又被凝视掉的代码,比方"a.equals("b")"。从使用上来说,没有必要使用handler,那干嘛要多次一举?这里就给大家分享一下在写NotificationListenerDemo时遇到的一些坑。

①. NotificationMonitor的onCreate方法中使用handler来调用getActiveNotifications()方法

若直接在onCreate或者onBind方法中调用getActiveNotifications()方法是无法获取当前系统通知。主要是由于NotificationMonitor还未完毕初始化,而根本原因则是INotificationListenerWrapper对象mWrapper还未初始化,此时使用getActiveNotifications()方法又会调用到mWrapper,因此无法返回正常数据。在NotificationListenerService中能够看到getActiveNotifications()的源代码:

public StatusBarNotification[] getActiveNotifications() {
try {
return getNotificationInterface().getActiveNotificationsFromListener(mWrapper);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
return null;
}

也就是说仅仅要在onBind方法完毕之后,再调用getActiveNotifications()方法就能够正常获取数据了,因此这里使用了handler多线程的方式。当然,为了保险能够使用sendEmptyMessgeDelay加上延时。

②. 假设NotificationMonitor在onCreate或onBind方法中crash,则该service已经失效,需重新启动手机才干进行兴许开发验证

假设在onCreate或者onBind方法中,出现异常导致NotificationMonitor发生crash,就算找到问题并将其改正,之后的验证还是无法继续进行的,也就是无法收到通知的新增和删除消息,onNotificationPosted和onNotificationRemoved方法不会被调用。

这也是我在onBind方法中有益凝视导致空指针异常的代码,有兴趣的童鞋能够把凝视去掉后尝试,去掉凝视会导致NotificationListenerDemo异常停止,此时你再加上凝视再次执行NotificationListenerDemo,尽管程序能够正常启动,但无法正常执行NotificationMonitor中的onNotificationPosted和onNotificationRemoved方法。这个涉及NotificationListenerService的原理,后面会另行分析。

③. MainActivity中onClick方法里使用handler操作

当点击删除通知时,系统通知相关状态还未更新,此时还没有回调到NotificationMonitor中,所以获取的数据就还是上一次的数据。为了可以获取到正确的Notification数据,可以使用handler并加上延时,这样再去获取Notification信息时,系统已经触发了NotificationMonitor回调,数据也有正常了。另外,50ms的延时差点儿是感知不到的。

④. 为什么要使用ArrayList来保存StatusBarNotification数组对象

当新增或者删除通知时,会触发onNotificationPosted或onNotificationRemoved回调,在该方法中调用getActiveNotifications()方法用以获取当前系统通知信息。而getActiveNotifications()返回的是StatusBarNotification[]数组,由于这个数组是可变长的,也就是长度会随时变化,因此无法直接存储。使用ArrayList能够非常好的解决问题,在ArrayList对象中加入一个StatusBarNotification[]对象,之后使用ArrayList.set(0,statusbar[])方法对数据进行更新就可以。

总结

NotificationListenerService是Android 4.3 之后新增的接口服务,用于获取系统Notification信息,这在之前的Android版本号是无法直接办到的。在Android 4.4中,添加了Notification.extra变量,使得获取Notification相关信息更加丰富,这些接口的开放更加利于三方应用的使用,但同一时候也会带来一些隐私问题。

本文针对NotificationListenerService的使用进行了具体分析,当然当中不乏有失偏颇的地方,本着互联网知识共享精神也将自己的一些记录公布出来,一来可做笔记,二来希望可以给苦苦寻觅的童鞋一些帮助。

兴许会对NotificationListenerService的原理进行分析,敬请期待。

NotificationMonitor代码免积分下载:下载Demo

为了兴许可以更新,已经代码传到github上,有兴趣的童鞋可以在github上查看,连接戳这里

Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(一)__使用具体解释的更多相关文章

  1. Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(二)__原理分析

    前置文章: <Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(一)__使用具体解释> 转载请务必注明出处:http://b ...

  2. 【构建Android缓存模块】(一)吐槽与原理分析

    http://my.oschina.net/ryanhoo/blog/93285 摘要:在我翻译的Google官方系列教程中,Bitmap系列由浅入深地介绍了如何正确的解码Bitmap,异步线程操作以 ...

  3. Android插件化与热修复(六)-微信Tinker原理分析

    Tinker热修复原理分析 热补丁技术是在用户不需要重新安装应用的情况下实现应用更新,可快速解决一些线上问题.热补丁省去了Android应用发布版本的成本,而且用户端的更新也是无感知的. Tinker ...

  4. Android 学习笔记之WebService实现远程调用+内部原理分析...

    PS:终于可以抽出时间写写博客了,忙着学校的三周破实训外加替考...三周了,没怎么学习...哎... 学习内容: 1.WebService 实现远程方法的调用   什么是WebService...   ...

  5. Android大图片裁剪终极解决方案(上:原理分析)

    转载声明:Ryan的博客文章欢迎您的转载,但在转载的同时,请注明文章的来源出处,不胜感激! :-)  http://my.oschina.net/ryanhoo/blog/86842 约几个月前,我正 ...

  6. Android窗口管理服务WindowManagerService显示窗口动画的原理分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8611754 在前一文中,我们分析了Activi ...

  7. Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程

    本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处 本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉. ...

  8. Android 4.4 Kitkat Phone工作流程浅析(七)__来电(MT)响铃流程

    本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处 本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉. ...

  9. Android 4.4 KitKat 新特性

    New in Android 4.4 KitKat 本文是一个概览,关于KitKat,也即Android4.4的新东西,先是功能型的,之后是设计上的. 很多特性本文并没有提到,很多提到的特性也只是简短 ...

随机推荐

  1. Android DrawerLayout 抽屉

    Android DrawerLayout 抽屉 DrawerLayout 在supportV4 Lib在.类似的开源slidemenu如,DrawerLayout父类ViewGroup,自定义组件基本 ...

  2. Eclipse中为什么创建DynamicWebProject后没有默认的web.xml文件?

    在Eclipse中新建DynamicWebProject的时候不要直接点"完毕",在下一步有个勾选项(Generate web.xml deployment descriptor) ...

  3. Android 动画具体解释View动画

    为了让用户更舒适的在某些情况下,利用动画是那么非常有必要的.Android在3.0一旦支持两种动画Tween动漫Frame动画.Tween动画支持简单的平移,缩放,旋转,渐变.Frame动画就像Gif ...

  4. 让Android系统支持ubifs文件系统

    原文地址:http://www.cnblogs.com/linucos/p/3279381.html 1. ubifs号称性能比yaffs2 好,同时压缩可读写,文件系统image体较小同时可写,相当 ...

  5. linux文件打开模式

     文件打开 int open(const char *pathname, int flags, mode_t mode); 普通方式(Canonical mode) flags中没有设置O_SYN ...

  6. 创建Material Design风格Android应用--自定义阴影和裁剪视图

    之前已经写过通过应用主题和使用ListView, CardView,应用Material Design样式,同一时候都都能够通过support library向下兼容.今天要写的阴影和视图裁剪.无法向 ...

  7. Redis 的性能

    Redis 的性能幻想与残酷现实 2011 年,当初选择 Redis 作为主要的内存数据存储,主要吸引我的是它提供多样的基础数据结构可以很方便的实现业务需求.另一方面又比较担心它的性能是否足以支撑,毕 ...

  8. Directx11学习笔记【十一】 画一个简单的三角形--effect框架的使用

    这里不再介绍effect框架的具体使用,有关effect框架使用可参考http://www.cnblogs.com/zhangbaochong/p/5475961.html 实现的功能依然是画一个简单 ...

  9. crm创建基于fetch自己的自定义报告

    在解决方案资源管理器,右键点击"报表"目录.然后点击"增加了新的报告". 打开"报表向导". 在"欢迎来到报表向导"前, ...

  10. Android下拉刷新上拉载入控件,对全部View通用!

    转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38868463 前面写过一篇关于下拉刷新控件的博客下拉刷新控件终结者:Pull ...