在Android系统中,广播(Broadcast)是在组件之间传播数据(Intent)的一种机制;这些组件甚至是可以位于不同的进程中,这样它就像Binder机制一样,起到进程间通信的作用;本文通过一个简单的例子来学习Android系统的广播机制,为后续分析广播机制的源代码作准备。

在Android系统中,为什么需要广播机制呢?广播机制,本质上它就是一种组件间的通信方式,如果是两个组件位于不同的进程当中,那么可以用Binder机制来实现,如果两个组件是在同一个进程中,那么它们之间可以用来通信的方式就更多了,这样看来,广播机制似乎是多余的。然而,广播机制却是不可替代的,它和Binder机制不一样的地方在于,广播的发送者和接收者事先是不需要知道对方的存在的,这样带来的好处便是,系统的各个组件可以松耦合地组织在一起,这样系统就具有高度的可扩展性,容易与其它系统进行集成。

在软件工程中,是非常强调模块之间的高内聚低耦合性的,不然的话,随着系统越来越庞大,就会面临着越来越难维护的风险,最后导致整个项目的失败。Android应用程序的组织方式,可以说是把这种高内聚低耦合性的思想贯彻得非常透彻,在任何一个Activity中,都可以使用一个简单的Intent,通过startActivity或者startService,就可以把另外一个Activity或者Service启动起来为它服务,而且它根本上不依赖这个Activity或者Service的实现,只需要知道它的字符串形式的名字即可,而广播机制更绝,它连接收者的名字都不需要知道。

不过话又说回来,广播机制在Android系统中,也不算是什么创新的东西。如果读者了解J2EE或者COM,就会知道,在J2EE中,提供了消息驱动Bean(Message-Driven Bean),用来实现应用程序各个组件之间的消息传递;而在COM中,提供了连接点(Connection Point)的概念,也是用来在应用程序各个组间间进行消息传递。无论是J2EE中的消息驱动Bean,还是COM中的连接点,或者Android系统的广播机制,它们的实现机理都是消息发布/订阅模式的事件驱动模型,消息的生产者发布事件,而使用者订阅感兴趣的事件。

废话说了一大堆,现在开始进入主题了,和前面的文章一样,我们通过具体的例子来介绍Android系统的广播机制。在这个例子中,有一个Service,它在另外一个线程中实现了一个计数器服务,每隔一秒钟就自动加1,然后将结果不断地反馈给应用程序中的界面线程,而界面线程中的Activity在得到这个反馈后,就会把结果显示在界面上。为什么要把计数器服务放在另外一个线程中进行呢?我们可以把这个计数器服务想象成是一个耗时的计算型逻辑,如果放在界面线程中去实现,那么势必就会导致应用程序不能响应界面事件,最后导致应用程序产生ANR(Application Not Responding)问题。计数器线程为了把加1后的数字源源不断地反馈给界面线程,这时候就可以考虑使用广播机制了。

首先在Android源代码工程中创建一个Android应用程序工程,名字就称为Broadcast吧。关于如何获得Android源代码工程,请参考在Ubuntu上下载、编译和安装Android最新源代码一文;关于如何在Android源代码工程中创建应用程序工程,请参考在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务一文。这个应用程序工程定义了一个名为shy.luo.broadcast的package,这个例子的源代码主要就是实现在这里了。下面,将会逐一介绍这个package里面的文件。

首先,我们在src/shy/luo/broadcast/ICounterService.java文件中定义计数器的服务接口:

  1. package shy.luo.broadcast;
  2. public interface ICounterService {
  3. public void startCounter(int initVal);
  4. public void stopCounter();
  5. }

这个接口很简单,它只有两个成员函数,分别用来启动和停止计数器;启动计数时,还可以指定计数器的初始值。

