NotificationManagerService使用详解与原理分析(二)

2015-05-12  来源:本站原创  分类:Android  人气:265 

前置文章:

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

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

概况

在上一篇文章《Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解》中详细介绍了NotificationListenerService的使用方法,以及在使用过程中遇到的问题和规避方案。本文主要分析NotificationListenerService实现原理,以及详细分析在上一篇文章中提到的相关问题和产生的根本原因。在原理分析前,先看看NotificationListenerService涉及到的类以及基本作用,如图1所示:

NotificationManagerService使用详解与原理分析(二)

图 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代码如下:

[java] view plaincopy

  1. void rebindListenerServices() {
  2. final int currentUser = ActivityManager.getCurrentUser();
  3. //获取系统中哪些应用开启了Notification access
  4. String flat = Settings.Secure.getStringForUser(
  5. mContext.getContentResolver(),
  6. Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
  7. currentUser);
  8. NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
  9. final ArrayList<ComponentName> toAdd;
  10. synchronized (mNotificationList) {
  11. // unbind and remove all existing listeners
  12. toRemove = mListeners.toArray(toRemove);
  13. toAdd = new ArrayList<ComponentName>();
  14. final HashSet<ComponentName> newEnabled = new HashSet<ComponentName>();
  15. final HashSet<String> newPackages = new HashSet<String>();
  16. // decode the list of components
  17. if (flat != null) {
  18. String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
  19. for (int i=0; i<components.length; i++) {
  20. final ComponentName component
  21. = ComponentName.unflattenFromString(components[i]);
  22. if (component != null) {
  23. newEnabled.add(component);
  24. toAdd.add(component);
  25. newPackages.add(component.getPackageName());
  26. }
  27. }
  28. mEnabledListenersForCurrentUser = newEnabled;
  29. mEnabledListenerPackageNames = newPackages;
  30. }
  31. }
  32. //对所有NotificationListenerService全部unbindService操作
  33. for (NotificationListenerInfo info : toRemove) {
  34. final ComponentName component = info.component;
  35. final int oldUser = info.userid;
  36. Slog.v(TAG, "disabling notification listener for user " + oldUser + ": " + component);
  37. unregisterListenerService(component, info.userid);
  38. }
  39. //对所有NotificationListenerService进行bindService操作
  40. final int N = toAdd.size();
  41. for (int i=0; i<N; i++) {
  42. final ComponentName component = toAdd.get(i);
  43. Slog.v(TAG, "enabling notification listener for user " + currentUser + ": "
  44. + component);
  45. registerListenerService(component, currentUser);
  46. }
  47. }

在该方法中将获取系统中所有NotificationListenerService,并进行registerListenerService操作,代码如下:

[java] view plaincopy

  1. private void registerListenerService(final ComponentName name, final int userid) {
  2. //... ...省略
  3. Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
  4. intent.setComponent(name);
  5. intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
  6. R.string.notification_listener_binding_label);
  7. intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
  8. mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0));
  9. try {
  10. if (DBG) Slog.v(TAG, "binding: " + intent);
  11. //使用bindService启动NotificationListenerService
  12. if (!mContext.bindServiceAsUser(intent,
  13. new ServiceConnection() {
  14. INotificationListener mListener;
  15. @Override
  16. public void onServiceConnected(ComponentName name, IBinder service) {
  17. synchronized (mNotificationList) {
  18. mServicesBinding.remove(servicesBindingTag);
  19. try {
  20. mListener = INotificationListener.Stub.asInterface(service);
  21. NotificationListenerInfo info = new NotificationListenerInfo(
  22. mListener, name, userid, this);
  23. service.linkToDeath(info, 0);
  24. //service启动成功之后将相关信息添加到mListeners列表中,后续通过该列表触发回调
  25. mListeners.add(info);
  26. } catch (RemoteException e) {
  27. // already dead
  28. }
  29. }
  30. }
  31. @Override
  32. public void onServiceDisconnected(ComponentName name) {
  33. Slog.v(TAG, "notification listener connection lost: " + name);
  34. }
  35. },
  36. Context.BIND_AUTO_CREATE,
  37. new UserHandle(userid)))
  38. //... ...省略
  39. }
  40. }

整个启动流程如图2所示:

NotificationManagerService使用详解与原理分析(二)

图 2 NLS开机启动时序图

广播启动

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

