【Android】Binder
来源:互联网 发布:4g网络加速代码 编辑:程序博客网 时间:2024/06/13 23:08
一、跨进程通信
为了演示方便,将Service与Activity处于不同的进程,可以在AndroidManifest.xml中,把service配置成android:process=":remote"
,也可以命名成其他的。
AIDL
(1) IRemoteService.aidl:定义Server端提供的服务接口
// IRemoteService.aidlpackage com.cuc.myandroidtest;import com.cuc.myandroidtest.MyData;import com.cuc.myandroidtest.IServiceCallback;interface IRemoteService { int getPid(); //获取服务端进程ID MyData getMyData(); //从服务端获取数据 void registerCallback(IServiceCallback callback); //注册服务端回调 void unregisterCallback();}
(2) IServiceCallBack.aidl:定义服务端回调接口,把Service的下载进度通知给客户端ClientActivity
// IServiceCallBack.aidlpackage com.cuc.myandroidtest;interface IServiceCallback { void onDownloadProgress(double progress); //服务端下载进度通知 void onDownloadCompleted(); //下载完成通知}
(3) MyData.aidl:定义传输的Parcel数据
// MyData.aidlpackage com.cuc.myandroidtest;parcelable MyData;
Parcel数据
public class MyData implements Parcelable{ int data1; int data2; String key; public MyData(){} protected MyData(Parcel in) { data1 = in.readInt(); data2 = in.readInt(); key = in.readString(); } public static final Creator<MyData> CREATOR = new Creator<MyData>() { @Override public MyData createFromParcel(Parcel in) { return new MyData(in); } @Override public MyData[] newArray(int size) { return new MyData[size]; } }; @Override public int describeContents() { return 0; } /** 将数据写入到Parcel **/ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(data1); dest.writeInt(data2); dest.writeString(key); } public int getData1() { return data1; } public void setData1(int data1) { this.data1 = data1; } public int getData2() { return data2; } public void setData2(int data2) { this.data2 = data2; } public String getKey() { return key; } public void setKey(String key) { this.key = key; }}
Server端
public class RemoteService extends Service { private static final String TAG = "BinderSimple"; int mDownloadCount = 0; MyData mMyData; IServiceCallback mCallback; //用来通知客户端的回调 ScheduledExecutorService mThreadPool; //下载线程 @Override public void onCreate() { super.onCreate(); Log.i(TAG, "[RemoteService] onCreate"); mMyData = new MyData(); mMyData.setData1(10); mMyData.setData2(20); mMyData.setKey("在那遥远的地方"); } @Override public IBinder onBind(Intent intent) { Log.i(TAG,"[RemoteService] onBind"); //开始下载 startDownloadThread(); return mBinder; } @Override public boolean onUnbind(Intent intent) { Log.i(TAG, "[RemoteService] onUnbind"); try { mBinder.unregisterCallback(); } catch (RemoteException e) { e.printStackTrace(); } return super.onUnbind(intent); } @Override public void onDestroy() { Log.i(TAG, "[RemoteService] onDestroy"); super.onDestroy(); } /** * 实现接口IRemoteService.aidl中定义的方法 */ private final IRemoteService.Stub mBinder = new IRemoteService.Stub() { @Override public int getPid() throws RemoteException { Log.i(TAG,"[RemoteService] getPid() = " + android.os.Process.myPid()); return android.os.Process.myPid(); } @Override public MyData getMyData() throws RemoteException { Log.i(TAG,"[RemoteService] getMyData() " + mMyData.toString()); return mMyData; } @Override public void registerCallback(IServiceCallback callback) throws RemoteException { Log.i(TAG,"[RemoteService] registerCallback() "); mCallback = callback; } @Override public void unregisterCallback() throws RemoteException { Log.i(TAG,"[RemoteService] unregisterCallback() "); mCallback = null; } /**此处可用于权限拦截**/ @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { return super.onTransact(code, data, reply, flags); } }; /** * 模拟下载线程 */ private void startDownloadThread(){ Log.i(TAG,"[RemoteService] startDownloadThread()"); //2秒后开始下载 mThreadPool = Executors.newScheduledThreadPool(1); mThreadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { mDownloadCount++; if(mCallback != null){ mCallback.onDownloadProgress((double) mDownloadCount / 100.0); if (mDownloadCount == 100) { mCallback.onDownloadCompleted(); mThreadPool.shutdown(); } } } catch (RemoteException e) { e.printStackTrace(); } } }, 2000, 50, TimeUnit.MILLISECONDS); }}
Client端
public class ClientActivity extends AppCompatActivity { private static final String TAG = "BinderSimple"; private IRemoteService mRemoteService; private TextView textView; private boolean mIsBound; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_client); textView = (TextView) findViewById(R.id.tv); Button btn0 = (Button) findViewById(R.id.btn_bind); Button btn1 = (Button) findViewById(R.id.btn_unbind); Button btn2 = (Button) findViewById(R.id.btn_kill); btn0.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { bindMyService(); } }); btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { unBindMyService(); } }); btn2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { killMyService(); } }); } private ServiceConnection mRemoteConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //注意这里,把Server返回的Binder对象转换成了IRemoteService接口对象 mRemoteService = IRemoteService.Stub.asInterface(service); try { mRemoteService.registerCallback(mServiceCallbackBinder); MyData myData = mRemoteService.getMyData(); String pidInfo = " servicePid = "+ mRemoteService.getPid() + "\n myPid = " + android.os.Process.myPid() + "\n data1 = "+ myData.getData1() + "\n data2 = "+ myData.getData2() + "\n key = "+ myData.getKey(); Log.i(TAG, "[ClientActivity] onServiceConnected\n" + pidInfo); textView.setText(pidInfo); Toast.makeText(ClientActivity.this, "remoteService 连接成功", Toast.LENGTH_SHORT).show(); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "[ClientActivity] onServiceDisconnected"); mRemoteService = null; Toast.makeText(ClientActivity.this, "remoteService 断开连接", Toast.LENGTH_SHORT).show(); } }; //服务回调 private IServiceCallback.Stub mServiceCallbackBinder = new IServiceCallback.Stub(){ @Override public void onDownloadProgress(final double progress) throws RemoteException { Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { textView.setText("下载进度:" + progress); } }); } @Override public void onDownloadCompleted() throws RemoteException { Log.i(TAG, "[ClientActivity] mServiceCallbackBinder -> onDownloadCompleted"); Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { textView.setText("下载完成"); } }); } }; /** * 绑定远程服务 */ private void bindMyService(){ Log.i(TAG, "[ClientActivity] bindMyService"); Intent intent = new Intent(ClientActivity.this, RemoteService.class); intent.setAction(IRemoteService.class.getName()); bindService(intent, mRemoteConnection, Context.BIND_AUTO_CREATE); mIsBound = true; textView.setText("bind success"); } /** * 解除绑定远程服务 */ private void unBindMyService(){ if(!mIsBound){ return; } Log.i(TAG, "[ClientActivity] unBindMyService"); unbindService(mRemoteConnection); mIsBound = false; textView.setText("unbind"); } /** * 杀死远程服务 */ private void killMyService(){ Log.i(TAG, "[ClientActivity] killMyService"); try { android.os.Process.killProcess(mRemoteService.getPid()); textView.setText("kill success"); } catch (RemoteException e) { e.printStackTrace(); Toast.makeText(ClientActivity.this, "kill failed", Toast.LENGTH_SHORT).show(); } }}
运行
该工程会生成一个apk,安装到手机,打开apk,界面如下:
界面上有三个按钮,分别是功能分别是:
- bindService(绑定Service)
- unbindService(解除绑定Service)
- killProcess(杀死Service进程)
从左往右,依次点击界面,可得:
二、同一进程
如果Activity和Service位于同一进程内,也可以使用上面的方式。不过还有一种方法是下面这种。
AIDL文件、Parcel数据与上面均一致,下面仅列出不同的Server端和Client端的实现。
Server端
public class LocalService extends Service { private static final String TAG = "BinderSimple"; //封装的服务端功能对象,供Client端bindService之后调用 private LocalServerFunc mBinder = new LocalServerFunc(); @Override public void onCreate() { super.onCreate(); Log.i(TAG, "[LocalService] onCreate"); } @Override public IBinder onBind(Intent intent) { Log.i(TAG,"[LocalService] onBind"); //开启下载线程 mBinder.startDownloadThread(); return mBinder; } @Override public boolean onUnbind(Intent intent) { Log.i(TAG, "[LocalService] onUnbind"); try { mBinder.unregisterCallback(); } catch (RemoteException e) { e.printStackTrace(); } return super.onUnbind(intent); } @Override public void onDestroy() { Log.i(TAG, "[LocalService] onDestroy"); super.onDestroy(); }}
因为,Service被bind了之后,需要返回一个IBinder对象。所以需要继承自Binder封装一个IBinder对象供客户端调用。
public class LocalServerFunc extends Binder implements IRemoteService { private static final String TAG = "BinderSimple"; int mDownloadCount = 0; MyData mMyData; IServiceCallback mCallback; ScheduledExecutorService mThreadPool; public LocalServerFunc(){ mMyData = new MyData(); mMyData.setData1(66); mMyData.setData2(88); mMyData.setKey("就在眼前"); } public void startDownloadThread(){ Log.i(TAG,"[LocalServerFunc] startDownloadThread()"); mThreadPool = Executors.newScheduledThreadPool(1); mThreadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { mDownloadCount++; if(mCallback != null){ mCallback.onDownloadProgress((double) mDownloadCount / 100.0); if (mDownloadCount == 100) { mCallback.onDownloadCompleted(); mThreadPool.shutdown(); } } } catch (RemoteException e) { e.printStackTrace(); } } }, 2000, 50, TimeUnit.MILLISECONDS); } @Override public int getPid() throws RemoteException { Log.i(TAG,"[LocalServerFunc] getPid()"); return android.os.Process.myPid(); } @Override public MyData getMyData() throws RemoteException { Log.i(TAG,"[LocalServerFunc] getMyData()"); return mMyData; } @Override public void registerCallback(IServiceCallback callback) throws RemoteException { Log.i(TAG,"[LocalServerFunc] registerCallback()"); mCallback = callback; } @Override public void unregisterCallback() throws RemoteException { Log.i(TAG,"[LocalServerFunc] unregisterCallback()"); if(mThreadPool != null){ mThreadPool.shutdown(); } mCallback = null; } @Override public IBinder asBinder() { return null; }}
Client端
public class ClientActivity extends AppCompatActivity { private static final String TAG = "BinderSimple"; private IRemoteService mLocalService; private TextView textView; private boolean mIsBound; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_client); textView = (TextView) findViewById(R.id.tv); Button btn0 = (Button) findViewById(R.id.btn_bind); Button btn1 = (Button) findViewById(R.id.btn_unbind); Button btn2 = (Button) findViewById(R.id.btn_kill); btn0.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { bindMyService(); } }); btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { unBindMyService(); } }); btn2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { killMyService(); } }); } private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //注意这个Binder对象转换成IRemoteService接口方式的不同 mLocalService = (IRemoteService) service; try { mLocalService.registerCallback(mLocalServiceCallback); MyData myData = mLocalService.getMyData(); String pidInfo = " servicePid = "+ mLocalService.getPid() + "\n myPid = " + android.os.Process.myPid() + "\n data1 = "+ myData.getData1() + "\n data2 = "+ myData.getData2() + "\n key = "+ myData.getKey(); Log.i(TAG, "[ClientActivity] onServiceConnected\n" + pidInfo); textView.setText(pidInfo); Toast.makeText(ClientActivity.this, "localService 连接成功", Toast.LENGTH_SHORT).show(); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "[ClientActivity] onServiceDisconnected"); mLocalService = null; Toast.makeText(ClientActivity.this, "localService 断开连接", Toast.LENGTH_SHORT).show(); } }; //服务回调,注意这个对象的不同 private IServiceCallback mLocalServiceCallback = new IServiceCallback() { @Override public void onDownloadProgress(final double progress) throws RemoteException { Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { textView.setText("下载进度:" + progress); } }); } @Override public void onDownloadCompleted() throws RemoteException { Log.i(TAG, "[ClientActivity] mServiceCallback -> onDownloadCompleted"); Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { textView.setText("下载完成"); } }); } @Override public IBinder asBinder() { return null; } }; /** * 绑定服务 */ private void bindMyService(){ Log.i(TAG, "[ClientActivity] bindMyService"); Intent intent = new Intent(ClientActivity.this, LocalService.class); intent.setAction(IRemoteService.class.getName()); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); mIsBound = true; textView.setText("bind success"); } /** * 解除绑定服务 */ private void unBindMyService(){ if(!mIsBound){ return; } Log.i(TAG, "[ClientActivity] unBindMyService"); unbindService(mServiceConnection); mIsBound = false; textView.setText("unbind"); } /** * 杀死服务 */ private void killMyService(){ Log.i(TAG, "[ClientActivity] killMyService"); try { android.os.Process.killProcess(mLocalService.getPid()); textView.setText("kill success"); } catch (RemoteException e) { e.printStackTrace(); Toast.makeText(ClientActivity.this, "kill failed", Toast.LENGTH_SHORT).show(); } }}
运行
因为这个Acitivy和Sevice位于同一进程,所以当点击KILL按钮杀死Service进程时,Activity也会同时被杀掉,所以可以看到动画最后就退出了App。
从左往右,依次点击三个按钮,可得:
简单看AIDL的原理
IRemoteService.aidl
文件和IServiceCallback.aidl
文件生成的接口文件分别如下:
#IRemoteService.java
/* * This file is auto-generated. DO NOT MODIFY. */public interface IRemoteService extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.cuc.myandroidtest.IRemoteService { private static final java.lang.String DESCRIPTOR = "com.cuc.myandroidtest.IRemoteService"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.cuc.myandroidtest.IRemoteService interface, * generating a proxy if needed. */ public static com.cuc.myandroidtest.IRemoteService asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.cuc.myandroidtest.IRemoteService))) { return ((com.cuc.myandroidtest.IRemoteService) iin); } return new com.cuc.myandroidtest.IRemoteService.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getPid: { data.enforceInterface(DESCRIPTOR); int _result = this.getPid(); reply.writeNoException(); reply.writeInt(_result); return true; } case TRANSACTION_getMyData: { data.enforceInterface(DESCRIPTOR); com.cuc.myandroidtest.MyData _result = this.getMyData(); reply.writeNoException(); if ((_result != null)) { reply.writeInt(1); _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0); } return true; } case TRANSACTION_registerCallback: { data.enforceInterface(DESCRIPTOR); com.cuc.myandroidtest.IServiceCallback _arg0; _arg0 = com.cuc.myandroidtest.IServiceCallback.Stub.asInterface(data.readStrongBinder()); this.registerCallback(_arg0); reply.writeNoException(); return true; } case TRANSACTION_unregisterCallback: { data.enforceInterface(DESCRIPTOR); this.unregisterCallback(); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.cuc.myandroidtest.IRemoteService { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public int getPid() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public com.cuc.myandroidtest.MyData getMyData() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); com.cuc.myandroidtest.MyData _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getMyData, _data, _reply, 0); _reply.readException(); if ((0 != _reply.readInt())) { _result = com.cuc.myandroidtest.MyData.CREATOR.createFromParcel(_reply); } else { _result = null; } } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void registerCallback(com.cuc.myandroidtest.IServiceCallback callback) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeStrongBinder((((callback != null)) ? (callback.asBinder()) : (null))); mRemote.transact(Stub.TRANSACTION_registerCallback, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public void unregisterCallback() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_unregisterCallback, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_getPid = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_getMyData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_registerCallback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); static final int TRANSACTION_unregisterCallback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); } public int getPid() throws android.os.RemoteException; public com.cuc.myandroidtest.MyData getMyData() throws android.os.RemoteException; public void registerCallback(com.cuc.myandroidtest.IServiceCallback callback) throws android.os.RemoteException; public void unregisterCallback() throws android.os.RemoteException;}
#IServiceCallback.java
/* * This file is auto-generated. DO NOT MODIFY. */public interface IServiceCallback extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.cuc.myandroidtest.IServiceCallback { private static final java.lang.String DESCRIPTOR = "com.cuc.myandroidtest.IServiceCallback"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.cuc.myandroidtest.IServiceCallback interface, * generating a proxy if needed. */ public static com.cuc.myandroidtest.IServiceCallback asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.cuc.myandroidtest.IServiceCallback))) { return ((com.cuc.myandroidtest.IServiceCallback) iin); } return new com.cuc.myandroidtest.IServiceCallback.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_onDownloadProgress: { data.enforceInterface(DESCRIPTOR); double _arg0; _arg0 = data.readDouble(); this.onDownloadProgress(_arg0); reply.writeNoException(); return true; } case TRANSACTION_onDownloadCompleted: { data.enforceInterface(DESCRIPTOR); this.onDownloadCompleted(); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.cuc.myandroidtest.IServiceCallback { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public void onDownloadProgress(double progress) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeDouble(progress); mRemote.transact(Stub.TRANSACTION_onDownloadProgress, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } //下载进度 @Override public void onDownloadCompleted() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_onDownloadCompleted, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_onDownloadProgress = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_onDownloadCompleted = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } public void onDownloadProgress(double progress) throws android.os.RemoteException; //下载进度 public void onDownloadCompleted() throws android.os.RemoteException;}
参考资料
- Android 接口定义语言 (AIDL) - Google Developer文档
- Binder系列9—如何使用AIDL
- Android跨进程bindService与callback
- 在Activity和Service之间使用本地Binder和回调接口进行通信
阅读全文
0 0
- Android Binder -什么是binder
- Android Binder
- Android Binder
- android Binder
- Android-binder
- android Binder
- Android--Binder
- android binder
- Android Binder
- android binder
- android: binder
- 【Android】Binder
- Android BInder
- android binder
- android Binder
- Android Binder
- android binder
- Android Binder
- Max number of threads (maxWaitQueueSize) of 500 has been exceeded
- Uncaught TypeError: jQuery.handleError is not a function
- form outline submit
- 学习python(基础)
- poj 基础入门题
- 【Android】Binder
- linux 建立自启动脚本的两种方法
- 查看oracle数据库的数据文件的目录
- 使用命令行工具,如何创建nicelooking DMG的Mac OS X?
- AJax 把拿到的后台数据在页面中渲染
- 【Maven+SSM】如何通过Mybatis写一个中文查询请求及所遇到的问题
- virtualbox虚拟机ubuntu,安装docker,配置javaweb环境,简单实现负载均衡
- 1003 Max Sum
- 性能测试报告