今天要梳理的知识是Android中的IPC机制,由于这一点难度太高又相对重要,所以笔者也是主要参考了一些书才完成了这篇文章。

  • IPC是Inter-Process Communication的缩写,含义是进程间通信或者是进程间通信,是指两个进程之间进行数据交换的过程。

先说一下进程与线程的定义吧
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
所以多进程有什么意义呢?

好处:

  • 分担主进程的内存压力。
    当应用越做越大,内存越来越多,将一些独立的组件放到不同的进程,它就不占用主进程的内存空间了。当然还有其他好处,有心人会发现
  • 使应用常驻后台,防止主进程被杀守护进程,守护进程和主进程之间相互监视,有一方被杀就重新启动它。
    Android后台进程里有很多应用是多个进程的,因为它们要常驻后台,特别是即时通讯或者社交应用,不过现在多进程已经被用烂了。
    典型用法是在启动一个不可见的轻量级私有进程,在后台收发消息,或者做一些耗时的事情,或者开机启动这个进程,然后做监听等。

坏处:

  • 消耗用户的电量。
  • 多占用了系统的空间,若所有应用都这样占用,系统内存很容易占满而导致卡顿。
  • 应用程序架构会变得复杂,因为要处理多进程之间的通信。这里又是另外一个问题了。

其它缺陷:

  • Application的多次重建。
  • 静态成员和单例模式的完全失效。
  • 线程同步机制失效。
  • SharedPreference可靠性下降。

既然它有很多缺点,所以我们就放弃吧

(怎么可能?你在逗我?当然要学习啊!)

下面通过一些小代码来看一下吧
在Android中使用多进程只有一种方法:
就是给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidManifest中指定android:process属性。

<activity  android:name=".MainActivity"  android:configChanges="orientation|screenSize"  android:label="@string/app_name"  android:launchMode="standard" >  <intent-filter>  <action android:name="android.intent.action.MAIN" />  </intent-filter>
</activity>
<activity  android:name=".SecondActivity"  android:configChanges="screenLayout"  android:label="@string/app_name"  android:process=":remote" />
<activity  android:name=".ThirdActivity"  android:configChanges="screenLayout"  android:label="@string/app_name"  android:process="com.ryg.chapter_2.remote" />  

上面的代码中,
(1)MainActivity没有指定process属性,所以它运行在默认的进程中,默认进程的进程名是包名。
(2)SecondActivity会运行在一个单独的进程中,进程名为“com.ryg.chapter_2:remote”,其中com.ryg.chapter_2是包名。在程序中的冒号“:”的含义是指要在当前的进程名前面附加上当前的包名,是一种简写的方法。而且以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。
(3)ThirdActivity会运行在另一个单独的进程中,进程名为“com.ryg.chapter_2.remote”。这是一种完整的命名方式。属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。
进程也创建好了,下面就是通信的内容了
在Android中最有特色的进程间通信方式就是Binder了,通过Binder可以轻松地实现进程间通信。
当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializeble。Serializable和Parcelable接口可以完成对象的序列化过程。还有时候我们需要把对象持久化到存储 设备上或者通过网络传输给其他客户端,这个时候也需要Serializable来完成对象的持久化。
http://blog.csdn.net/callmesp/article/details/54632139 在我的这篇博客里面有使用过Serializable,我们就不再讲解了。我们就说一下Parcelable吧。

  • Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。

看一个小demo

public class User implements Parcelable {  public int userId;  public String userName;  public boolean isMale;  public Book book;  public User(int userId, String userName, boolean isMale) {  this.userId = userId;  this.userName = userName;  this.isMale = isMale;  }  /* * 内容描述功能几乎都是直接返回0的。 * */  public int describeContents() {  return 0;  }  /* * 序列化由writeToParcel方法来完成,最终是通过Parcel中一系列write方法来完成的。 * 其中flags标识有两种值:0和1(PARCELABLE_WRITE_RETURN_VALUE)。 * 为1时标识当前对象需要作为返回值返回,不能立即释放资源, * 几乎所有情况都为0。 * */  public void writeToParcel(Parcel out, int flags) {  out.writeInt(userId);  out.writeString(userName);  out.writeInt(isMale? 1:0);  out.writeParcelable(book, 0);  }  /* * 反序列化功能是由CREATOR来完成,其内部标明了如何创建序列化对象和数组, * 并通过Parcel的一些了read方法来完成反序列化过程。 * */  public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {  // 从序列化后的对象中创建原始对象。  public User createFromParcel(Parcel in) {  return new User(in);  }  // 创建指定长度的原始对象数组  public User[] newArray(int size) {  return new User[size];  }  };  /* * Parcel内部包装了可序列化的数据,可以在Binder中自由传输。 * 从序列化后的对象中创建原始对象。 * */  private User(Parcel in) {  userId = in.readInt();  userName = in.readString();  isMale = in.readInt() == 1;  /* * 由于book是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文类加载器, * 否则会报无法找到类的错误。 * */  book = in.readParcelable(Thread.currentThread().getContextClassLoader());  }
}  

既然Parcelable和Serializable都能实现系列化并且都可用于Intent间的数据传递,该如何选取呢?