接着,我们来看一个应用程序的默认Activity的实现,在src/shy/luo/broadcast/MainActivity.java文件中:

  1. package shy.luo.broadcast;
  2. import android.app.Activity;
  3. import android.content.BroadcastReceiver;
  4. import android.content.ComponentName;
  5. import android.content.Context;
  6. import android.content.Intent;
  7. import android.content.IntentFilter;
  8. import android.content.ServiceConnection;
  9. import android.os.Bundle;
  10. import android.os.IBinder;
  11. import android.util.Log;
  12. import android.view.View;
  13. import android.view.View.OnClickListener;
  14. import android.widget.Button;
  15. import android.widget.TextView;
  16. public class MainActivity extends Activity implements OnClickListener {
  17. private final static String LOG_TAG = "shy.luo.broadcast.MainActivity";
  18. private Button startButton = null;
  19. private Button stopButton = null;
  20. private TextView counterText = null;
  21. private ICounterService counterService = null;
  22. @Override
  23. public void onCreate(Bundle savedInstanceState) {
  24. super.onCreate(savedInstanceState);
  25. setContentView(R.layout.main);
  26. startButton = (Button)findViewById(R.id.button_start);
  27. stopButton = (Button)findViewById(R.id.button_stop);
  28. counterText = (TextView)findViewById(R.id.textview_counter);
  29. startButton.setOnClickListener(this);
  30. stopButton.setOnClickListener(this);
  31. startButton.setEnabled(true);
  32. stopButton.setEnabled(false);
  33. Intent bindIntent = new Intent(MainActivity.this, CounterService.class);
  34. bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
  35. Log.i(LOG_TAG, "Main Activity Created.");
  36. }
  37. @Override
  38. public void onResume() {
  39. super.onResume();
  40. IntentFilter counterActionFilter = new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION);
  41. registerReceiver(counterActionReceiver, counterActionFilter);
  42. }
  43. @Override
  44. public void onPause() {
  45. super.onPause();
  46. unregisterReceiver(counterActionReceiver);
  47. }
  48. @Override
  49. public void onDestroy() {
  50. super.onDestroy();
  51. unbindService(serviceConnection);
  52. }
  53. @Override
  54. public void onClick(View v) {
  55. if(v.equals(startButton)) {
  56. if(counterService != null) {
  57. counterService.startCounter(0);
  58. startButton.setEnabled(false);
  59. stopButton.setEnabled(true);
  60. }
  61. } else if(v.equals(stopButton)) {
  62. if(counterService != null) {
  63. counterService.stopCounter();
  64. startButton.setEnabled(true);
  65. stopButton.setEnabled(false);
  66. }
  67. }
  68. }
  69. private BroadcastReceiver counterActionReceiver = new BroadcastReceiver(){
  70. public void onReceive(Context context, Intent intent) {
  71. int counter = intent.getIntExtra(CounterService.COUNTER_VALUE, 0);
  72. String text = String.valueOf(counter);
  73. counterText.setText(text);
  74. Log.i(LOG_TAG, "Receive counter event");
  75. }
  76. };
  77. private ServiceConnection serviceConnection = new ServiceConnection() {
  78. public void onServiceConnected(ComponentName className, IBinder service) {
  79. counterService = ((CounterService.CounterBinder)service).getService();
  80. Log.i(LOG_TAG, "Counter Service Connected");
  81. }
  82. public void onServiceDisconnected(ComponentName className) {
  83. counterService = null;
  84. Log.i(LOG_TAG, "Counter Service Disconnected");
  85. }
  86. };
  87. }

MainActivity的实现也很简单,它在创建(onCreate)的时候,会调用bindService函数来把计数器服务(CounterService)启动起来,它的第二个参数serviceConnection是一个ServiceConnection实例。计数器服务启动起来后,系统会调用这个实例的onServiceConnected函数将一个Binder对象传回来,通过调用这个Binder对象的getService函数,就可以获得计数器服务接口。这里,把这个计数器服务接口保存在MainActivity的counterService成员变量中。同样,当我们调用unbindService停止计数器服务时,系统会调用这个实例的onServiceDisconnected函数告诉MainActivity,它与计数器服务的连接断开了。

注意,这里通过调用bindService函数来启动Service时,这个Service与启动它的Activity是位于同一个进程中,它不像我们在前面一篇文章Android系统在新进程中启动自定义服务过程(startService)的原理分析中所描述那样在新的进程中启动服务,后面我们再写一篇文章来分析bindService启动服务的过程。

在MainActivity的onResume函数中,通过调用registerReceiver函数注册了一个广播接收器counterActionReceiver,它是一个BroadcastReceiver实例,并且指定了这个广播接收器只对CounterService.BROADCAST_COUNTER_ACTION类型的广播感兴趣。当CounterService发出一个CounterService.BROADCAST_COUNTER_ACTION类型的广播时,系统就会把这个广播发送到counterActionReceiver实例的onReceiver函数中去。在onReceive函数中,从参数intent中取出计数器当前的值,显示在界面上。

