前置文章:

《Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(一)__使用具体解释》

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

概况

在上一篇文章《Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(一)__使用具体解释》中具体介绍了NotificationListenerService的用法,以及在使用过程中遇到的问题和规避方案。本文主要分析NotificationListenerService实现原理。以及具体分析在上一篇文章中提到的相关问题和产生的根本原因。

在原理分析前。先看看NotificationListenerService涉及到的类以及基本作用,如图1所看到的:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWlob25neXVlbGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

图 1 NLS注冊及回调过程

通过图1能够看到,整个通知状态获取分为三部分:

①. 监听器注冊。新建一个类NotificationMonitor继承自NotificationListenerService。

②. 系统通知管理。系统通知管理由NotificationManagerService负责。

③. 通知状态回调;当系统通知状态改变之后,NotificationManagerService会通知NotificationListenerService。最后再由NotificationListenerService通知其全部子类。

在整个系统中,通知管理是由NotificationManagerService完毕的。NotificationListenerService仅仅是在通知改变时,会获得对应的通知消息。这些消息终于会回调到NotificationListenerService的全部子类中。

NotificationListenerService启动

NotificationListenerService尽管继承自Service,但系统中实际上启动的是其子类,为了表述方便,后文统一使用NotificationListenerService启动来指代。其子类的启动有三个途径。各自是:开机启动、接收PACKAGE相关广播(安装、卸载等)启动、SettingsProvider数据变更启动。

既然NotificationListenerService是一个service,那其子类启动方式自然就是bindService或者startService。在SourceCode/frameworks/base/services/java/com/android/server/NotificationManagerService.java中能够找到。实际上NotificationListenerService的启动是通过bindServiceAsUser来实现的,而bindServiceAsUser与bindService作用一致。

开机启动

由于NotificationListenerService终于是在NotificationManagerService中启动的,因此当系统在开机第一次启动时。会进行NotificationManagerService初始化,之后会调用其SystemReady方法,继而调用rebindListenerServices以及registerListenerService(),最后使用bindServiceAsUser实现NotificationListenerService的启动。rebindListenerServices代码例如以下:

void rebindListenerServices() {
final int currentUser = ActivityManager.getCurrentUser();
//获取系统中哪些应用开启了Notification access
String flat = Settings.Secure.getStringForUser(
mContext.getContentResolver(),
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
currentUser); NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
final ArrayList<ComponentName> toAdd; synchronized (mNotificationList) {
// unbind and remove all existing listeners
toRemove = mListeners.toArray(toRemove); toAdd = new ArrayList<ComponentName>();
final HashSet<ComponentName> newEnabled = new HashSet<ComponentName>();
final HashSet<String> newPackages = new HashSet<String>(); // decode the list of components
if (flat != null) {
String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
for (int i=0; i<components.length; i++) {
final ComponentName component
= ComponentName.unflattenFromString(components[i]);
if (component != null) {
newEnabled.add(component);
toAdd.add(component);
newPackages.add(component.getPackageName());
}
} mEnabledListenersForCurrentUser = newEnabled;
mEnabledListenerPackageNames = newPackages;
}
} //对全部NotificationListenerService全部unbindService操作
for (NotificationListenerInfo info : toRemove) {
final ComponentName component = info.component;
final int oldUser = info.userid;
Slog.v(TAG, "disabling notification listener for user " + oldUser + ": " + component);
unregisterListenerService(component, info.userid);
}
//对全部NotificationListenerService进行bindService操作
final int N = toAdd.size();
for (int i=0; i<N; i++) {
final ComponentName component = toAdd.get(i);
Slog.v(TAG, "enabling notification listener for user " + currentUser + ": "
+ component);
registerListenerService(component, currentUser);
}
}

在该方法中将获取系统中全部NotificationListenerService,并进行registerListenerService操作,代码例如以下:

private void registerListenerService(final ComponentName name, final int userid) {
//... ...省略 Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
intent.setComponent(name); intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
R.string.notification_listener_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0)); try {
if (DBG) Slog.v(TAG, "binding: " + intent);
//使用bindService启动NotificationListenerService
if (!mContext.bindServiceAsUser(intent,
new ServiceConnection() {
INotificationListener mListener;
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mNotificationList) {
mServicesBinding.remove(servicesBindingTag);
try {
mListener = INotificationListener.Stub.asInterface(service);
NotificationListenerInfo info = new NotificationListenerInfo(
mListener, name, userid, this);
service.linkToDeath(info, 0);
//service启动成功之后将相关信息加入到mListeners列表中,兴许通过该列表触发回调
mListeners.add(info);
} catch (RemoteException e) {
// already dead
}
}
} @Override
public void onServiceDisconnected(ComponentName name) {
Slog.v(TAG, "notification listener connection lost: " + name);
}
},
Context.BIND_AUTO_CREATE,
new UserHandle(userid)))
//... ...省略
}
}

整个启动流程如图2所看到的:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWlob25neXVlbGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

图 2 NLS开机启动时序图

广播启动

当系统安装或者卸载应用的时候。也会触发NotificationListenerService的启动。当一个使用NotificationListenerService的应用被卸载掉后,须要在Notification access界面清除对应的选项,或者当多用户切换时,也会更新NotificationListenerService的状态。在NotificationManagerService中监听了下面广播:

Intent.ACTION_PACKAGE_ADDED
Intent.ACTION_PACKAGE_REMOVED
Intent.ACTION_PACKAGE_RESTARTED
Intent.ACTION_PACKAGE_CHANGED
Intent.ACTION_QUERY_PACKAGE_RESTART
Intent.ACTION_USER_SWITCHED

这些广播在应用变更时由系统发出。比方安装、卸载、覆盖安装应用等等。当NotificationManagerService接收这些广播后编会调用rebindListenerServices,之后的流程就与前面一样。

启动流程例如以下:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWlob25neXVlbGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

图 3 NLS广播启动

数据库变更启动

在NotificationManagerService中使用了ContentObserver监听SettingsProvider数据库变化,当Notification access有更新时,会更新NotificationListenerService的状态。比如,当用户进入Notification access界面,手动开启或关闭相关应用的Notification access权限时便会触发这样的启动方式。当数据库中NotificationListenerService关联的信息改变后。会触发ContentObserver的onChange方法,继而调用update方法更新系统中NotificationListenerService的服务状态,最后调用到rebindListenerServices中。

整个流程例如以下:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWlob25neXVlbGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

图 4 NLS数据库变更启动

NotificationListenerService启动小结

在系统中实际上运行的是NotificationListenerService的子类,这些子类的启动方式分为三种:开机启动时NotificationManagerService初始化回调。接收相关广播后运行;数据库变更后运行。

这些启动方式归根究竟还是bindService的操作。

NotificationListenerService调用流程

前面提到了NotificationListenerService的启动流程,当启动完毕之后就是调用。整个调用流程分为两种情况。即:新增通知和删除通知。

新增通知

当系统收到新的通知消息时,会调用NotificationManager的notify方法用以发起系统通知,在notify方法中则调用关键方法enqueueNotificationWithTag:

service.enqueueNotificationWithTag(......)

这里的service是INotificationManager的对象,而NotificationManagerService继承自INotificationManager.Stub。

也就是说NotificationManager与NotificationManagerService实际上就是client与server的关系,这里的service终于是NotificationManagerService的对象。这里便会跳转到NotificationManagerService的enqueueNotificationWithTag方法中,实际调用的是enqueueNotificationInternal方法。在该方法中就涉及到Notification的组装。之后调用关键方法notifyPostedLocked():