[java] view plaincopy

  1. Intent.ACTION_PACKAGE_ADDED
  2. Intent.ACTION_PACKAGE_REMOVED
  3. Intent.ACTION_PACKAGE_RESTARTED
  4. Intent.ACTION_PACKAGE_CHANGED
  5. Intent.ACTION_QUERY_PACKAGE_RESTART
  6. Intent.ACTION_USER_SWITCHED

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

NotificationManagerService使用详解与原理分析(二)

图 3 NLS广播启动

数据库变更启动

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

NotificationManagerService使用详解与原理分析(二)

图 4 NLS数据库变更启动

NotificationListenerService启动小结

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

NotificationListenerService调用流程

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

新增通知

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

[java] view plaincopy

  1. service.enqueueNotificationWithTag(......)

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

[java] view plaincopy

  1. private void notifyPostedLocked(NotificationRecord n) {
  2. final StatusBarNotification sbn = n.sbn.clone();
  3. //这里触发mListeners中所有的NotificationListenerInfo回调
  4. for (final NotificationListenerInfo info : mListeners) {
  5. mHandler.post(new Runnable() {
  6. @Override
  7. public void run() {
  8. info.notifyPostedIfUserMatch(sbn);
  9. }});
  10. }
  11. }

到这里就开始准备回调了,因为前面通知已经组装完毕准备显示到状态栏了,之后就需要将相关的通知消息告诉所有监听者。继续看到notifyPostedIfUserMatch方法:

[java] view plaincopy

  1. public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
  2. //... ...省略
  3. try {
  4. listener.onNotificationPosted(sbn);
  5. } catch (RemoteException ex) {
  6. Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
  7. }
  8. }

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

[java] view plaincopy

  1. public void onServiceConnected(ComponentName name, IBinder service) {
  2. synchronized (mNotificationList) {
  3. mServicesBinding.remove(servicesBindingTag);
  4. try {
  5. //mListener就是NotificationListenerService子类的对象
  6. //service是INotificationListenerWrapper的对象,INotificationListenerWrapper
  7. //继承自INotificationListener.Stub,是NotificationListenerService的内部类
  8. mListener = INotificationListener.Stub.asInterface(service);
  9. //使用mListener对象生成对应的NotificationListenerInfo对象
  10. NotificationListenerInfo info = new NotificationListenerInfo(
  11. mListener, name, userid, this);
  12. service.linkToDeath(info, 0);
  13. mListeners.add(info);
  14. } catch (RemoteException e) {
  15. // already dead
  16. }
  17. }
  18. }

也就是说在NotificationListenerService启动并连接的时候,将binder对象保存到了NotificationListenerInfo中。这里就得看看NotificationListenerService的onBind方法返回了,代码如下:

[java] view plaincopy

  1. @Override
  2. public IBinder onBind(Intent intent) {
  3. if (mWrapper == null) {
  4. mWrapper = new INotificationListenerWrapper();
  5. }
  6. //这里返回的是INotificationListenerWrapper对象
  7. return mWrapper;
  8. }
  9. private class INotificationListenerWrapper extends INotificationListener.Stub {
  10. @Override
  11. public void onNotificationPosted(StatusBarNotification sbn) {
  12. try {
  13. //onNotificationPosted是抽象方法之一
  14. NotificationListenerService.this.onNotificationPosted(sbn);
  15. } catch (Throwable t) {
  16. Log.w(TAG, "Error running onNotificationPosted", t);
  17. }
  18. }
  19. @Override
  20. public void onNotificationRemoved(StatusBarNotification sbn) {
  21. try {
  22. //onNotificationRemoved是另一个抽象方法
  23. NotificationListenerService.this.onNotificationRemoved(sbn);
  24. } catch (Throwable t) {
  25. Log.w(TAG, "Error running onNotificationRemoved", t);
  26. }
  27. }
  28. }

通过以上代码可以知道,当在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的子类。整个过程的调用时序图如下:

NotificationManagerService使用详解与原理分析(二)

图 5 onNotificationPosted触发流程

删除通知

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

NotificationManagerService使用详解与原理分析(二)

图 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中可以看到如下初始化代码:

[java] view plaincopy

  1. //... ...省略
  2. mNotificationAccess = findPreference(KEY_NOTIFICATION_ACCESS);
  3. if (mNotificationAccess != null) {
  4. final int total = NotificationAccessSettings.getListenersCount(mPM);
  5. if (total == 0) {
  6. if (deviceAdminCategory != null) {
  7. //如果系统中没有安装使用NLS的应用则删除显示
  8. deviceAdminCategory.removePreference(mNotificationAccess);
  9. }
  10. } else {
  11. //获取系统中有多少启动了Notification access的应用
  12. final int n = getNumEnabledNotificationListeners();
  13. //根据启用的数量显示不同的Summary
  14. if (n == 0) {
  15. mNotificationAccess.setSummary(getResources().getString(
  16. R.string.manage_notification_access_summary_zero));
  17. } else {
  18. mNotificationAccess.setSummary(String.format(getResources().getQuantityString(
  19. R.plurals.manage_notification_access_summary_nonzero,
  20. n, n)));
  21. }
  22. }
  23. }
  24. //... ...省略

getActiveNotifications()方法返回为null

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

[java] view plaincopy

  1. public class NotificationMonitor extends NotificationListenerService {
  2. @Override
  3. public void onCreate() {
  4. //getActiveNotifications();
  5. super.onCreate();
  6. }
  7. @Override
  8. public IBinder onBind(Intent intent) {
  9. getActiveNotifications();
  10. return super.onBind(intent);
  11. }
  12. }

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

[java] view plaincopy

  1. public StatusBarNotification[] getActiveNotifications() {
  2. try {
  3. //getActiveNotifications成功执行的两个关键点:
  4. //1.getNotificationInterface方法返回正常
  5. //2.mWrapper对象不为null
  6. return getNotificationInterface().getActiveNotificationsFromListener(mWrapper);
  7. } catch (android.os.RemoteException ex) {
  8. Log.v(TAG, "Unable to contact notification manager", ex);
  9. }
  10. return null;
  11. }
  12. //通过查看可以知道,getNotificationInterface没有问题,如果为null会进行初始化
  13. private final INotificationManager getNotificationInterface() {
  14. if (mNoMan == null) {
  15. mNoMan = INotificationManager.Stub.asInterface(
  16. ServiceManager.getService(Context.NOTIFICATION_SERVICE));
  17. }
  18. return mNoMan;
  19. }
  20. //如果mWrapper为null则进行初始化
  21. @Override
  22. public IBinder onBind(Intent intent) {
  23. if (mWrapper == null) {
  24. mWrapper = new INotificationListenerWrapper();
  25. }
  26. return mWrapper;
  27. }

通过上面的代码可以知道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中:

[java] view plaincopy

  1. private void registerListenerService(final ComponentName name, final int userid) {
  2. //servicesBindingTag可以理解为需要启动的service的标签
  3. final String servicesBindingTag = name.toString() + "/" + userid;
  4. //如果mServicesBinding中已经包含正在处理的service则直接return退出
  5. if (mServicesBinding.contains(servicesBindingTag)) {
  6. // stop registering this thing already! we're working on it
  7. return;
  8. }
  9. //将准备启动的service标签添加到mServicesBinding中
  10. mServicesBinding.add(servicesBindingTag);
  11. //... ...省略
  12. //使用bindServiceAsUser启动service
  13. Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
  14. intent.setComponent(name);
  15. intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
  16. R.string.notification_listener_binding_label);
  17. intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
  18. mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0));
  19. try {
  20. if (DBG) Slog.v(TAG, "binding: " + intent);
  21. if (!mContext.bindServiceAsUser(intent,
  22. new ServiceConnection() {
  23. INotificationListener mListener;
  24. @Override
  25. public void onServiceConnected(ComponentName name, IBinder service) {
  26. synchronized (mNotificationList) {
  27. //服务成功启动之后删除标签
  28. mServicesBinding.remove(servicesBindingTag);
  29. try {
  30. mListener = INotificationListener.Stub.asInterface(service);
  31. NotificationListenerInfo info = new NotificationListenerInfo(
  32. mListener, name, userid, this);
  33. service.linkToDeath(info, 0);
  34. mListeners.add(info);
  35. } catch (RemoteException e) {
  36. // already dead
  37. }
  38. }
  39. }
  40. @Override
  41. public void onServiceDisconnected(ComponentName name) {
  42. Slog.v(TAG, "notification listener connection lost: " + name);
  43. }
  44. },
  45. Context.BIND_AUTO_CREATE,
  46. new UserHandle(userid)))
  47. {
  48. //绑定服务失败后删除标签
  49. mServicesBinding.remove(servicesBindingTag);
  50. Slog.w(TAG, "Unable to bind listener service: " + intent);
  51. return;
  52. }
  53. } catch (SecurityException ex) {
  54. Slog.e(TAG, "Unable to bind listener service: " + intent, ex);
  55. return;
  56. }
  57. }
  58. }