  • Serializable用起来简单,但开销很大,序列化和反序列化过程都需要大量的I/O操作。
  • Parcelable是Android中的序列化方式,更适合在Android平台上使用,用起来比较麻烦,效率很高,首选。主要用在内存序列化上。

接下来就到了我们的重头戏Binder:

  • Binder实现了IBinder接口
  • 从IPC角度来说,Binder是Android中的一种跨进程通信方式。Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,这种通信方式在Linux中没有。

  • 从Android
    Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁。

  • 从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

  • AIDL即Android interface definition Language,即Android接口定义语言。
    在分析Binder的工作原理之前,我们先补充一下Android设计模式之Proxy模式

Proxy代理模式简介

  • 代理模式是对象的结构模式。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
  • 模式的使用场景:就是一个人或者机构代表另一个人或者机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
  • 抽象对象角色AbstarctObject:声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。

    目标对象角色RealObject:定义了代理对象所代表的目标对象。

    代理对象角色ProxyObject:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。
    Proxy代理模式的简单实现
    抽象对象角色:

public abstract class AbstractObject {  //操作  public abstract void operation();
}  

目标对象角色:

public class RealObject extends AbstractObject {  @Override  public void operation() {  //一些操作  System.out.println("一些操作");  }
}  

代理对象角色:

public class ProxyObject extends AbstractObject{  RealObject realObject = new RealObject();//目标对象角色  @Override  public void operation() {  //调用目标对象之前可以做相关操作  System.out.println("before");          realObject.operation();        //目标对象角色的操作函数  //调用目标对象之后可以做相关操作  System.out.println("after");  }
}  

客户端:

public class Client {  public static void main(String[] args) {  AbstractObject obj = new ProxyObject();  obj.operation();  }
}  

我们通过一个案例来分析Binder工作原理

我们需要新建一个AIDL示例,SDK会自动为我们生产AIDL所对应的Binder类。
(1)Book.java:这里面没有什么特殊之处,为了实现Parcelable,添加了几个方法,上面在Parcelable部分已经介绍过了。

package com.ryg.chapter_2.aidl;  import android.os.Parcel;
import android.os.Parcelable;  /* * (1)它是一个表示图示信息的类, * 它实现了Parcelable接口,因为实现了Parcelable接口便可以进行序列化 * (2)Book.aidl是Book类在ADIL中的声明。 * (3)IBookManager.aidl是我们定义的一个接口,里面有两个方法:getBookList和addBook, * 其中getBookList用于从远程服务端获取图书列表,而addBook用于往图书列表中添加一本书, * 当然这两个方法主要是示例用,不一定要有实际意义。 * (4)尽管Book类和IBookManager位于相同的包中,但是在IBookManager中仍然要导入Book类, * 这就是AIDL的特殊之处。 * */
public class Book implements Parcelable {      public int bookId;  public String bookName;      /* * 普通构造函数: * */  public Book() {      }       /* * 普通构造函数: * */  public Book(int bookId, String bookName) {  this.bookId = bookId;  this.bookName = bookName;  }  public int describeContents() {  return 0;  }  /* * 序列化: * */  public void writeToParcel(Parcel out, int flags) {  out.writeInt(bookId);  out.writeString(bookName);  }      /* * 反序列化, * 这个creator就是通过一个Parcle来创建一个book对象或者数组。 * */  public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {  public Book createFromParcel(Parcel in) {  return new Book(in);  }          public Book[] newArray(int size) {  return new Book[size];  }  };      /* * 用于反序列化的构造函数: * */  private Book(Parcel in) {  bookId = in.readInt();  bookName = in.readString();  }  @Override  public String toString() {  return String.format("[bookId:%s, bookName:%s]", bookId, bookName);  }
}  

(2)Book.aidl:它是Book在AIDL中的声明。

package com.ryg.chapter_2.aidl;  parcelable Book;  

(3)IBookManager.aidl:虽然Book类已经和IBookManager位于相同的包中,但是这里依然需要导入Book类。这是AIDL的特殊之处。
它是一个接口,里面有四个方法。

package com.ryg.chapter_2.aidl;  import com.ryg.chapter_2.aidl.Book;
import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener;  interface IBookManager {  List<Book> getBookList();  void addBook(in Book book);  void registerListener(IOnNewBookArrivedListener listener);  void unregisterListener(IOnNewBookArrivedListener listener);
}  

(4)下面我们要看一下系统为IBookManager.aidl生产的Binder类,在gen目录下有一个IBookManager.java的类,这就是我们要找的类。

/* * This file is auto-generated.  DO NOT MODIFY. */
package com.ryg.chapter_2.aidl;
/* * IBookManager它继承了IInterface这个接口,同时它自己也还是个接口, * 所有可以在Binder中传输的接口都要继承IInterface接口。 * 首先,它声明了两个方法getBookList和addBook,显然这就是我们在IBookManager.aidl中所声明的方法, * 同时它还声明了两个整型的id分别用于标识这两个方法。 * 接着,它声明了一个内部类Stub,这个Stub就是一个Binder类, * 当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程, * 而当两者位于不同进程时,方法调用需要走transact过程, * 这个逻辑由Stub的内部代理类Proxy来完成。 * */
public interface IBookManager extends android.os.IInterface
{  /** Local-side IPC implementation stub class. */  /* * 首先这个Stub,它是一个内部类,它继承了Binder,所以它是一个Binder, * 同时Stub还实现了IBookManager中的方法。 * */  public static abstract class Stub extends android.os.Binder implements com.ryg.chapter_2.aidl.IBookManager  {  /* * Binder的唯一标识符。 * */  private static final java.lang.String DESCRIPTOR = "com.ryg.chapter_2.aidl.IBookManager";  /** Construct the stub at attach it to the interface. */  public Stub()  {  this.attachInterface(this, DESCRIPTOR);  }  /** * Cast an IBinder object into an com.ryg.chapter_2.aidl.IBookManager interface, * generating a proxy if needed. */  /* * 用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象, * 这种转换过程是区分进程的, * 如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身, * 否则返回的是系统封装后的Stub.proxy代理对象。 * */  public static com.ryg.chapter_2.aidl.IBookManager asInterface(android.os.IBinder obj)  {  if ((obj==null)) {  return null;  }  android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);  // 同一进程  if (((iin!=null)&&(iin instanceof com.ryg.chapter_2.aidl.IBookManager))) {  return ((com.ryg.chapter_2.aidl.IBookManager)iin);  }  // 不同进程  return new com.ryg.chapter_2.aidl.IBookManager.Stub.Proxy(obj);  }  /* * 此方法用于返回当前Binder对象,也就是内部类Stub。 * */  @Override public android.os.IBinder asBinder()  {  return this;  }  /* * 这个方法运行在服务端中的Binder线程池中, * 当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。 * 服务端通过code可以确定客户端所请求的目标方法是什么, * 接着从data中取出目标方法所需的参数, * 然后执行目标方法。 * 当目标方法执行完毕后,就向reply中写入返回值。 * 如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证。 * */  @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_getBookList:  {  data.enforceInterface(DESCRIPTOR);  /* * 这句才是调用了真正的执行过程呢 * */  java.util.List<com.ryg.chapter_2.aidl.Book> _result = this.getBookList();  reply.writeNoException();  reply.writeTypedList(_result);  return true;  }  case TRANSACTION_addBook:  {  data.enforceInterface(DESCRIPTOR);  com.ryg.chapter_2.aidl.Book _arg0;  if ((0!=data.readInt())) {  _arg0 = com.ryg.chapter_2.aidl.Book.CREATOR.createFromParcel(data);  }  else {  _arg0 = null;  }  /* * 这句才是调用了真正的执行过程呢 * */  this.addBook(_arg0);  reply.writeNoException();  return true;  }  case TRANSACTION_registerListener:  {  data.enforceInterface(DESCRIPTOR);  com.ryg.chapter_2.aidl.IOnNewBookArrivedListener _arg0;  _arg0 = com.ryg.chapter_2.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());  this.registerListener(_arg0);  reply.writeNoException();  return true;  }  case TRANSACTION_unregisterListener:  {  data.enforceInterface(DESCRIPTOR);  com.ryg.chapter_2.aidl.IOnNewBookArrivedListener _arg0;  _arg0 = com.ryg.chapter_2.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());  this.unregisterListener(_arg0);  reply.writeNoException();  return true;  }  }  return super.onTransact(code, data, reply, flags);  }  /* * 代理类Proxy。 * */  private static class Proxy implements com.ryg.chapter_2.aidl.IBookManager  {  /* * 这个mRemote代表的就是目标对象角色, * */  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;  }  /* * 这个方法运行在客户端, * 因为当客户端和服务端不在同一进程时,服务端返回代理类Proxy,所以客户端会通过Proxy调用到代理类的getBookList方法, * 当客户端远程调用此方法时,它的内部实现是这样的: * 首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List, * 然后把该方法的参数信息写入_data中, * 接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起, * 然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行, * 并从_reply中取出RPC过程的返回结果。 * 最后返回_reply中的数据。 * */  @Override public java.util.List<com.ryg.chapter_2.aidl.Book> getBookList() throws android.os.RemoteException  {  android.os.Parcel _data = android.os.Parcel.obtain();  android.os.Parcel _reply = android.os.Parcel.obtain();  java.util.List<com.ryg.chapter_2.aidl.Book> _result;  try {  _data.writeInterfaceToken(DESCRIPTOR);  mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);  _reply.readException();  _result = _reply.createTypedArrayList(com.ryg.chapter_2.aidl.Book.CREATOR);  }  finally {  _reply.recycle();  _data.recycle();  }  return _result;  }  @Override public void addBook(com.ryg.chapter_2.aidl.Book book) throws android.os.RemoteException  {  android.os.Parcel _data = android.os.Parcel.obtain();  android.os.Parcel _reply = android.os.Parcel.obtain();  try {  _data.writeInterfaceToken(DESCRIPTOR);  if ((book!=null)) {  _data.writeInt(1);  book.writeToParcel(_data, 0);  }  else {  _data.writeInt(0);  }  mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);  _reply.readException();  }  finally {  _reply.recycle();  _data.recycle();  }  }  @Override public void registerListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) 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((((listener!=null))?(listener.asBinder()):(null)));  mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);  _reply.readException();  }  finally {  _reply.recycle();  _data.recycle();  }  }  @Override public void unregisterListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) 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((((listener!=null))?(listener.asBinder()):(null)));  mRemote.transact(Stub.TRANSACTION_unregisterListener, _data, _reply, 0);  _reply.readException();  }  finally {  _reply.recycle();  _data.recycle();  }  }  }  /* * 用于标识方法的整型id。 * 它们用于在transact过程总客户端所请求的到底是哪个方法。 * */  static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);  static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);  static final int TRANSACTION_registerListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);  static final int TRANSACTION_unregisterListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);  }  /* * 声明了在IBookManager.aidl中所声明的方法。 * 这里才是真正的方法声明。具体实现我们仍然没有看到呢。 * */  public java.util.List<com.ryg.chapter_2.aidl.Book> getBookList() throws android.os.RemoteException;  public void addBook(com.ryg.chapter_2.aidl.Book book) throws android.os.RemoteException;  public void registerListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException;  public void unregisterListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException;
}  

**注意点一:上面的Book类,就是一个可以Parcelable序列化的简单的Book类,它里面没有任何的方法,就是定义了一个简单的Book类结构。
注意点二:Book.aidl的存在是因为在IBookManager.aidl中出现的对象也必须有aidl声明。
注意点三:在IBookManager.aidl中,对于自动生成的IBookManager.java文件,它是服务器端的代码。当客户端向服务端发送连接请求时,如果客户端和服务端在同一进程中,那么服务端就向客户端返回Stub这个Binder对象,如果客户端和服务端在不同进程中,那么服务端就向客户端返回内部类Stub的内部代理类Proxy,然后客户端根据这个Proxy来调用Proxy内部的方法,这个Proxy内部含有服务端真正的Binder对象也就是那个内部类Stub,在客户端调用Proxy内部的方法也就会导致调用Stub的transact方法,而Stub的transact方法又会回调它自己的onTransact方法,onTransact方法是在服务端运行的,而transact方法是在客户端调用的,这样就实现了客户端调用服务端的方法了。当然这所有的传递过程也少不了Parcel这个数据包的协助。**

春风十里不如你、与IPC的邂逅相关推荐