MainActivity界面上有两个按钮,分别是Start Counter和Stop Counter按钮,点击前者开始计数,而点击后者则停止计数。

计数器服务CounterService实现在src/shy/luo/broadcast/CounterService.java文件中:

  1. package shy.luo.broadcast;
  2. import android.app.Service;
  3. import android.content.Intent;
  4. import android.os.AsyncTask;
  5. import android.os.Binder;
  6. import android.os.IBinder;
  7. import android.util.Log;
  8. public class CounterService extends Service implements ICounterService {
  9. private final static String LOG_TAG = "shy.luo.broadcast.CounterService";
  10. public final static String BROADCAST_COUNTER_ACTION = "shy.luo.broadcast.COUNTER_ACTION";
  11. public final static String COUNTER_VALUE = "shy.luo.broadcast.counter.value";
  12. private boolean stop = false;
  13. private final IBinder binder = new CounterBinder();
  14. public class CounterBinder extends Binder {
  15. public CounterService getService() {
  16. return CounterService.this;
  17. }
  18. }
  19. @Override
  20. public IBinder onBind(Intent intent) {
  21. return binder;
  22. }
  23. @Override
  24. public void onCreate() {
  25. super.onCreate();
  26. Log.i(LOG_TAG, "Counter Service Created.");
  27. }
  28. @Override
  29. public void onDestroy() {
  30. Log.i(LOG_TAG, "Counter Service Destroyed.");
  31. }
  32. public void startCounter(int initVal) {
  33. AsyncTask<Integer, Integer, Integer> task = new AsyncTask<Integer, Integer, Integer>() {
  34. @Override
  35. protected Integer doInBackground(Integer... vals) {
  36. Integer initCounter = vals[0];
  37. stop = false;
  38. while(!stop) {
  39. publishProgress(initCounter);
  40. try {
  41. Thread.sleep(1000);
  42. } catch (InterruptedException e) {
  43. e.printStackTrace();
  44. }
  45. initCounter++;
  46. }
  47. return initCounter;
  48. }
  49. @Override
  50. protected void onProgressUpdate(Integer... values) {
  51. super.onProgressUpdate(values);
  52. int counter = values[0];
  53. Intent intent = new Intent(BROADCAST_COUNTER_ACTION);
  54. intent.putExtra(COUNTER_VALUE, counter);
  55. sendBroadcast(intent);
  56. }
  57. @Override
  58. protected void onPostExecute(Integer val) {
  59. int counter = val;
  60. Intent intent = new Intent(BROADCAST_COUNTER_ACTION);
  61. intent.putExtra(COUNTER_VALUE, counter);
  62. sendBroadcast(intent);
  63. }
  64. };
  65. task.execute(0);
  66. }
  67. public void stopCounter() {
  68. stop = true;
  69. }
  70. }

这个计数器服务实现了ICounterService接口。当这个服务被binderService函数启动时,系统会调用它的onBind函数,这个函数返回一个Binder对象给系统。上面我们说到,当MainActivity调用bindService函数来启动计数器服务器时,系统会调用MainActivity的ServiceConnection实例serviceConnection的onServiceConnected函数通知MainActivity,这个服务已经连接上了,并且会通过这个函数传进来一个Binder远程对象,这个Binder远程对象就是来源于这里的onBind的返回值了。

函数onBind返回的Binder对象是一个自定义的CounterBinder实例,它实现了一个getService成员函数。当系统通知MainActivity,计数器服务已经启动起来并且连接成功后,并且将这个Binder对象传给MainActivity时,MainActivity就会把这个Binder对象强制转换为CounterBinder实例,然后调用它的getService函数获得服务接口。这样,MainActivity就通过这个Binder对象和CounterService关联起来了。

当MainActivity调用计数器服务接口的startCounter函数时,计数器服务并不是直接进入计数状态,而是通过使用异步任务(AsyncTask)在后台线程中进行计数。这里为什么要使用异步任务来在后台线程中进行计数呢?前面我们说过,这个计数过程是一个耗时的计算型逻辑,不能把它放在界面线程中进行,因为这里的CounterService启动时,并没有在新的进程中启动,它与MainActivity一样,运行在应用程序的界面线程中,因此,这里需要使用异步任务在在后台线程中进行计数。

