Activity与Service之间的通讯机制
来源:互联网 发布:java生成支付宝二维码 编辑:程序博客网 时间:2024/05/22 11:08
Activity传递数据给Service
Intent中直接传递数据。
Intent intent = new Intent(“com.service”);intent.putExtra(“data”, "helloData");startService(intent);
Service回传数据给Activity
应用场景举例:有一个Service,它在另外一个线程中实现了一个计数器服务,每隔一秒钟就自动加1,然后将结果不断地反馈给应用程序中的界面线程,而界面线程中的Activity在得到这个反馈后,就会把结果显示在界面上。
方法一:service在数据发生变化的时候发送一条广播,在Activity注册广播接收器,接收到广播之后获取数据更新UI。
MainActivity 核心代码:
public class MainActivity extends Activity implements OnClickListener { private final static String LOG_TAG = "shy.luo.broadcast.MainActivity"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 绑定服务 Intent bindIntent = new Intent(MainActivity.this, CounterService.class); bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE); } @Override public void onResume() { super.onResume(); // 注册广播 IntentFilter counterActionFilter = new IntentFilter( CounterService.BROADCAST_COUNTER_ACTION); registerReceiver(counterActionReceiver, counterActionFilter); } private BroadcastReceiver counterActionReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { int counter = intent.getIntExtra(CounterService.COUNTER_VALUE, 0); String text = String.valueOf(counter); // 接收数据更新界面 counterText.setText(text); } }; private ServiceConnection serviceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { counterService = ((CounterService.CounterBinder) service) .getService(); } public void onServiceDisconnected(ComponentName className) { counterService = null; } };}
CounterService核心代码:
public class CounterService extends Service implements ICounterService { public final static String BROADCAST_COUNTER_ACTION = "shy.luo.broadcast.COUNTER_ACTION"; public final static String COUNTER_VALUE = "shy.luo.broadcast.counter.value"; private boolean stop = false; ... public void startCounter(int initVal) { AsyncTask<Integer, Integer, Integer> task = new AsyncTask<Integer, Integer, Integer>() { @Override protected Integer doInBackground(Integer... vals) { Integer initCounter = vals[0]; stop = false; while (!stop) { publishProgress(initCounter); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } initCounter++; } return initCounter; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); int counter = values[0]; Intent intent = new Intent(BROADCAST_COUNTER_ACTION); intent.putExtra(COUNTER_VALUE, counter); sendBroadcast(intent); } @Override protected void onPostExecute(Integer val) { int counter = val; Intent intent = new Intent(BROADCAST_COUNTER_ACTION); intent.putExtra(COUNTER_VALUE, counter); sendBroadcast(intent); } }; task.execute(0); } ...}
方法二:注册回调接口,数据发生变化的时候Service主动通知Activity,Activity就可以接收数据更新UI了。
MainActivity 核心代码:
public class MainActivity extends Activity implements OnClickListener { private final static String LOG_TAG = "shy.luo.broadcast.MainActivity"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Intent bindIntent = new Intent(MainActivity.this, CounterService.class); bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE); } private ServiceConnection serviceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { counterService = ((CounterService.CounterBinder) service) .getService(); // 注册监听 counterService.setOnProgressListener(new OnProgressListener() { @Override public void onProgress(int progress) { counterText.setText(progress); } }); } public void onServiceDisconnected(ComponentName className) { counterService = null; } };}
CounterService 核心代码:
public class CounterService extends Service implements ICounterService { public final static String BROADCAST_COUNTER_ACTION = "shy.luo.broadcast.COUNTER_ACTION"; public final static String COUNTER_VALUE = "shy.luo.broadcast.counter.value"; private boolean stop = false; ... /** * 更新数据的回调接口 */ private OnProgressListener onProgressListener; /** * 注册回调接口的方法,供外部调用 * @param onProgressListener */ public void setOnProgressListener(OnProgressListener onProgressListener) { this.onProgressListener = onProgressListener; } public void startCounter(int initVal) { AsyncTask<Integer, Integer, Integer> task = new AsyncTask<Integer, Integer, Integer>() { @Override protected Integer doInBackground(Integer... vals) { Integer initCounter = vals[0]; stop = false; while (!stop) { publishProgress(initCounter); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } initCounter++; } return initCounter; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); int counter = values[0]; //数据发生变化通知调用方 if(onProgressListener != null){ onProgressListener.onProgress(counter); } } @Override protected void onPostExecute(Integer val) { //数据发生变化通知调用方 if(onProgressListener != null){ onProgressListener.onProgress(val); } } }; task.execute(0); } ... public interface OnProgressListener { void onProgress(int progress); }}
Activity调用Service的方法(进程内)
进程内与服务通信实际上就是通过bindService的方式与服务绑定,获取到通信中介Binder实例,通过Binder实例可以得到Service对象的一个引用,然后通过调用这个引用对象,完成对服务的各种操作。
下面来看具体代码演示:
MainActivity:
public class MainActivity extends Activity implements View.OnClickListener { private static final String TAG = "MainActivity"; private Button bind; private Button unbind; private Button sayHello; private boolean binded; private MyService.MyBinder myBinder; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { binded = true; myBinder = (MyService.MyBinder) service; } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bind = (Button) findViewById(R.id.bt_bind); unbind = (Button) findViewById(R.id.bt_unbind); sayHello = (Button) findViewById(R.id.bt_sayHello); bind.setOnClickListener(this); unbind.setOnClickListener(this); sayHello.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.bt_bind: bind(); break; case R.id.bt_unbind: unbind(); break; case R.id.bt_sayHello: sayHello(); break; } } private void sayHello() { myBinder.sayHello("wuseyukui"); } /** * 绑定服务 * * @param */ public void bind() { Intent intent = new Intent(this, MyService.class); bindService(intent, conn, Context.BIND_AUTO_CREATE); } /** * 解除绑定 * * @param */ public void unbind() { if (binded) { unbindService(conn); binded = false; } } @Override protected void onDestroy() { super.onDestroy(); unbind(); }}
Myservice:
public class MyService extends Service { private static final String TAG = "MyService"; @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { return new MyBinder(); } public class MyBinder extends Binder { public void sayHello(String name) { Log.i(TAG, "hello, " + name); } }}
点击Say Hello按钮:
我们创建了一个MyBinder的内部类,定义了一个sayHello方法,在onBind方法中就将这个MyBinder的实例返回,只要在Activity中获取到这个实例,就可以像拿着游戏手柄一样对服务进行操作。
以上就是一个最简单的进程内通讯的例子。
Activity调用Service的方法(进程间)
Android IPC(Inter-Process Communication)进程间通信最为大家所熟知得就是AIDL(Android Interface definition language)了,当然进程间通信并不止这一种方式,也可以通过Messenger方式 或 广播方式 完成的。
一、AIDL方式
服务端
结构:
Person类是我们要在服务端和客户端中间进行传递的类型
Person.java:
package com.wuseyukui.mytest.aidl;import android.os.Parcel;import android.os.Parcelable;public class Person implements Parcelable { private String name; private int sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; } public Person() { } //必须提供一个名为CREATOR的static final属性 该属性需要实现android.os.Parcelable.Creator<T>接口 public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() { @Override public Person createFromParcel(Parcel source) { return new Person(source); } @Override public Person[] newArray(int size) { return new Person[size]; } }; private Person(Parcel source) { readFromParcel(source); } @Override public int describeContents() { return 0; } //注意写入变量和读取变量的顺序应该一致 不然得不到正确的结果 @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(sex); } //注意读取变量和写入变量的顺序应该一致 不然得不到正确的结果 public void readFromParcel(Parcel source) { name = source.readString(); sex = source.readInt(); }}
我们需要在同一包下建立一个与包含复杂类型的Person.java文件匹配的Person.aidl文件
Person.aidl:
package com.wuseyukui.mytest.aidl;parcelable Person;
IGreetService.aidl文件,以接收类型为Person的输入参数,以便客户端可以将Person传递给服务
ISayHelloService.aidl:
package com.wuseyukui.mytest.aidl;// 需要为Person提供一个import语句(虽然说在同一个包下)import com.wuseyukui.mytest.aidl.Person;interface ISayHelloService { // in代表参数由客户端设置,out表示由服务端设置,inout表示客户端和服务端都设置了该值 String sayHello(in Person person);}
补充AIDL对Java类型的支持
1、AIDL支持Java原始数据类型。
2、AIDL支持String和CharSequence。
3、AIDL支持传递其他AIDL接口,但你引用的每个AIDL接口都需要一个import语句,即使位于同一个包中。
4、AIDL支持传递实现了android.os.Parcelable接口的复杂类型,同样在引用这些类型时也需要import语句。
5、AIDL支持java.util.List和java.util.Map,但是有一些限制。集合中项的允许数据类型包括Java原始类型、String、CharSequence或是android.os.Parcelable。无需为List和Map提供import语句,但需要为Parcelable提供import语句。
6、非原始类型中,除了String和CharSequence以外,其余均需要一个方向指示符。方向指示符包括in、out、和inout。in表示由客户端设置,out表示由服务端设置,inout表示客户端和服务端都设置了该值。
如果以上aidl没有错误,此时,Android Studio的AIDL编译器会自动编译生成一个IGreetService.java文件:
Stub类继承了Binder,并继承我们在aidl文件中定义的接口,我们需要实现接口方法
接下来,就该编写Service,实现接口方法了,代码如下:
Myservice.java:
package com.wuseyukui.mytest;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.os.RemoteException;import android.util.Log;import com.wuseyukui.mytest.aidl.ISayHelloService;import com.wuseyukui.mytest.aidl.Person;public class MyService extends Service { private static final String TAG = "MyService"; ISayHelloService.Stub stub = new ISayHelloService.Stub() { @Override public String sayHello(Person person) throws RemoteException { Log.i(TAG, "sayHello(Person person) called"); String greeting = "hello, " + person.getName(); switch (person.getSex()) { case 0: greeting = greeting + ", you're man."; break; case 1: greeting = greeting + ", you're women."; break; } return greeting; } }; @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind() called"); return stub; } @Override public boolean onUnbind(Intent intent) { Log.i(TAG, "onUnbind() called"); return true; } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestroy() called"); }}
Manifest.xml中配置如下:
<service android:name="com.wuseyukui.mytest.MyService"> <intent-filter> <action android:name="com.wuseyukui.mytest.MyService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service>
到此服务端创建完毕,可以运行到模拟器上了。
客户端
结构:
我们需要把服务端的Person.java、Person.aidl和IGreetService.aidl拷到对应的包下,然后才能进行客户端开发。
MainActivity.java:
public class MainActivity extends Activity implements View.OnClickListener { private static final String TAG = "MainActivity"; private Button bind; private Button unbind; private Button sayHello; private boolean binded; private ISayHelloService sayHelloService; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { sayHelloService = ISayHelloService.Stub.asInterface(service); binded = true; Log.i("ServiceConnection", "onServiceConnected() called"); } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bind = (Button) findViewById(R.id.bt_bind); unbind = (Button) findViewById(R.id.bt_unbind); sayHello = (Button) findViewById(R.id.bt_sayHello); bind.setOnClickListener(this); unbind.setOnClickListener(this); sayHello.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.bt_bind: bind(); break; case R.id.bt_unbind: unbind(); break; case R.id.bt_sayHello: try { sayHello(); } catch (RemoteException e) { e.printStackTrace(); } break; } } private void sayHello() throws RemoteException { try { Person person = new Person(); person.setName("wuseyukui"); person.setSex(0); String result = sayHelloService.sayHello(person); Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show(); Log.i(TAG, "====== return from service:"+result); } catch (Exception e) { Toast.makeText(MainActivity.this, "error", Toast.LENGTH_SHORT).show(); Log.i(TAG, "====== error: 服务未启动、未绑定或其他错误"); } } /** * 绑定服务 * * @param */ public void bind() { Intent intent = new Intent("com.wuseyukui.mytest.MyService"); bindService(intent, conn, Context.BIND_AUTO_CREATE); } /** * 解除绑定 * * @param */ public void unbind() { if (binded) { unbindService(conn); binded = false; } } @Override protected void onDestroy() { super.onDestroy(); unbind(); }}
将客户端运行起来。
依次点击绑定服务、say Hello、解除绑定服务按钮,结果打印如下:
至此,进程间通信AIDL方式说完了。
二、Messenger方式:
以下内容来自:http://blog.csdn.net/ljd2038/article/details/50739713
使用步骤
1. 在服务端我们实现一个 Handler,接收来自客户端的每个调用的回调 2. 这个Handler 用于创建 Messenger 对象(也就是对 Handler 的引用) 3. 用Messenger 创建一个 IBinder,服务端通过 onBind() 使其返回客户端 4. 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用后者将 Message 对象发送给服务端 5. 服务端在其 Handler 中(具体地讲,是在 handleMessage() 方法中)接收每个 Message
服务端示例代码:
public class MessengerService extends Service { private final Messenger mMessenger = new Messenger(new ServiceHandler()); private class ServiceHandler extends Handler{ @Override public void handleMessage(Message msg) { switch (msg.what){ case 0: Messenger clientMessenger = msg.replyTo; Message replyMessage = Message.obtain(); replyMessage.what = 1; Bundle bundle = new Bundle(); //将接收到的字符串转换为大写后发送给客户端 bundle.putString("service", msg.getData().getString("client").toUpperCase()); replyMessage.setData(bundle); try { clientMessenger.send(replyMessage); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); } } } @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); }}
Manifest清单文件中,我们配置服务在新进程中运行:
<service android:name=".MessengerService" android:process=":remote"></service>
客户端示例代码:
public class MainActivity extends AppCompatActivity { @Bind(R.id.messenger_linear) LinearLayout mShowLinear; private Messenger mMessenger; private final String LETTER_CHAR = "abcdefghijkllmnopqrstuvwxyz"; private class ClientHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what){ case 1: TextView textView = new TextView(MainActivity.this); textView.setText("convert ==>:" + (msg.getData().containsKey("service")?msg.getData().getString("service"):"")); mShowLinear.addView(textView); break; default: super.handleMessage(msg); } } } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mMessenger = new Messenger(service); } @Override public void onServiceDisconnected(ComponentName name) { bindService(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); bindService(); } @OnClick({R.id.test_button,R.id.clear_button}) public void onClickButton(View v){ switch (v.getId()){ case R.id.test_button: sendToService(); break; case R.id.clear_button: mShowLinear.removeAllViews(); break; } } @Override protected void onDestroy() { super.onDestroy(); unbindService(mConnection); ButterKnife.unbind(this); } private void bindService(){ Intent intent = new Intent(MainActivity.this,MessengerService.class); bindService(intent,mConnection, Context.BIND_AUTO_CREATE); } private void sendToService(){ Message messageClient = Message.obtain(null,0); //用于传递给服务端回复的Messenger Messenger replyMessenger = new Messenger(new ClientHandler()); Bundle bundle = new Bundle(); bundle.putString("client",generateMixString()); messageClient.setData(bundle); //通过Message的replyTo属性将Messenger对象传递到服务端 messageClient.replyTo = replyMessenger; TextView textView = new TextView(MainActivity.this); textView.setText("send:" + (bundle.getString("client"))); mShowLinear.addView(textView); try { mMessenger.send(messageClient); } catch (RemoteException e) { e.printStackTrace(); } } /** * 随机生成10位小写字母的字符串 * @return */ public String generateMixString() { StringBuffer sb = new StringBuffer(); Random random = new Random(); for (int i = 0; i < 10; i++) { sb.append(LETTER_CHAR.charAt(random.nextInt(LETTER_CHAR.length()))); } return sb.toString(); }}
Messenger原理分析
源码:
package android.os;public final class Messenger implements Parcelable { private final IMessenger mTarget; public Messenger(Handler target) { mTarget = target.getIMessenger(); } public void send(Message message) throws RemoteException { mTarget.send(message); } public IBinder getBinder() { return mTarget.asBinder(); } public boolean equals(Object otherObj) { if (otherObj == null) { return false; } try { return mTarget.asBinder().equals(((Messenger)otherObj) .mTarget.asBinder()); } catch (ClassCastException e) { } return false; } public int hashCode() { return mTarget.asBinder().hashCode(); } public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeStrongBinder(mTarget.asBinder()); } public static final Parcelable.Creator<Messenger> CREATOR = new Parcelable.Creator<Messenger>() { public Messenger createFromParcel(Parcel in) { IBinder target = in.readStrongBinder(); return target != null ? new Messenger(target) : null; } public Messenger[] newArray(int size) { return new Messenger[size]; } }; public static void writeMessengerOrNullToParcel(Messenger messenger, Parcel out) { out.writeStrongBinder(messenger != null ? messenger.mTarget.asBinder() : null); } public static Messenger readMessengerOrNullFromParcel(Parcel in) { IBinder b = in.readStrongBinder(); return b != null ? new Messenger(b) : null; } public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }}
我们可以看到这个Messenger实现Parcelable接口。然后声明了一个IMessenger 对象mTarget。并且在Messenger中的两个构造方法中都对这个mTarget进行了初始化。那么这个IMessenger 是什么东西呢?我们首先看一下Messenger中的第一个构造方法。
public Messenger(Handler target) { mTarget = target.getIMessenger();}
首先我们使用这个构造方法在服务端创建一个Messenger对象,在onBind中通过Messenger中的getBinder方法向客户端返回一个Binder对象。而在这个构造方法里面通过Handler中的getIMessenger方法初始化mTarget。那么我们进入Handler中看一下这个getIMessenger方法。
public class Handler { ... final IMessenger getIMessenger() { synchronized (mQueue) { if (mMessenger != null) { return mMessenger; } mMessenger = new MessengerImpl(); return mMessenger; } } private final class MessengerImpl extends IMessenger.Stub { public void send(Message msg) { msg.sendingUid = Binder.getCallingUid(); Handler.this.sendMessage(msg); } } ...}
这回我们看到了这个getIMessenger方法中返回了一个MessengerImpl对象,而这个MessengerImpl中对send方法实现,也只不过是通过Handler发送了一个message。并且在Handler中的handleMessage方法中进行处理。
而在我们的客户端当中,我们通过客户端的onServiceConnected中拿到服务端返回的Binder对象,并且在onServiceConnected方法中new Messenger(service)去获取这个IMessenger对象,下面我们就看一下这个Messenger的第二个构造方法。
public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target);}
这个构造方法一般用于客户端中。并且到现在我们一路走下来似乎看到了许多熟悉的身影IMessenger.Stub,Stub.asInterface等等。这些不正是我们在AIDL中使用到的吗?于是我们猜想这个IMessenger应该是一个AIDL接口。现在就去找找这个IMessenger。这个IMessenger位于frameworks的base中, 完整路径为frameworks/base/core/Java/Android/os/。我们就看一下这个AIDL接口。
package android.os;import android.os.Message;/** @hide */oneway interface IMessenger { void send(in Message msg);}
这个IMessenger接口中只有一个send方法。而在这里关键字oneway表示当服务用户请求相应功能的时后不需要等待应答就可以直接调用返回,这个关键字可以用于接口声明或者方法声明语句中,如果接口声明语句中使用了oneway关键字,则这个接口中声明的所有方法都采用了oneway方式。这回我们也就明白这个Messenger了,其实这个Messenger就是进一步对AIDL进行了一次封装。
在这里客户端通过服务端返回的Binder创建了Messenger对象。然后我们只需要创建一个Message对象,在这个Message对象中携带我们所需要传递给服务端的数据,这时候就可以在服务端中的handleMessage进行处理。若是我们需要服务端给我们返回数据,只需要在客户端创建一个Handler,并且使用这个Handler创建一个Messenger对象,然后将这个Messenger对象通过Message中的replyTo字段传递到服务端,在服务端获取到客户端的Messenger对象后,便可以通过这个Messenger发送给客户端,然后在客户端中的handlerMessage处理即可。
AIDL与Messenger的区别
我们可以发现在IPC通信中使用Messenger要比使用AIDL简单很多。因为在Messenger中是通过Handler来对AIDL进行的封装,也就是说Messenger是通过队列来调用服务的,而单纯的AIDL会同时像服务端发出请求,这时候我们就必须对多线程进行处理。
那么对于一个应用来说。它的Service不需要执行多线程我们应该去使用这个Messenger,返过来若是我们Service处理是多线程的我们就应该使用AIDL去定义接口。
总结
Messenger它是一种轻量级的IPC 方法,我们使用起来也是非常的简单。他是使用Handler对AIDL进行了一次封装,一次只能处理一个请求。并且Messenger在发送Message的时候不能使用他的obj字段,我们可以用bundle来代替。最后还有一点就是Messenger只是在客户端与服务端跨进程的传递数据,而不能够去访问服务端的方法。
- Activity与Service之间的通讯机制
- Android Service 与Activity之间的通讯方式
- Service与Activity之间的通讯(二)
- service和activity之间通讯
- Activity与Thread之间的通讯
- Activity与Thread之间的通讯
- Activity与Thread之间的通讯
- Activity与Thread之间的通讯
- Activity与Thread之间的通讯
- Activity与Thread之间的通讯
- Activity与Thread之间的通讯
- Activity与Thread之间的通讯
- Activity与Thread之间的通讯
- Activity与Thread之间的通讯
- 独立AsyncTask与activity之间的通讯
- Activity和Service之间的通讯(下载的例子)
- Android--activity配置与activity之间的通讯(一)
- android activity与service之间的通信
- iOS动态类型和动态绑定
- Dialog的各种使用方式
- Ubuntu 15.04 安装VNCServer, Xfce4桌面与配置
- AD14使用过程记录!
- SPFA(最短路问题)
- Activity与Service之间的通讯机制
- linux下普通用户没有crontab权限问题解决
- 快学Scala习题解答—第四章 映射和元组
- fibonacci数列的递归与非递归实现
- volatile关键字的作用
- 工作中的程序员如何进阶
- HDOJ 2222.Keywords Search(AC自动机模板)
- Javascript(一)-09-(JS语句-switch语句)
- Servlet