  1. 春风十里_C调简单版_酷音小伟

    教学视频 <春风十里>简单版吉他弹唱教学 酷音小伟吉他教学 谱子

  2. 《春风十里不如你》身体骚动的魔性,精神成长的传记

    最近关于<春风十里不如你>的结局引起了不少网友的关注,这部由周冬雨和张一山主演的青春爱情电视剧一上映就吸引了不少观众的视线 这是周冬雨和张一山的首次合作,张一山凭借<余罪>摆脱 ...

  3. python画图绘制紫荆花_春风十里,紫荆花满城

    我所在的城市,除了特色食物外,和它一样出名的还有开满大街小巷的洋紫荆.每年的三四月,整座城市放眼望去的尽是粉白.淡紫的花瓣,弥漫着淡淡花香.正可谓,春风十里,你在花海里. 落英缤纷的梦幻 虽是生活在这 ...

  4. 计算机弹奏春风十里报新年,春风吹十里,好运新一年〈春风十里报新年〉接个吻,开一枪/火鸡/吕口口/Lambert/杨胖雨「尤克里里+吉他弹唱谱」...

    原标题:春风吹十里,好运新一年〈春风十里报新年〉接个吻,开一枪/火鸡/吕口口/Lambert/杨胖雨「尤克里里+吉他弹唱谱」 春风十里报新年 接个吻,开一枪/火鸡/吕口口/Lambert/杨胖雨 ○〈 ...