异步任务AsyncTask的具体用法可以参考官方文档http://developer.android.com/reference/android/os/AsyncTask.html。它的大概用法是,当我们调用异步任务实例的execute(task.execute)方法时,当前调用线程就返回了,系统启动一个后台线程来执行这个异步任务实例的doInBackground函数,这个函数就是我们用来执行耗时计算的地方了,它会进入到一个循环中,每隔1秒钟就把计数器加1,然后进入休眠(Thread.sleep),醒过来,再重新这个计算过程。在计算的过程中,可以通过调用publishProgress函数来通知调用者当前计算的进度,好让调用者来更新界面,调用publishProgress函数的效果最终就是直入到这个异步任务实例的onProgressUpdate函数中,这里就可以把这个进度值以广播的形式(sendBroadcast)发送出去了,这里的进度值就定义为当前计数服务的计数值。

当MainActivity调用计数器服务接口的stopCounter函数时,会告诉函数doInBackground停止执行计数(stop = true),于是,函数doInBackground就退出计数循环,然后将最终计数结果返回了,返回的结果最后进入到onPostExecute函数中,这个函数同样通过广播的形式(sendBroadcast)把这个计数结果广播出去。

计算器服务就介绍到这里了,下面我们看看应用程序的配置文件AndroidManifest.xml:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="shy.luo.broadcast"
  4. android:versionCode="1"
  5. android:versionName="1.0">
  6. <application android:icon="@drawable/icon" android:label="@string/app_name">
  7. <activity android:name=".MainActivity"
  8. android:label="@string/app_name">
  9. <intent-filter>
  10. <action android:name="android.intent.action.MAIN" />
  11. <category android:name="android.intent.category.LAUNCHER" />
  12. </intent-filter>
  13. </activity>
  14. <service android:name=".CounterService"
  15. android:enabled="true">
  16. </service>
  17. </application>
  18. </manifest>

这个配置文件很简单,只是告诉系统,它有一个Activity和一个Service。

再来看MainActivity的界面文件,它定义在res/layout/main.xml文件中:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. android:gravity="center">
  7. <LinearLayout
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content"
  10. android:layout_marginBottom="10px"
  11. android:orientation="horizontal"
  12. android:gravity="center">
  13. <TextView
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:layout_marginRight="4px"
  17. android:gravity="center"
  18. android:text="@string/counter">
  19. </TextView>
  20. <TextView
  21. android:id="@+id/textview_counter"
  22. android:layout_width="wrap_content"
  23. android:layout_height="wrap_content"
  24. android:gravity="center"
  25. android:text="0">
  26. </TextView>
  27. </LinearLayout>
  28. <LinearLayout
  29. android:layout_width="fill_parent"
  30. android:layout_height="wrap_content"
  31. android:orientation="horizontal"
  32. android:gravity="center">
  33. <Button
  34. android:id="@+id/button_start"
  35. android:layout_width="wrap_content"
  36. android:layout_height="wrap_content"
  37. android:gravity="center"
  38. android:text="@string/start">
  39. </Button>
  40. <Button
  41. android:id="@+id/button_stop"
  42. android:layout_width="wrap_content"
  43. android:layout_height="wrap_content"
  44. android:gravity="center"
  45. android:text="@string/stop" >
  46. </Button>
  47. </LinearLayout>
  48. </LinearLayout>

这个界面配置文件也很简单,等一下我们在模拟器把这个应用程序启动起来后,就可以看到它的截图了。

应用程序用到的字符串资源文件位于res/values/strings.xml文件中:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <string name="app_name">Broadcast</string>
  4. <string name="counter">Counter: </string>
  5. <string name="start">Start Counter</string>
  6. <string name="stop">Stop Counter</string>
  7. </resources>

最后,我们还要在工程目录下放置一个编译脚本文件Android.mk:

  1. LOCAL_PATH:= $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE_TAGS := optional
  4. LOCAL_SRC_FILES := $(call all-subdir-java-files)
  5. LOCAL_PACKAGE_NAME := Broadcast
  6. include $(BUILD_PACKAGE)