private void notifyPostedLocked(NotificationRecord n) {
final StatusBarNotification sbn = n.sbn.clone();
//这里触发mListeners中全部的NotificationListenerInfo回调
for (final NotificationListenerInfo info : mListeners) {
mHandler.post(new Runnable() {
@Override
public void run() {
info.notifyPostedIfUserMatch(sbn);
}});
}
}

到这里就開始准备回调了,由于前面通知已经组装完成准备显示到状态栏了,之后就须要将相关的通知消息告诉全部监听者。

继续看到notifyPostedIfUserMatch方法:

public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
//... ...省略
try {
listener.onNotificationPosted(sbn);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}

上面的listener对象是NotificationListenerInfo类的全局变量,那是在哪里赋值的呢?还记得前面注冊NotificationListenerService的时候bindServiceAsUser,当中new了一个ServiceConnection对象,并在其onServiceConnected方法中有例如以下代码:

public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mNotificationList) {
mServicesBinding.remove(servicesBindingTag);
try {
//mListener就是NotificationListenerService子类的对象
//service是INotificationListenerWrapper的对象。INotificationListenerWrapper
//继承自INotificationListener.Stub,是NotificationListenerService的内部类
mListener = INotificationListener.Stub.asInterface(service);
//使用mListener对象生成相应的NotificationListenerInfo对象
NotificationListenerInfo info = new NotificationListenerInfo(
mListener, name, userid, this);
service.linkToDeath(info, 0);
mListeners.add(info);
} catch (RemoteException e) {
// already dead
}
}
}

也就是说在NotificationListenerService启动并连接的时候,将binder对象保存到了NotificationListenerInfo中。

这里就得看看NotificationListenerService的onBind方法返回了,代码例如以下:

@Override
public IBinder onBind(Intent intent) {
if (mWrapper == null) {
mWrapper = new INotificationListenerWrapper();
}
//这里返回的是INotificationListenerWrapper对象
return mWrapper;
} private class INotificationListenerWrapper extends INotificationListener.Stub {
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
try {
//onNotificationPosted是抽象方法之中的一个
NotificationListenerService.this.onNotificationPosted(sbn);
} catch (Throwable t) {
Log.w(TAG, "Error running onNotificationPosted", t);
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
try {
//onNotificationRemoved是还有一个抽象方法
NotificationListenerService.this.onNotificationRemoved(sbn);
} catch (Throwable t) {
Log.w(TAG, "Error running onNotificationRemoved", t);
}
}
}

通过以上代码能够知道,当在notifyPostedIfUserMatch运行listener.onNotificationPosted方法时。实际上会调用到NotificationListenerService.INotificationListenerWrapper的onNotificationPosted方法。

NotificationListenerService是一个Abstract类。当中的Abstract方法是onNotificationPosted和onNotificationRemoved。当触发NotificationListenerService.INotificationListenerWrapper的onNotificationPosted方法时,继续调用了NotificationListenerService.this.onNotificationPosted(sbn)。这样会继续调用全部NotificationListenerService子类中的onNotificationPosted方法,系统通知新增的消息便传到了全部NotificationListenerService中。

从整个流程来看。新增通知的发起点是NotificationManager,处理通知则是由NotificationManagerService完毕,传输过程是通过NotificationListenerService。最后回调方法是各个继承自NotificationListenerService的子类。整个过程的调用时序图例如以下:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWlob25neXVlbGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

图 5 onNotificationPosted触发流程

删除通知

与"新增通知"类似的流程是"删除通知",发起点在NotificationManager,之后经由NotificationManagerService处理和NotificationListenerService传递,最后到达各个继承自NotificationListenerService的子类中,仅仅只是最后的处理方法变成了onNotificationRemoved。调用时序图下:

图 6 onNotificationRemoved触发流程

NotificationListenerService调用流程小结

简单来看,NotificationListenerService在系统通知的消息传递过程中,起到了代理的作用。