  5. 杂记 -春风十里不如你

    今天是她的生日,我再次踏上了去往北京的路上...... 一路上的风景,我已经看过很多遍,今天的感觉有一种安心的情绪在里面,可能遇到了云后,发现自己对于家的理解又有了一层新的感官,而不是之前的通俗.在北 ...

  6. 春水初生,春林初盛,春风十里,不如你

    每日心情签名 01 人生最可怕的是,知道得太多,明白得太晚. 02 一个人最大的缺点不是自私,多情,野蛮,任性,而是偏执地爱一个不爱自己的人. 03 最辛苦的不是两地相隔,而是明明相爱,却不敢想未来. ...

  7. 春风十里计算机谱子,春风十里曲谱

    "我说所有的酒,都不如你",鹿先森乐队首支单曲<春风十里>.只是想说,喝再多的酒,在心里还是会有一个人更让你沉醉. 乐曲背景 "我说所有的酒,都不如你&quo ...

  8. 【时光纪念】春风十里,不如你

    [春·望] 光阴荏苒.白驹过隙.似水流年.席不暇暖--都已经无法表达我所感受到的时间飞逝了.就只觉得,前些日子刚刚落笔年度总结,今天就又到了半年总结的提笔时. 快的猝不及防,脑海里闪过一个又一个场景, ...