接下来就要编译了。有关如何单独编译Android源代码工程的模块,以及如何打包system.img,请参考如何单独编译Android源代码中的模块一文。
          执行以下命令进行编译和打包:

  1. USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/Broadcast
  2. USER-NAME@MACHINE-NAME:~/Android$ make snod

这样,打包好的Android系统镜像文件system.img就包含我们前面创建的Broadcast应用程序了。
         再接下来,就是运行模拟器来运行我们的例子了。关于如何在Android源代码工程中运行模拟器,请参考在Ubuntu上下载、编译和安装Android最新源代码一文。
         执行以下命令启动模拟器:

  1. USER-NAME@MACHINE-NAME:~/Android$ emulator

模拟器启动起,就可以App Launcher中找到Broadcast应用程序图标,接着把它启动起来,然后点击界面上的Start Counter按钮,就可以把计数器服务启动起来了,计数器服务又通过广播把计数值反馈给MainActivity,于是,我们就会在MainActivity界面看到计数器的值不断地增加了:

这样,使用广播的例子就介绍完了。回顾一下,使用广播的两个步骤:

1. 广播的接收者需要通过调用registerReceiver函数告诉系统,它对什么样的广播有兴趣,即指定IntentFilter,并且向系统注册广播接收器,即指定BroadcastReceiver:

  1. IntentFilter counterActionFilter = new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION);
  2. registerReceiver(counterActionReceiver, counterActionFilter);

这里,指定感兴趣的广播就是CounterService.BROADCAST_COUNTER_ACTION了,而指定的广播接收器就是counterActonReceiver,它是一个BroadcastReceiver类型的实例。

2. 广播的发送者通过调用sendBroadcast函数来发送一个指定的广播,并且可以指定广播的相关参数:

  1. Intent intent = new Intent(BROADCAST_COUNTER_ACTION);
  2. intent.putExtra(COUNTER_VALUE, counter);
  3. sendBroadcast(intent)

这里,指定的广播为CounterService.BROADCAST_COUNTER_ACTION,并且附带的带参数当前的计数器值counter。调用了sendBroadcast函数之后,所有注册了CounterService.BROADCAST_COUNTER_ACTION广播的接收者便可以收到这个广播了。

在第1步中,广播的接收者把广播接收器注册到ActivityManagerService中;在第2步中,广播的发送者同样是把广播发送到ActivityManagerService中,由ActivityManagerService去查找注册了这个广播的接收者,然后把广播分发给它们。

在第2步的分发的过程,其实就是把这个广播转换成一个消息,然后放入到接收器所在的线程消息队列中去,最后就可以在消息循环中调用接收器的onReceive函数了。这里有一个要非常注意的地方是,由于ActivityManagerService把这个广播放进接收器所在的线程消息队列后,就返回了,它不关心这个消息什么时候会被处理,因此,对广播的处理是异步的,即调用sendBroadcast时,这个函数不会等待这个广播被处理完后才返回。

下面,我们以一个序列图来总结一下,广播的注册和发送的过程:

虚线上面Step 1到Step 4步是注册广播接收器的过程,其中Step 2通过LoadedApk.getReceiverDispatcher在LoadedApk内部创建了一个IIntentReceiver接口,并且传递给ActivityManagerService;虚线下面的Step 5到Step 11是发送广播的过程,在Step 8中,ActivityManagerService利用上面得到的IIntentReceiver远程接口,调用LoadedApk.performReceiver接口,LoadedApk.performReceiver接口通过ActivityThread.H接口的post函数将这个广播消息放入到ActivityThread的消息队列中去,最后这个消息在LoadedApk的Args.run函数中处理,LoadedApk.Args.run函数接着调用MainActivity.BroadcastReceiver的onReceive函数来最终处理这个广播。

文章开始的时候,我们提到,举这个例子的最终目的,是为了进一步学习Android系统的广播机制,因此,在接下来的两篇文章中,我们将详细描述上述注册广播接收器和发送广播的过程:

1.Android应用程序注册广播接收器(registerReceiver)的过程分析;

2. Android应用程序发送广播(sendBroadcast)的过程分析。

相信学习完这两篇文章后,能够加深对Android系统广播机制的了解,敬请关注。