当调用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的关系类图:

NotificationManagerService使用详解与原理分析(二)

图 7 NLS关系类图

示例代码:

https://github.com/yihongyuelan/NotificationListenerServiceDemo

相关文章
  • NotificationManagerService使用详解与原理分析(二) 2015-05-12

    前置文章: <Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解> 转载请务必注明出处:http://blog.csdn.net/yihongyuelan 概况 在上一篇文章<Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解>中详细介绍了NotificationListenerService的使用方法,以及在使用过程中遇到的问题和

  • NotificationManagerService使用详解与原理分析(一) 2015-05-12

    概况 Android在4.3的版本中(即API 18)加入了NotificationListenerService,根据SDK的描述(AndroidDeveloper)可以知道,当系统收到新的通知或者通知被删除时,会触发NotificationListenerService的回调方法.同时在Android 4.4 中新增了Notification.extras 字段,也就是说可以使用NotificationListenerService获取系统通知具体信息,这在以前是需要用反射来实现的. 转载请

  • (二)代理模式详解(包含原理详解) 2015-01-21

    LZ不希望写的东西与网络上的资料千篇一律,所以这一系列不会像很多典型文章一章,只是列出这个模式的定义以及一堆适用的情况,然后就是一堆这个模式的各个角色,对于这种罗列LZ并不反对,相比之下会比较清晰,但是如果脱离了实际,就会导致看的人特别是初学者觉得设计模式很陌生很遥远. LZ并不反对这种教学式的标准模式,但说实话,LZ本人看这种帖子从来都感觉收获不大,看一遍看一遍,到现在都没记住那些各个适用的情况与一堆乱七八糟的角色. 所以LZ探讨代理模式,不会再按这个步骤进行,而是跟着自己的思维进行. 首先代

  • apache日志文件详解和实用分析命令 2015-02-04

    这篇文章主要介绍了apache日志文件每条数据的请意义,以及一些实用日志分析命令,需要的朋友可以参考下 一.日志分析 如果apache的安装时采用默认的配置,那么在/logs目录下就会生成两个文件,分别是access_log和error_log 1).access_log access_log为访问日志,记录所有对apache服务器进行请求的访问,它的位置和内容由CustomLog指令控制,LogFormat指令可以用来简化该日志的内容和格式 例如,我的其中一台服务器配置如下: CustomLo

  • [XML系列]详解Digester处理XML(二) 2011-10-25

    通过XML指定规则 在前面的内容中,我们用程序代码的方式指定模式和规则,这些模式和规则都是在编译的时候就已经确定,虽然从概念上来讲比较简单,但却不能说尽善尽美:Digester框架的总体目标是在运行时识别和处理各种数据结构,但如果我们用编程的方法指定模式和规则,则所有行为在编译时已经固定!如果Java源程序中包含了大量固定的字符串,通常意味着程序在执行某些配置操作,这部分操作可以被(或许是应该被)延迟到运行时进行. org.apache.commons.digester.xmlrules包解决了

  • Android事件详解--拖放事件DragEvent(二) 2014-06-24

    1.Android拖放框架的作用? 利用Android的拖放框架,可以让用户用拖放手势把一个View中的数据移到当前layout内的另一个View中去. 2.拖放框架的内容? 1)拖放事件类 2)拖放监听器 3)其他辅助的方法和类 3.拖放过程? 拖放过程有四个基本步骤: 1)启动 为了响应用户开始拖动的手势,需要调用View的startDrag方法来通知系统.startDrag方法的参数需要指定所拖动的数据.元数据和绘制拖动阴影的回调方法. 作为响应,系统首先通过回调来获取拖动阴影,然后在设备

  • iptables详解 2013-02-22

    iptables详解 Iptables原理 现在防火墙主要分以下三种类型: 包过滤.应用代理.状态检测 包过滤防火墙:现在静态包过滤防火墙市面上已经看不到了,取而代之的是动态包过滤技术的防火墙哈~ 代理防火墙:因一些特殊的报文攻击可以轻松突破包过滤防火墙的保护,比如大家知道的SYN攻击.ICMP洪水攻击,所以以代理服务器作为专门为用户保密或者突破访问限制的数据转发通道的应用代理防火墙出现了哈~其使用了一种应用协议分析的新技术. 状态检测防火墙:其基于动态包过滤技术发展而来,加入了一种状态检测的模

  • mysql免安装版配置步骤详解分享 2015-01-13

    这篇文章主要介绍了mysql免安装版配置步骤详解,提供了二个网友的安装方法,大家可以参考使用 1.准备工作 下载mysql的最新免安装版本mysql-noinstall-5.1.53-win32.zip,解压缩到相关目录,如:d:\\ mysql-noinstall-5.1.53-win32.这个就是mysql的根目录了. 2.配置 在根目录下有几个文件如下: my-small.ini (这是针对一个小内存(〈= 64MB)的系统,MySQL 只会被时不时地用一下,很重要的是 mysqld 守护

  • PHP 采集大全 采集原理分析 禁用采集 各种采集方法详解 采集的攻于防 采集性能 应用协议分析 2014-03-11

    //py by http://my.codeweblog.com/cart 做了N年的PHP,采集了N家数据,由初学者菜鸟,到现在的熟手,采集天猫.淘宝.腾讯.京东.敦煌.Lightinthebox.大龙.zencart.magento.prestashop.opencart.xcart.踏踏....对采集颇有诸多的理解. 现在给大家分析下,如有误,请指出. 我能想到的常用采集方法: 1. file 支持应用层协议,返回的数据是以数组形式返回,需要开启allow_url_fopen. 长处:擅长

  • php urlencode()与urldecode()函数字符编码原理详解 2014-05-24

    中文字符编码研究系列第五期,详解 urlencode()与urldecode()函数字符编码原理,两个函数分别用于编码 URL 字符串和解码已编码的 URL 字符串,实现对中文字符的编码 其原理就是把中文字符转换为十六进制并按某种规则进行字符串组合,实现字符的编码与解编码,保证URL数据传递过程中字符的完整性和兼容性,主要讨论中文字符的编码情况. 一,FireFox浏览器编码中文字符 在Firefox浏览器下如果输入中文字符,将会自动实现URL编码,如下 按下Enter键前 按下Enter键后

  • 基于一个应用程序多线程误用的分析详解 2014-11-26

    本篇文章是对一个应用程序多线程的误用进行了详细的分析介绍,需要的朋友参考下 一.需求和初步实现很简单的一个windows服务:客户端连接邮件服务器,下载邮件(含附件)并保存为.eml格式,保存成功后删除服务器上的邮件.实现的伪代码大致如下: public void Process() { var recordCount = 1000;//每次取出邮件记录数 while (true) { using (var client = new Pop3Client()) { //1.建立连接,并进行身份认

  • 企业网站架构之Nginx详解原理以及工作模块:源码Lnmp架构 2014-08-11

    Nginx详解及lnmp环境架构 一.Nginx详解以及优点 在当前互联网环境下,一般高端的服务前端都采用nginx作为web前端,而更多的都是采用lnmp架构,真正的后端服务器才会采用apache. 为什么这么做,要取决于nginx和apache两者之间的优缺性.: nginx与apache相比有以下优势:在性能上,nginx占用很少的系统资源,能支持更多的并发链接,达到更高的访问率;在功能上,Nginx是优秀的代理服务器和负载均衡器;在安装配置上,简单灵活. nginx模块基本都是静态编译,

  • Linux 日志分析工具之awstats详解 2014-10-13

    一.前言 二.awstats 简介 三.awstats 特点 四.awstats 运行原理 五.awstats 安装与配置详解 六.awstats 执行日志分析 七.awstats 进行多站点日志分析 八.awstats 问题汇总 IP 地址国家.区域显示问题 中文乱码问题 九.awstats 总结 注,操作系统 CentOS 6.4 x86_64,软件版本 awstats 7.2(稳定版),软件下载 http://sourceforge.net/projects/awstats/ . 一.前言

  • jQuery中extend函数的实现原理详解 2013-11-14

    这篇文章主要介绍了jQuery中extend函数的实现原理详解,extend()函数用于jQuery插件的开发,本文主要分析它的实现原理,需要的朋友可以参考下 extend()是jQuery中一个重要的函数,作用是实现对对象的扩展, 它经常用于jQuery插件的开发,jQuery内部也使用它来扩展属性方法,如上篇文章中讲到的noConflict方法,就是用extend方法来扩展的. 在jQuery的API手册中,我们看到,extend实际上是挂载在jQuery和jQuery.fn上的两个不同方法

  • Windows+OpenLDAP+MySQL配置及使用详解(二)--数据库初始化 2011-07-03

    现在我们了解一下数据库端的原理. 如果已经按照上一篇文章:"Windows+OpenLDAP+MySQL配置及使用详解(一)--基础配置"配置成功,在了解本篇文章内容之前,我们先将数据库中的测试数据清除掉: 1.删除原数据库中所有表格: 2.在下载的源码包中找到servers/slapd/back-sql/rdbms_depend/mysql目录,执行脚本backsql_create.sql . 本文将通过一个例子讲解数据库结构.假设我们的LDAP服务端目录结构如下: 图一 首先给大家

  • 负载均衡原理与实践详解 第一篇 2012-04-05

    系列文章: 负载均衡原理与实践详解 第一篇(重新整理) 负载均衡原理与实践详解 第二篇(重新整理) 负载均衡原理与实践详解 第三篇 服务器负载均衡的基本概念-网络基础 负载均衡在服务器和网络的世界中并不是一个新的概念,许多产品都能够提供不同类型的负载均衡解决方案.比如,路由器能够在不同的路径之间分配流量到达相同的目的地,在不同的网络资源中平衡它们的负担.另一方面,一个服务器负载均衡设备,能在多台服务器之间分发流量. 最初,负载均衡设备只是满足简单的负载均衡需求,而如今这些产品已得到迅速的发展,能

  • Struts2学习(二):struts2配置详解! 2013-07-19

    Struts2框架按照以下搜索顺序加载Struts2常量: 1.struts-default.xml---该文件保存在struts2-core-2.x.x.jar文件中 2.struts-pluugin.xml---在struts2一些插件jar包里,比如struts2-xxx-plugin-2.3.x.x.jar 3.struts.xml---Web应用默认的Struts2配置文件 4.struts.properties---Web应用默认的Struts2配置文件 5.web.xml--Web

  • 酷炫开源项目cardsui-for-android-超详细源码分析,详解所用特效是如何实现的 2014-10-22

    cardsui-for-android的下载地址https://github.com/Androguide/cardsui-for-android 以下是我截取的2个图片,可以自定义成Card形式的View,布局可以自己设定.点击露出来的部分可以使点击的Card滑落到下面,也可以左右滑动删除Card.效果非常好 这篇文章主要写下通过源码分析一下几个地方是怎么实现的. Card的View和布局 相互叠层的card 点击翻转下滑 左右移动删除条目 可以根据需求自行决定观看,哈哈 从git下载导入项目

  • PHP学习笔记(二):变量详解 2013-10-17

    这篇文章主要介绍了PHP学习笔记(二):变量详解,本文讲解了PHP变量简介.变量数据类型.常用函数.变量声明方法等内容,需要的朋友可以参考下 一.PHP 变量简介 1.语法 //PHP是弱类型语言,变量类型由存储的值决定 //强类型语言:int a = 1 $变量名 = 值 2.命名规则 1).不能数字开头 2).不能使用PHP运算符(+-x/%&) 3).可以使用PHP关键字 4).区分大小写(php只有变量.常量区分大小写) 5).驼峰命名法:aaBbCc(第一个单词首字母小写) 3.可变变

  • php 分页原理详解 2014-02-02

    分页原理详解,其实各个语言的都差不多,主要是程序跟数据库的表达方式不一样. 在看本文之前,请确保你已掌握了PHP的一些知识以及MYSQL的查询操作基础哦. 作为一个Web程序,经常要和不计其数的数据打交道,比如会员的数据,文章数据,假如只有几十个会员那很好办,在一页显示就可以了,可是假如你的网站是几千甚至几十万会员的话,如果都在一页打开的话无论对浏览器还是观看者都是一种折磨,而且如果数据上亿,从数据库里查询一次的话,对服务器的压力是很大的,这不是正确的方法. 相信每个学习PHP的新手都会对分页这