  9. 【Java二十周年】春风十里,不如你

    看到这个题材突然内心肿胀无限,有强烈的倾诉冲动.今夜我不关心人类,我只想你. 上中学时我还不认识你,java.初一时家里买了电脑,但是除了红警之外仅存的记忆就是163.169拨号的滴滴声再无其他:真正 ...

  10. 春风十里,不如一起看数据的你!

    今夏虐剧之首"春风十里不如你"终于虐到尽头了! "春风迷"一边被虐一边又享受着追剧的快乐. 那么"春风迷"究竟是怎样的一个群体,有什么特别的 ...

最新文章

  1. 在阿里干了五年,面试个小公司挂了…
  2. Hibernate的generator属性
  3. 在O(1)的时间内计算n个整数落在区间[a,b]的个数(预处理时间为O(n+k))
  4. mediumtext和string转换_数据库用varchar和text的差别
  5. Myesclipe+SSH+jsp+mysql+tomcate实现一个简单的CRM客户关系管理系统
  6. 日是这一年的等几天Java代码_java中计算指定日期是一年的第几天的方法
  7. 奇异值分解SVD(证明全部省略)
  8. pyqt5实时动态曲线
  9. matlab lyap,Matlab的Lyapunov、Sylvester和Riccati方程的Matlab求解
  10. 金山办公:2021年净利润10.42亿元,同比增长19%
  11. web的case点总结
  12. php-java-bridge 作用_PHP-Java-Bridge的使用(平安银行支付功能专版)
  13. anaconda moviepy_Anaconda使用之安装篇(Windows)
  14. 网络摄像头的地盘争夺战——四款僵尸软件的技术解析
  15. 什么是Ruby on Rails?
  16. 冰点文库下载器的使用
  17. 计算机毕业设计之会议预约系统设计与实现
  18. 软件外包公司到底干啥的?要不要去外包公司?
  19. 计算机也无法解的函数,XP提示“无法访问函数不正确”怎么办|XP提示“函数不正确”的四种解决方案...
  20. C#渐变色方法 实例

热门文章

  1. 在word “打钩” √
  2. 浏览器User-Agent大全
  3. 调度程序所用数据结构—Linux
  4. Python实现小说下载器,可以打包exe
  5. h5输入框提示语 正常文本框提示语
  6. shell 四种循环详解
  7. 王者荣耀账号转服务器,王者荣耀账号如何跨系统转移
  8. Spring Boot入门系列(六)Spring Boot如何整合Mybatis【附详细步骤】
  9. 【随机数生成算法系列】线性同余法和梅森旋转法
  10. 深度学习之选择GPU或CPU方法