Android系统中的广播(Broadcast)机制简要介绍和学习计划的更多相关文章

  1. Android应用程序的Activity启动过程简要介绍和学习计划

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6685853 在Android系统中,Activ ...

  2. SEAndroid安全机制简要介绍和学习计划

    与iOS相比.Android最被人诟病的是其流畅性和安全性. 然而,从4.0開始,Android不遗余力地改善其流畅性. 特别是在即将公布的L版本号中,用ART替换了Dalvik,相信会越来越流畅.至 ...

  3. [转] Android资源管理框架(Asset Manager)简要介绍和学习计划

    转自:http://blog.csdn.net/luoshengyang/article/details/8738877 Android应用程序主要由两部分内容组成:代码和资源.资源主要就是指那些与U ...

  4. Android资源管理框架(Asset Manager)简要介绍和学习计划

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8738877 Android应用程序主要由两部分 ...

  5. [转]Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划

    转自:Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划 前面我们从Android应用程序与SurfaceFlinger服务的关系出发,从侧面简单学习了Surfa ...

  6. Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8010977 前面我们从Android应用程序与 ...

  7. Android应用程序组件Content Provider简要介绍和学习计划

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6946067 在Android系统中,Conte ...

  8. Android窗口管理服务WindowManagerService的简要介绍和学习计划

    在前一个系列文章中,我们从个体的角度来分析了Android应用程序窗口的实现框架.事实上,如果我们从整体的角度来看,Android应用程序窗口的 实现要更复杂,因为它们的类型和作用不同,且会相互影响. ...

  9. Android应用程序窗口(Activity)实现框架简要介绍和学习计划

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8170307 前面我们学习了SurfaceFli ...

随机推荐

  1. codevs 3012 线段覆盖 4 & 3037 线段覆盖 5

    3037 线段覆盖 5  时间限制: 3 s  空间限制: 256000 KB  题目等级 : 钻石 Diamond 题解       题目描述 Description 数轴上有n条线段,线段的两端都 ...

  2. 关于“服务器提交了协议冲突. Section=ResponseStatusLine"问题

    你的问题的原因是这样的,ASP.Net 2.0 增强了安全性,对一些有危害的http 头进行了判断,比如url中有空格的情况,以帮助网站提高网络攻击的防御能力.如果你的http头中有一些ASP.NET ...

  3. Silverlight中使用MVVM:DataGrid中触发Button的Click事件

    方法1.使用RelativeSource向上查找DataContext中的命令,但是需要注意的是命令绑定需要写全 类似: DataContext.ReLoadCommand<Button Gri ...

  4. Jenkins + NuGet + MSBuild

    Jenkins + NuGet + MSBuild 背景 项目上需要做UWP的自动安装包,在以前的公司接触的是TFS来做自动build. 公司要求用Jenkins来做,别笑话我,之前还真不晓得这个东西 ...

  5. 集锦.txt

    不同的时代,有不同的需要以前,我们都以为自己可以为爱情去死,可是后来爱情死了,我们还活着. 小时候男孩子喜欢电动玩具,女孩子喜欢娃娃.长大了男孩子喜欢娃娃,女孩子喜欢电动玩具.也许这就是成长吧! 我大 ...

  6. Math类和Random类(数学公式相关类)

    Math 类包含用于执行基本数学运算的方法,如初等指数.对数.平方根和三角函数. 常用方法: 1.static 数值类型 abs(数值类型 a)      返回 double 值的绝对值. 2.sta ...

  7. 【转】使用sklearn优雅地进行数据挖掘

    这里是原文 目录 使用sklearn进行数据挖掘 1.1 数据挖掘的步骤 1.2 数据初貌 1.3 关键技术并行处理 并行处理 2.1 整体并行处理 2.2 部分并行处理流水线处理自动化调参持久化回顾 ...

  8. listview向下滑动过程中背景色变成黑色和一些奇怪问题

    ListView是一个经常要用到的android控件,现总结遇到过的一些美化的小细节. 1.listview在拖动的时候背景图片消失变成黑色背景,等到拖动完毕我们自己的背景图片才显示出来 这个问题是我 ...

  9. C语言操作符优先级

    C语言操作符优先级 优先级 运算符 含    义 要求运算 对象的个数 结合方向 1 () [] -> . 圆括号 下标运算符 指向结构体成员运算符 结构体成员运算符 自左至右 2 ! 逻辑非运 ...

  10. Quartz.NET syudy

    Quartz.NET   Quartz.NET是一个开源的作业调度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#写成,可用于winform和asp.net应用中.它 ...