ps:阅读本文 需要对android 多进程编程有一定了解。

1.Android中总共有几种方式进行IPC?

答:一共有两种,一种是binder 还有一种是socket。Binder 大家用的比较多。Socket很少有人用,这里给出一个利用Socket进行ipc通信的例子。

服务端代码:

 1 package com.example.administrator.socketipcdemo; 2  3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 import android.util.Log; 7  8 import java.io.BufferedReader; 9 import java.io.BufferedWriter;10 import java.io.IOException;11 import java.io.InputStreamReader;12 import java.io.OutputStreamWriter;13 import java.io.PrintWriter;14 import java.net.ServerSocket;15 import java.net.Socket;16 17 public class ServerService extends Service {18 19     public static final int SERVER_PORT_NUMBER = 5678;20     private boolean mServiceDestroyed = false;21 22     @Override23     public void onCreate() {24         new Thread(new ServerRunnable()).start();25         super.onCreate();26     }27 28     @Override29     public void onDestroy() {30         mServiceDestroyed = true;31         super.onDestroy();32     }33 34     public ServerService() {35     }36 37     @Override38     public IBinder onBind(Intent intent) {39         // TODO: Return the communication channel to the service.40         throw new UnsupportedOperationException("Not yet implemented");41     }42 43     //这个线程 用于监听端口号44     class ServerRunnable implements Runnable {45         @Override46         public void run() {47 48             ServerSocket serverSocket = null;49             try {50                 serverSocket = new ServerSocket(SERVER_PORT_NUMBER);51             } catch (IOException e) {52                 e.printStackTrace();53             }54             while (!mServiceDestroyed) {55                 try {56                     final Socket client = serverSocket.accept();57                     new Thread() {58                         @Override59                         public void run() {60                             try {61                                 responseClientRequest(client);62                             } catch (IOException e) {63                                 e.printStackTrace();64                             }65                         }66                     }.start();67 68                 } catch (IOException e) {69                     e.printStackTrace();70                 }71             }72         }73     }74 75     private void responseClientRequest(Socket client) throws IOException {76         BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));77         PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);78         while (!mServiceDestroyed) {79             String str = in.readLine();80             if (str == null) {81                 break;82             }83             String reponseStr = "我是服务器端,我收到了来自客户端的消息:" + str;84             out.println(reponseStr);85         }86         out.close();87         in.close();88         client.close();89     }90 }
View Code

客户端代码:

  1 package com.example.administrator.socketipcdemo;  2   3 import android.content.Intent;  4 import android.os.Bundle;  5 import android.os.SystemClock;  6 import android.support.design.widget.FloatingActionButton;  7 import android.support.design.widget.Snackbar;  8 import android.support.v7.app.AppCompatActivity;  9 import android.support.v7.widget.Toolbar; 10 import android.util.Log; 11 import android.view.View; 12 import android.view.Menu; 13 import android.view.MenuItem; 14 import android.widget.Button; 15 import android.widget.EditText; 16 import android.widget.TextView; 17 import android.widget.Toast; 18  19 import java.io.BufferedReader; 20 import java.io.BufferedWriter; 21 import java.io.IOException; 22 import java.io.InputStreamReader; 23 import java.io.OutputStreamWriter; 24 import java.io.PrintWriter; 25 import java.net.Socket; 26  27 public class MainActivity extends AppCompatActivity { 28  29     private Button bt, bt2; 30     private TextView tv; 31     private EditText et; 32  33     private Socket socket; 34     private PrintWriter mPrintWriter; 35  36  37     @Override 38     protected void onCreate(Bundle savedInstanceState) { 39         super.onCreate(savedInstanceState); 40         setContentView(R.layout.activity_main); 41         Intent intent = new Intent(this, ServerService.class); 42         startService(intent); 43         tv = (TextView) findViewById(R.id.tv); 44         et = (EditText) findViewById(R.id.et); 45         bt = (Button) this.findViewById(R.id.bt); 46         bt.setOnClickListener(new View.OnClickListener() { 47             @Override 48             public void onClick(View v) { 49                 new Thread() { 50                     @Override 51                     public void run() { 52                         tryToLinkServer(); 53                     } 54                 }.start(); 55  56             } 57         }); 58  59         bt2 = (Button) this.findViewById(R.id.bt2); 60         bt2.setOnClickListener(new View.OnClickListener() { 61             @Override 62             public void onClick(View v) { 63                 sendMessageToServer(et.getText().toString()); 64             } 65         }); 66  67         Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 68         setSupportActionBar(toolbar); 69  70         FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 71         fab.setOnClickListener(new View.OnClickListener() { 72             @Override 73             public void onClick(View view) { 74                 Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 75                         .setAction("Action", null).show(); 76             } 77         }); 78     } 79  80     @Override 81     protected void onDestroy() { 82         if(socket!=null) 83         { 84             try { 85                 socket.shutdownInput(); 86                 socket.close(); 87             } catch (IOException e) { 88                 e.printStackTrace(); 89             } 90         } 91         super.onDestroy(); 92     } 93  94     @Override 95     public boolean onCreateOptionsMenu(Menu menu) { 96         // Inflate the menu; this adds items to the action bar if it is present. 97         getMenuInflater().inflate(R.menu.menu_main, menu); 98         return true; 99     }100 101     @Override102     public boolean onOptionsItemSelected(MenuItem item) {103         // Handle action bar item clicks here. The action bar will104         // automatically handle clicks on the Home/Up button, so long105         // as you specify a parent activity in AndroidManifest.xml.106         int id = item.getItemId();107 108         //noinspection SimplifiableIfStatement109         if (id == R.id.action_settings) {110             return true;111         }112 113         return super.onOptionsItemSelected(item);114     }115 116     private void sendMessageToServer(final String str) {117         if (mPrintWriter != null) {118             mPrintWriter.println(str);119         }120     }121 122 123     private void tryToLinkServer() {124         while (socket == null) {125             try {126                 socket = new Socket("localhost", ServerService.SERVER_PORT_NUMBER);127                 mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);128             } catch (IOException e) {129                 //每次连不上就等待1秒再重新连130                 SystemClock.sleep(1000);131                 e.printStackTrace();132             }133         }134         runOnUiThread(new Runnable() {135             @Override136             public void run() {137                 Toast.makeText(MainActivity.this, "和服务器进程成功链接", Toast.LENGTH_SHORT).show();138             }139         });140         try {141             BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));142             while (!MainActivity.this.isFinishing()) {143                 final String msg = br.readLine();144                 runOnUiThread(new Runnable() {145                     @Override146                     public void run() {147                         tv.setText(msg);148                     }149                 });150             }151             mPrintWriter.close();152             br.close();153             socket.close();154         } catch (IOException e) {155             e.printStackTrace();156         }157     }158 159 160 }
View Code

最后看一下运行效果:

2.android:process 属性值的书写有什么讲究?

答::remote 这种写法 其实就等于 包名:remote 而且这种写法 属于当前app的私有进程, 其他app 不可以和这个进程跑在一起的。

如果是直接 把android:process 写成字符串形式的话, 那这种进程就是全局进程了,谁都可以用。哪怕是其他app 只要是shareUid一样

就可以和这个进程跑在一起。当然了,签名也必须一样!

3.多进程编程的时候 我们需要注意哪些坑?

答:一定要记住 多进程的时候 实际上 一个进程就是一个虚拟机,所以 会引发如下几个问题:

a:所有静态变量和单例模式之类的 全部没用了。因为你多进程么,假设你的app代码里面 开启了2个进程,同时你的代码里面 有一个静态变量,这个时候注意 你的静态变量虽然在代码里只有一份,

但是此时是2个进程在跑 ,有2个虚拟机,所以你这个静态变量也是会变成两份的!

b:SharedPreferences 会失去同步效果。因为sp这个东西 你们看下源码就知道了,实际上他的底层就是个xml。。。并且android 对这个xml的读写自己弄了一套缓存,注意这个缓存是运行在内存中的,

所以你就知道 一旦多个进程的话,这个缓存就是多份了。。。。所以记住 不要在多进程里面使用sp

c:Application 会创建多次。这个你们打下log就知道了,所以如果你们application 有特殊处理的话 记得考虑下多进程的情况。

上面3个是我们需要注意的点,其实也很好理解,对于同一个app的 多个进程来说 ,他就等于 多个app 在运行,只不过这些app的uid和签名都一样!你想明白这点 就懂了android 多进程了。

4.android中有哪几种序列化方式?分别要注意什么?

答:两种。Serializable和Parcelable

Serializable:这种方式 就是要注意serialVersionUID,一般ide 给你生成的值 都是根据你class的结构来做的,假设你在版本1.0.1 里面 创建了class A,并且序列化了一个对象a 在sd卡里。

过了一段时间 你在1.0.2 版本里 把A的代码 改了很多,此时ide 会把你的serialVersionUID 值也随之改掉。那如果你在1.0.2版本里 还想反序列化这个对象a的话 就会出错了。

所以serialVersionUID 就是一个记录class 版本号的标示,我们最好还是把他写死。。。。这样不会影响我们的客户端。此外 transient 这个标记的变量也不会被序列化。

Parcelable:write方法就是序列化的时候 执行的方法 creator 就是反序列化的过程。Intent bundle bitmap 都实现了这个接口。List Map 也可以序列化 提前是 元素是这个接口对象。

两者基本上是差不多的,只不过Parcelable效率更高,一般在内存中使用时 我们用Parcelable,如果是 序列化到本地或者网络 这种流里的时候 我们就用Serializable 因为比较方便。

5.当遇到一个数据 无法放在Bundle里时,如何进行进程间通讯?

答:假设我们有一个需求,让A进程 计算一个东西以后 把 结果 result 传递给B进程。但是这个result结果 没有实现Parcelable 接口也无法放在bundle里。此时 该如何传递这个result到B?

其实可以让A进程 启动B进程 的一个intentService。让这个service 去计算出来这个结果result,这时候 我们会发现 此时这个service和b进程 在一个进程里。 result当然就随便传递了。不需要ipc来传递

6.Messenger 使用时,要注意哪些,他的原理是什么?

答:原理的话 其实看一下 源码就知道了:

1 /**2      * Create a Messenger from a raw IBinder, which had previously been3      * retrieved with {@link #getBinder}.4      * 5      * @param target The IBinder this Messenger should communicate with.6      */7     public Messenger(IBinder target) {8         mTarget = IMessenger.Stub.asInterface(target);9     }

一看到这个代码我们就明白 原理就是AIDL么。要注意的话 就是Messenger只能用于单向通信,不能用于双向的 。如果你要用双向通信并且还要用Messenger来完成的话 就必须用2个Messenger。

Messenger传递对象就是传递的Message对象。其中我们注意了,在进程通信的时候,Message里的object 字段 只能传递 系统的parceable对象。也就是intent bitmap 这种系统自己实现的parceable接口对象。

如果是我们自己写的parceable 是无法放在object里面 用于进程通信的,如果你要用,请放在bundle里。此外Messenger 在内部 是串行处理请求的,大量消息 一起发给服务端这种业务模型

最好就别用Messenger了。直接自己写一个AIDL 最合适。

7.使用AIDL的时候 有哪些注意事项?

答:有下面几个注意事项:

a:aidl文件里不是所有数据类型都可以使用。只支持 基本数据类型,string,charSequence,List(只支持ArrayList,且里面的元素都能被AIDL支持),Map(只支持hashMap,并且元素必须是AIDL支持的),Parcelable,AIDL自己。

b:自定义的parceable对象要在aidl中 import 进来。并且自定义的parceable 要在aidl里 也额外声明一下,具体的看我前面的那篇blog即可。

c:aidl 里面只能写方法 不能写静态常量。

8.in out inout 这三个参数 在aidl 到底有啥用,不写有什么危害么?

答:据我观察 你不写其实没什么危害,但是你写明白了 传输效率会增加。其实这3个参数很好理解,简单建立一个方法模型:

假设你的aidl文件里有一个方法 void addPerson(Person person),这方法一看 就是客户端传一个person 到服务端 所以这个地方参数就是in。

如果你有一个方法叫 void getList(List a) 你看这个方法 就是把服务端的list 传递给这个参数a的list 那你就把参数写成out即可。

inout 就更好理解了,实际就是 void modifyList(List b),意思就是 先把b 这个list 传递给服务端,然后还要修改这个list。

9.CopyOnWriteArrayList 可以在aidl中使用吗?为什么?

答:可以使用。

1 public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, Serializable {

虽然看声明 他并没有实现Parceable这个接口。也不是ArrayList的子类,但实际上AIDL支持的是抽象的List,所以在底层的时候 Binder 会把CopyOnWriteArrayList

转换成ArrayList 传递给客户端的。ConcurrentHashMap 也是一样。

10.Android 进程通信时候 能传递对象吗?

答:严格意义上 是不能的,实际上binder 会把客户端传递过来的对象 重新转换为一个新的对象 传递给服务端。对象的跨进程传输 本质上就是序列化和反序列化的过程。

11.RemoteCallBackList 干嘛的?使用时注意哪些?

答:具体可看http://www.cnblogs.com/punkisnotdead/p/5158016.html。 实际上这个东西讲明白了 就是用于 android中管理 跨进程listener接口的。

你想明白这个道理 就都懂了。而且他特别方便的是,客户端进程终止以后 他可以自动删除listenr。使用注意点就是begin和finish要成对出现。就算你是遍历listenrer操作

也要用这2个方法

12.aidl运行时的 线程模型?

答:http://www.cnblogs.com/punkisnotdead/p/5163464.html 这篇分析binder代码的时候 已经讲的很清楚了。这里再总结一次。

client 调用 server的方法a,client本身是会挂起的, 会一直等待server的方法a的 执行结果。

同时这个方法a 是运行在binder线程池中,所以server的方法 可以执行耗时操作。只不过 client调用的server方法时

注意不要在ui线程里调用,不然会anr的。同时如果你的业务是 一个server 多种client ,那你的server端 方法 要支持同步,不然会数据错乱的!

13.服务端 调用 客户端listener 方法有什么讲究?

答:实际上 和12问题是一样的,server 想调用 client的方法b,b也是会执行在 client所属进程的 binder线程池内的。同时我们也要避免 如果方法b执行时间很长,

记得server调用的时候 不要在主线程里调用。此外我们一般client 都是在activity里,记得 listener调用的方法 如果涉及到ui,请用handler切换到ui线程来做。

14.如何做权限认证?

答:两种方法,可以在onBind里 检查权限,也可以在server的onTransact里验证,验证失败返回false即可。后者方法更好 还可以验证uid pid等 可以支持复杂的多进程业务模型,

尤其是安全相关的。

15.一个aidl 就必须对应一个service吗?多种aidl 是不是只能多个service?

答:不是的,aidl 只不过是帮你书写binder用的工具,你当然可以只启动一个service 但是对应多个binder。要知道 service 资源是有限的 假设你app复杂 10个业务 开启10个service

那不是就乱套了。完全可以只开启一个service 但是这一个service 可以控制多种aidl 对应的binder,你要用哪个 就取那个binder 即可。

下面给出实例。

首先看一下 代码结构和运行效果,实际上就是一个加法binder 和一个乘法binder 只不过 这2个binder 都由一个service来控制而已。

然后来看一下加法和乘法的 aidl以及对应的binder 对象:

1 // IAddition.aidl2 package com.example.administrator.bindermanager;3 4 // Declare any non-default types here with import statements5 6 interface IAddition {7 8     int add(int a,int b);9 }
View Code
 1 package com.example.administrator.bindermanager; 2  3 import android.os.RemoteException; 4  5 /** 6  * Created by Administrator on 2016/1/29. 7  */ 8 public class AdditionImpl extends IAddition.Stub { 9     @Override10     public int add(int a, int b) throws RemoteException {11         return a + b;12     }13 }
View Code
1 // IMultiplication.aidl2 package com.example.administrator.bindermanager;3 4 // Declare any non-default types here with import statements5 6 interface IMultiplication {7 8     int multip(int a,int b);9 }
View Code
 1 package com.example.administrator.bindermanager; 2  3 import android.os.RemoteException; 4  5 /** 6  * Created by Administrator on 2016/1/29. 7  */ 8 public class MultiplicationImpl extends IMultiplication.Stub { 9     @Override10     public int multip(int a, int b) throws RemoteException {11         return a * b;12     }13 }
View Code

然后看看我们最为关键的binder manger 如何设计:

1 // IBinderManger.aidl2 package com.example.administrator.bindermanager;3 4 // Declare any non-default types here with import statements5 6 interface IBinderManger {7     IBinder queryBinder(int binderCode);8 }
View Code
 1 package com.example.administrator.bindermanager; 2  3 import android.os.IBinder; 4 import android.os.RemoteException; 5 import android.util.Log; 6  7 /** 8  * Created by Administrator on 2016/1/29. 9  */10 public class BinderMangerImpl extends IBinderManger.Stub {11 12     public BinderMangerImpl() {13         super();14     }15 16     @Override17     public IBinder queryBinder(int binderCode) throws RemoteException {18         IBinder binder = null;19         switch (binderCode) {20             case BinderManger.BINDER_ADDITION:21                 binder = new AdditionImpl();22                 break;23             case BinderManger.BINDER_MULTIPLICATION:24                 binder = new MultiplicationImpl();25                 break;26             default:27                 break;28         }29         return binder;30     }31 }
View Code
 1 package com.example.administrator.bindermanager; 2  3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.Binder; 6 import android.os.IBinder; 7 import android.util.Log; 8  9 public class BinderMangerService extends Service {10 11     private IBinder mBinderManger = new BinderMangerImpl();12 13     public BinderMangerService() {14     }15 16     @Override17     public IBinder onBind(Intent intent) {18         return mBinderManger;19     }20 }
View Code
 1 package com.example.administrator.bindermanager; 2  3 import android.content.ComponentName; 4 import android.content.Context; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.IBinder; 8 import android.os.RemoteException; 9 10 /**11  * Created by Administrator on 2016/1/29.12  */13 public class BinderManger {14 15     public static final int BINDER_ADDITION = 0;16     public static final int BINDER_MULTIPLICATION = 1;17     private Context mContext;18     private IBinderManger mBinderManger;19     private static BinderManger sInstance;20 21     private BinderManger(Context mContext) {22         this.mContext = mContext.getApplicationContext();23         connectBinderService();24     }25 26     public static BinderManger getInstance(Context context) {27         if (sInstance == null) {28             sInstance = new BinderManger(context);29         }30         return sInstance;31     }32 33 34     private synchronized void connectBinderService() {35         Intent service = new Intent(mContext, BinderMangerService.class);36         mContext.bindService(service, mServiceConnection, Context.BIND_AUTO_CREATE);37     }38 39     private ServiceConnection mServiceConnection = new ServiceConnection() {40         @Override41         public void onServiceConnected(ComponentName name, IBinder service) {42             mBinderManger = IBinderManger.Stub.asInterface(service);43         }44 45         @Override46         public void onServiceDisconnected(ComponentName name) {47 48         }49     };50 51     public IBinder queryBinder(int binderCode) throws RemoteException {52         IBinder binder = null;53         binder = mBinderManger.queryBinder(binderCode);54         return binder;55     }56 }
View Code

最后看一下 我们的clinet端:

  1 package com.example.administrator.bindermanager;  2   3 import android.os.Bundle;  4 import android.os.RemoteException;  5 import android.support.design.widget.FloatingActionButton;  6 import android.support.design.widget.Snackbar;  7 import android.support.v7.app.AppCompatActivity;  8 import android.support.v7.widget.Toolbar;  9 import android.view.View; 10 import android.view.Menu; 11 import android.view.MenuItem; 12 import android.widget.Button; 13 import android.widget.EditText; 14 import android.widget.TextView; 15  16 import org.w3c.dom.Text; 17  18 public class MainActivity extends AppCompatActivity { 19  20     private Button bt, bt2, bt3; 21     private EditText et1, et2; 22     private TextView tv; 23     private BinderManger binderManger; 24  25     @Override 26     protected void onCreate(Bundle savedInstanceState) { 27         super.onCreate(savedInstanceState); 28         setContentView(R.layout.activity_main); 29         tv = (TextView) findViewById(R.id.tv2); 30         et1 = (EditText) findViewById(R.id.et); 31         et2 = (EditText) findViewById(R.id.et2); 32         bt = (Button) this.findViewById(R.id.bt); 33         bt.setOnClickListener(new View.OnClickListener() { 34  35             @Override 36             public void onClick(View v) { 37                 binderManger = BinderManger.getInstance(MainActivity.this); 38             } 39         }); 40  41         bt2 = (Button) this.findViewById(R.id.bt2); 42         bt2.setOnClickListener(new View.OnClickListener() { 43  44             @Override 45             public void onClick(View v) { 46                 try { 47                     final IMultiplication multiplicationImpl = 48                             (IMultiplication) MultiplicationImpl.asInterface(binderManger.queryBinder(BinderManger.BINDER_MULTIPLICATION)); 49  50                     runOnUiThread(new Runnable() { 51                         @Override 52                         public void run() { 53                             try { 54                                 tv.setText(multiplicationImpl.multip(Integer.parseInt(et1.getText().toString()), Integer.parseInt(et2.getText().toString())) + ""); 55                             } catch (RemoteException e) { 56                                 e.printStackTrace(); 57                             } 58                         } 59                     }); 60  61                 } catch (RemoteException e) { 62                     e.printStackTrace(); 63                 } 64             } 65         }); 66  67         bt3 = (Button) this.findViewById(R.id.bt3); 68         bt3.setOnClickListener(new View.OnClickListener() { 69  70             @Override 71             public void onClick(View v) { 72                 try { 73                     final IAddition additionImpl = 74                             (IAddition) AdditionImpl.asInterface(binderManger.queryBinder(BinderManger.BINDER_ADDITION)); 75                     runOnUiThread(new Runnable() { 76                         @Override 77                         public void run() { 78                             try { 79                                 tv.setText(additionImpl.add(Integer.parseInt(et1.getText().toString()), Integer.parseInt(et2.getText().toString())) + ""); 80                             } catch (RemoteException e) { 81                                 e.printStackTrace(); 82                             } 83                         } 84                     }); 85  86                 } catch (RemoteException e) { 87                     e.printStackTrace(); 88                 } 89             } 90         }); 91  92  93         Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 94         setSupportActionBar(toolbar); 95  96         FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 97         fab.setOnClickListener(new View.OnClickListener() { 98             @Override 99             public void onClick(View view) {100                 Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)101                         .setAction("Action", null).show();102             }103         });104     }105 106     @Override107     public boolean onCreateOptionsMenu(Menu menu) {108         // Inflate the menu; this adds items to the action bar if it is present.109         getMenuInflater().inflate(R.menu.menu_main, menu);110         return true;111     }112 113     @Override114     public boolean onOptionsItemSelected(MenuItem item) {115         // Handle action bar item clicks here. The action bar will116         // automatically handle clicks on the Home/Up button, so long117         // as you specify a parent activity in AndroidManifest.xml.118         int id = item.getItemId();119 120         //noinspection SimplifiableIfStatement121         if (id == R.id.action_settings) {122             return true;123         }124 125         return super.onOptionsItemSelected(item);126     }127 }
View Code

更多相关文章

  1. Handler, Looper, MessageQueue, Message源码详细分析
  2. setContentView( )方法
  3. Android开发之异步详解(二)之AsyncTask
  4. Android(安卓)APP tcpdump抓包方法
  5. NDK DEBUG方法
  6. Android(安卓)操作蓝牙Ble时数据转换的常用方法
  7. Android(安卓)SharedPreferences存储的正确写法
  8. 【Android】AsyncTask机制
  9. Android(安卓)ContentProvider的实现及简单实例代码

随机推荐

  1. 天欲亡我,非战之罪!怪吾创业前未阅《三国》
  2. 全都是泡沫?勿把“晚轮”错当IPO!
  3. Apple Watch这个产品让我爆血管,但为什么
  4. 拿走,产品经理没个秘密武器哪好意思跟人打
  5. 商业模式的七种严刑逼供方式
  6. 资深VC: VC的“增值平台”就是一坨狗屎
  7. 资深投资人全力反击: VC增值平台从来就不
  8. 深度分析: Google 和 Apple 从来就不是死
  9. 总裁发话: 创业者应该如何避免陷入
  10. 藏不住了,Flink 未来发展的最新方向在这里