继承自NotificationListenerService的类作为client端。真正的server端则是NotificationManagerService,由它负责整个Notification的控制与管理。NotificationManagerService将处理之后的结果通过NotificationListenerService返回给client端,终于各个client端通过onNotificationPosted和onNotificationRemoved方法拿到系统通知状态变更的相关信息。

NotificationListenerService重点分析

前文分析了整个NotificationListenerService的启动和调用。通过以上分析能够非常清楚的了解NotificationListenerService的工作流程。在上一篇文章《Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(一)__使用具体解释》中,文末分析了在NotificationListenerService在使用过程中的遇到的一些问题。但并没有深究出现这些问题的根本原因,下文会对这些问题进行具体分析。

Notification access页面不存在

当手机上没有安装不论什么使用NotificationListenerService的应用时,系统默认不会显示"Notification access"选项。

仅仅有手机中安装了使用NotificationListenerService的应用,才干够在"Settings > Security > Notification access" 找到相应的设置页面。

在SourceCode/packages/apps/Settings/src/com/android/settings/SecuritySettings.java中能够看到例如以下初始化代码:

//... ...省略
mNotificationAccess = findPreference(KEY_NOTIFICATION_ACCESS);
if (mNotificationAccess != null) {
final int total = NotificationAccessSettings.getListenersCount(mPM);
if (total == 0) {
if (deviceAdminCategory != null) {
//假设系统中没有安装使用NLS的应用则删除显示
deviceAdminCategory.removePreference(mNotificationAccess);
}
} else {
//获取系统中有多少启动了Notification access的应用
final int n = getNumEnabledNotificationListeners();
//依据启用的数量显示不同的Summary
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)));
}
}
}
//... ...省略

getActiveNotifications()方法返回为null

有非常多人在使用getActiveNotifications方法时返回为null。多数情况下是由于在onCreate或者在onBind中调用了getActiveNotifications方法。比方NotificaionMonitor extends NotificationListenerService:

public class NotificationMonitor extends NotificationListenerService {
@Override
public void onCreate() {
//getActiveNotifications();
super.onCreate();
} @Override
public IBinder onBind(Intent intent) {
getActiveNotifications();
return super.onBind(intent);
} }

找到NotificationListenerService中的getActiveNotifications方法实现,代码例如以下:

public StatusBarNotification[] getActiveNotifications() {
try {
//getActiveNotifications成功运行的两个关键点:
//1.getNotificationInterface方法返回正常
//2.mWrapper对象不为null
return getNotificationInterface().getActiveNotificationsFromListener(mWrapper);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
return null;
} //通过查看能够知道。getNotificationInterface没有问题。假设为null会进行初始化
private final INotificationManager getNotificationInterface() {
if (mNoMan == null) {
mNoMan = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
}
return mNoMan;
} //假设mWrapper为null则进行初始化
@Override
public IBinder onBind(Intent intent) {
if (mWrapper == null) {
mWrapper = new INotificationListenerWrapper();
}
return mWrapper;
}

通过上面的代码能够知道getActiveNotifications方法调用失败的原因:

1. service的生命周期会先从onCraete->onBind逐步运行;

2. 此时调用getActiveNotifications方法会使用NotificationListenerService中的mWrapper对象;

3. mWrapper对象必须在NotificationMonitor完毕super.onBind方法之后才会初始化;

综上所述。当在onCreate或者onBind方法中使用getActiveNotifications方法时。会导致mWrapper没有初始化,即mWrapper == null。解决方式能够在onCreate或者onBind方法中使用handler异步调用getActiveNotification方法,详细可參考《Android
4.4 KitKat NotificationManagerService使用具体解释与原理分析(一)__使用具体解释》

NotificationListenerService失效

假设NotificationMonitor在onCreate或onBind方法中出现crash。则该NotificationMonitor已经失效。就算改动了NotificationMonitor的代码不会再crash,但NotificationMonitor还是不能收到onNotificationPosted和onNotificationRemoved回调。除非重新启动手机。

这个问题是google设计上的缺陷导致,出现NotificationListenerService失效的必要条件: 在NotificationMonitor的onCreate或者onBind中出现异常,导致service crash。也就是说service还没有全然启动的情况下出现了异常导致退出。

这里须要回到NotificationManagerService中,NotificationListenerService的注冊方法registerListenerService中:

private void registerListenerService(final ComponentName name, final int userid) {
//servicesBindingTag能够理解为须要启动的service的标签
final String servicesBindingTag = name.toString() + "/" + userid;
//假设mServicesBinding中已经包括正在处理的service则直接return退出
if (mServicesBinding.contains(servicesBindingTag)) {
// stop registering this thing already! we're working on it
return;
}
//将准备启动的service标签加入到mServicesBinding中
mServicesBinding.add(servicesBindingTag); //... ...省略
//使用bindServiceAsUser启动service
Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
intent.setComponent(name); intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
R.string.notification_listener_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0)); try {
if (DBG) Slog.v(TAG, "binding: " + intent);
if (!mContext.bindServiceAsUser(intent,
new ServiceConnection() {
INotificationListener mListener;
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mNotificationList) {
//服务成功启动之后删除标签
mServicesBinding.remove(servicesBindingTag);
try {
mListener = INotificationListener.Stub.asInterface(service);
NotificationListenerInfo info = new NotificationListenerInfo(
mListener, name, userid, this);
service.linkToDeath(info, 0);
mListeners.add(info);
} catch (RemoteException e) {
// already dead
}
}
} @Override
public void onServiceDisconnected(ComponentName name) {
Slog.v(TAG, "notification listener connection lost: " + name);
}
},
Context.BIND_AUTO_CREATE,
new UserHandle(userid)))
{
//绑定服务失败后删除标签
mServicesBinding.remove(servicesBindingTag);
Slog.w(TAG, "Unable to bind listener service: " + intent);
return;
}
} catch (SecurityException ex) {
Slog.e(TAG, "Unable to bind listener service: " + intent, ex);
return;
}
}
}

当调用registerListenerService方法时,使用了一个mServicesBinding的ArrayList<String>用来记录当前正在启动的服务。

在启动之前会推断当前service是否在mServicesBinding之中。假设是则表明正在运行bindServiceAsUser操作。直接退出,否则就继续运行bindServiceAsUser流程。

调用bindServiceAsUser之前会在mServicesBinding中会加入标签,当连接成功之后也就是onServiceConnected返回后,以及绑定失败后会在mServicesBinding中删除标签。

google这样设计的目的可能是为了避免同一个service多次启动。因此在运行bindServiceAsUser之前就打上标签,当处理完毕之后(onServiceConnected回调)就删掉这个标签,表明这个service 绑定完毕。

可是,假设运行bindServiceAsUser之后,NotificationMonitor在onCreate或者onBind的时候crash了。也就是NotificationMonitor还没有完毕启动,因此就不会去调用onServiceConnected方法,并终于导致不会调用
mServicesBinding.remove(servicesBindingTag)方法,从而使得NotificationMonitor的标签被一致记录在mServicesBinding中。那么当下一次想再次注冊该服务的时候,系统发现该服务已经在mServicesBinding中了,所以直接return,后面的bindServiceAsUser就不会被调用了。

尽管代码已经更新。但service无法正常启动,那么onNotificationPosted和onNotificationRemoved的回调自然就无法使用。此时的解决的方法就仅仅能重新启动手机,清空mServicesBinding的值。

总结

NotificationListenerService在系统通知获取的流程中,自身并没有启动,而是起到了一个代理的作用。每个继承自NotificationListenerService的类,当系统通知变化后终于都会收到onNotificationPosted和onNotificationRemoved的回调。

bindService方法的返回与service是否成功启动无关,因此才会导致NotificationListenerService失效。

最后再看一下整个NotificationListenerService的关系类图:

图 7 NLS关系类图

文中图片资源,免积分下载:戳这里

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

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

    概况 Android在4.3的版本号中(即API 18)增加了NotificationListenerService,依据SDK的描写叙述(AndroidDeveloper)能够知道,当系统收到新的通 ...

  2. Android 5.0 怎样正确启用isLoggable(二)__原理分析

    前置文章 <Android 5.0 怎样正确启用isLoggable(一)__使用具体解释> 概要 在上文<Android 5.0 怎样正确启用isLoggable(一)__使用具体 ...

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

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

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

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

  5. Android 4.4 KitKat 新特性

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

  6. 对Android项目中的文件夹进行解释

    对Android项目中的文件夹进行解释: · src:里面存放的是Activity程序,或者是以后的其他组件,在此文件夹之中建立类的时候一定要注意,包名称不能是一级. · gen:此文件夹中的内容是自 ...

  7. Android 4.4 KitKat, the browser and the Chrome WebView

    Having V8 as the JavaScript engine for the new web view, the JavaScript performance if much better, ...

  8. 让你的短信应用迎接Android 4.4(KitKat)

    原文地址:Getting Your SMS Apps Ready for KitKat 发送和接收短信是手机最基本的功能,很多的开发者也开发了很多成功的应用来增强Android这一方面的体验.你们当中 ...

  9. Using 1.7 requires compiling with Android 4.4 (KitKat); currently using API 10

    今天编译一个project,我设置为api 14,可是编译报错: Using 1.7 requires compiling with Android 4.4 (KitKat); currently u ...

随机推荐

  1. 洛谷 1052 dp 状态压缩

    洛谷1052 dp 状态压缩 传送门 (https://www.luogu.org/problem/show?pid=1052#sub) 做完这道题之后,感觉涨了好多见识,以前做的好多状压题目都是将一 ...

  2. 【LeetCode-面试算法经典-Java实现】【145-Binary Tree Postorder Traversal(二叉树非递归后序遍历)】

    [145-Binary Tree Postorder Traversal(二叉树非递归后序遍历)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given a bin ...

  3. oracle 时间戳TIMESTAMP

    //数据库 UPDATETIMESTAMP TIMESTAMP(6) //dto /** 更新时间戳 **/ private String updatetimestamp; //dao //插入操作 ...

  4. CNN tflearn处理mnist图像识别代码解说——conv_2d参数解释,整个网络的训练,主要就是为了学那个卷积核啊。

    官方参数解释: Convolution 2D tflearn.layers.conv.conv_2d (incoming, nb_filter, filter_size, strides=1, pad ...

  5. COGS 2479 奇怪的姿势卡♂过去 (bitset+折半)

    思路: 此题显然是CDQ套CDQ套树套树 (然而我懒) 想用一种奇怪的姿势卡过去 就出现了以下解法 5w*5w/8的bitset hiahiahia 但是空间会爆怎么办啊- 折半~ 变成5w*2.5w ...

  6. php查询字符串是否存在 strpos

    /*** 查询字符是否存在于某字符串** @param $haystack 字符串* @param $needle 要查找的字符* @return bool*/function str_exists( ...

  7. Reactive programming-文章解析

    数据源(信息源):静态的数组.动态的流: In computing, reactive programming is a declarative programming paradigm concer ...

  8. eval-Evaluation

    eval is a function which evaluates a string as though it were an expression and returns a result; in ...

  9. Linux grep 筛选语句

    1. 同时满足多个条件 cat logs.log |grep 123|grep 'abc'|more      --查询logs.log中同时满足123和abc的句子 2. 满足任意一个条件 cat ...

  10. OSI概述问答

    1.    网络中体系结构的七层.四层.五层是怎么回事? OSI(Open System Interconnection)开放系统互连参考模型的七层协议体系结构:概念清楚,理论比较完整,但既复杂又不用 ...