内容简介:2.再从感性上类比一下三个按钮跳转三个Activity,布局就不贴了。只贴一个MainActivity0,其他两个MainActivity1,MainActivity22.
2.再从感性上类比一下 进程和线程
如果手机是地球,进程就像一家公司,公司使用着地球的资源,也在地球上扮演一个独立的个体,实现自己特有的功能与价值。 而线程就像公司里的人,可以共享公司的公共资源,处理属于自己的任务,实现自身的功能与价值。 可以说进程(公司)是给线程(人)一个运行(工作)的环境。于此同时进程也获得了它的地位。 所以一个进程至少要一个线程来完成任务。线程销毁后,里面的进程也就失业拜拜了。 比如某公司的人(线程)集体罢工(崩溃),那公司无论曾经叫什么,都没有意义。公司(进程)倒闭了,再多的线程(人)也没卵用。 多进程就像若干个公司联盟做一个项目,这时候各个公司的内部资源(静态成员、单例等)就不再适用, 就像别的公司人到你公司吃你的零食,敲你键盘,摸你猫,你给吗? 不给,坚决不给。 然后那人非要吃你零食,敲你键盘,摸你猫,还搞出个职位叫IPC,说什么跨进程间通信。TM说白了就是专门抢你零食,抢你猫,你说气不气人。 复制代码
3.最后走一波概念
IPC(Inter-Process Communication): 进程间通信或者跨进程通信 进程:指的一个执行单元,在PC和移动设备上指的是一个程序或者一个应用。 线程:在操作系统中,线程是CPU调度的最小单元,也是一种有限的系统资源。 进程与线程关系:一个进程可以包含多个线程,因此进程和线程是包含被包含的关系。 复制代码
二、如何在一个应用创建多个进程
1.三个测试Activity
三个按钮跳转三个Activity,布局就不贴了。只贴一个MainActivity0,其他两个MainActivity1,MainActivity2
class MainActivity0 : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) title="MainActivity0" to_one.setOnClickListener { startActivity(Intent(this, MainActivity0::class.java)) } to_two.setOnClickListener { startActivity(Intent(this, MainActivity1::class.java)) } to_three.setOnClickListener { startActivity(Intent(this, MainActivity2::class.java)) } } } 复制代码
2. AndroidManifest.xml
配置文件
私有进程: 有:
---------- 全局进程: 没有:
名字可以随便取,只要唯一
<activity android:name=".MainActivity1" android:process=":ipc"> </activity> <activity android:name=".MainActivity2" android:process="com.toly1994.ipc.test"> </activity> 复制代码
三、多进程与单进程的区别
1.打开Activity1时
不加的话,直接通过窗口管理器来显示Activity1
加的话,会在孵化一个进程。zygote64的日志很多,下面只是一小部分。
不清楚Activity启动和View加载过程的小伙伴,可以看一下这个日志,也许会有帮助
比如下面完美呈现了LayoutInflater是怎么运行的,再跟着源码走一走,你会有所收获
然后发现确实是多了两个,名字也能对应上
2.Application的多次实例化
既然开一个进程会孵化一次,ActivityThread的main方法被触发,Application自然会被新建
喵了个咪的,创建了三个,一个进程一个。这显然值得注意,自定义Application初始化第三方库什么的
public class CatApplication extends Application { private static final String TAG = "CatApplication"; @Override public void onCreate() { super.onCreate(); Log.e(TAG, "onCreate: 创建了小猫土土"); } } 复制代码
3.静态成员变量无法在不同进程获取
public class CatManager { public static Cat cat = new Cat(); public CatManager() { cat = new Cat(); cat.color = "灰色" + Math.random(); cat.name = "土土"; } } ---->[MainActivity0#oncreate]------------------ CatManager() ---->[MainActivity1#oncreate]------------------ Log.e("CatManager", ": "+CatManager.cat.color);//null |--- 说明在MainActivity1里已经初始化的静态成员变量无法在MainActivity2(另一个进程)使用 |--- 如果将MainActivity2的process去掉可以打印:灰色0.22701789806635642 |--- 这就尴尬了,我的唯一玩到666的单例肿么办? 复制代码
4.单例模式会怎么样?
新建一个Cat(猫)和CatManager(铲屎官)的类
---->[Cat]------------------------------------ public class Cat { public String name; public String color; @Override public String toString() { return "Cat{" + "name='" + name + '\'' + ", color='" + color + '\'' + '}'; } } ---->[CatManager]------------------------------------ public class CatManager { private volatile static CatManager sCatManager; private static Cat cat=new Cat(); private CatManager() { } public static CatManager newInstance() { if (sCatManager == null) { synchronized (CatManager.class) { if (sCatManager == null) { sCatManager = new CatManager(); Log.e("CatApplication", "newInstance: "); cat.color = "灰色"+Math.random(); cat.name = "土土"; } } } return sCatManager; } public Cat getCat() { return cat; } } ---->[CatApplication]------------------------------------ public class CatApplication extends Application { private static final String TAG = "CatApplication"; @Override public void onCreate() { super.onCreate(); CatManager manager = CatManager.newInstance(); Log.e("CatApplication", manager.getCat().toString()); } } 复制代码
2019-05-08 10:18:02.482 25524-25524/? E/CatApplication: newInstance: 2019-05-08 10:18:02.482 25524-25524/? E/CatApplication: Cat{name='土土', color='灰色0.8695394451026908'} 2019-05-08 10:18:04.761 25561-25561/com.toly1994.ipc:ipc E/CatApplication: newInstance: 2019-05-08 10:18:04.761 25561-25561/com.toly1994.ipc:ipc E/CatApplication: Cat{name='土土', color='灰色0.9824119267379914'} 2019-05-08 10:18:07.096 25597-25597/com.toly1994.ipc.test E/CatApplication: newInstance: 2019-05-08 10:18:07.096 25597-25597/com.toly1994.ipc.test E/CatApplication: Cat{name='土土', color='灰色0.18620946012650275'} 复制代码
可见单例也没有卵用了,每次开启进程都会执行到newInstance,导致单例的失调。
5.小结:多进程带来的问题(老生常谈)
Application会多次创建:开启一个进程其实就等同于开多一个Application 静态成员和单例模式完全失效(处于不同的内存块(进程),拥有各自的副本) SharedPreferences的可靠性降低:因为SharedPreferences不支持两个进程同时去读写xml文件 线程同步机制完全失效(同一差不多) 复制代码
三、IPC的几种形式
为了多公司联盟(多进程)间的和谐,现在决定牺牲猫,让它可以被过各公司(进程)共享
[1].通过Intent传递Bundle对象通信: 简单,数据类型局限,用于组件间传递数据 [2].使用共享文件通信: 简单,实时性差,不适合高并发 [3].使用Messenger通信: 支持一对多串行通信,支持实时通信,不支持RPC [4].使用AIDL通信: 支持一对多并发通信,适用于,一对多通信且有RPC需求 [5].使用ContentProvider: 支持一对多并发数据共享 [6].使用Socket: 可以通过网络传输字节流,支持一对多并发实时通信 复制代码
0.现在的CatManager类和Cat类
既然单例不能用,就不用。这里默认开局一只猫。Cat实现序列化接口Serializable
public class CatManager { private static List<Cat> cats = new ArrayList<>(); public CatManager() { Cat tutu = new Cat(); tutu.color = "灰色" + Math.random(); tutu.name = "土土"; add(tutu); } public void add(Cat cat) { cats.add(cat); } public Cat getCatAt(int index) { return cats.get(index); } } public class Cat implements Serializable { public String name; public String color; @Override public String toString() { return "Cat{" + "name='" + name + '\'' + ", color='" + color + '\'' + '}'; } } 复制代码
1.IPC之 Intent发送Bundle对象通信
1-1.Serializable序列化对象实现
---->[MainActivity0#oncreate]------------------ to_two.setOnClickListener { val cat = CatManager().getCatAt(0) val bundle = Bundle()//创建Bundle对象 bundle.putSerializable("cat", cat)//把猫装到Bundle里,贴个标签cat val intent = Intent(this, MainActivity1::class.java) intent.putExtras(bundle) startActivity(intent) } ---->[MainActivity1#oncreate]------------------ val cat = intent.extras?.get("cat") as Cat //把Bundle用打开标签cat,然后猫到手 Log.e("MainActivity1", ": " + cat.name)//MainActivity1可以对猫为所欲为,IPC 通信完成 复制代码
注:当然你也可以直接通过Intent发送序列化(Serializable)对象,源码瞄一眼,都是通过Bundle的,并无本质区别
public @NonNull Intent putExtra(String name, Serializable value) { if (mExtras == null) { mExtras = new Bundle(); } mExtras.putSerializable(name, value); return this; } 复制代码
1-2.Parcelable序列化对象实现
Android里说Serializable,怎么能少得了同胞兄弟 Parcelable
呢,两者都是对象序列化的手段
两者的详细比较这里就不赘述,详见: Android点将台的Intent篇 。什么是序列化和反序列化,个人理解如下:
比如我家有个大的衣柜(对象),现在要搬家,一下子搬不走,怎么办? 把每块板贴个标签,然后拆了,一块块摆好,然后就能运走了,这叫序列化。 然后到新家里,把板再一块块地拼起来,然后大衣柜(对象)就又回来了,这叫反序列化。 上面说的是物质对象的运输过程,那么信息/数据对象也可以这么类比,思想上是[怎么好运和拼装还原] Serializable和Parcelable不影响序列化的概念,只是手段不同,就像是卡车运还是飞机运一样 Serializable和Parcelable接口代表这东西可拆,是一种可拆保证。要什么都乱拆,你家猫拆个试试。 下面直播拆猫:AS自动生成Parcelable相关代码,可以省我们一些事,but,请千万要了解一下他们是干嘛用的 复制代码
---->[MainActivity0#oncreate]------------------ to_two.setOnClickListener { val cat = CatManager().getCatAt(0) val bundle = Bundle()//创建Bundle对象 bundle.putParcelable("cat", cat)//把猫装到Bundle里,贴个标签cat val intent = Intent(this, MainActivity1::class.java) intent.putExtras(bundle) startActivity(intent) } ---->[MainActivity1#oncreate]------------------ val cat = intent.extras?.get("cat") as Cat //把Bundle用打开标签cat,然后猫到手 Log.e("MainActivity1", ": " + cat.name)//MainActivity1可以对猫为所欲为,IPC 通信完成 复制代码
2.IPC之 文件共享进行通信
把对象写入文件,然后通过文件反序列化出对象,给MainActivity2
(文件读写无论是效率还是多线程的不行,所以这里只是了解一下)
---->[MainActivity0#oncreate]------------------ to_three.setOnClickListener { val cat = CatManager().getCatAt(0) val file = File(cacheDir, "cat.obj") val oos = ObjectOutputStream(FileOutputStream(file)) oos.writeObject(cat) oos.close() startActivity(Intent(this, MainActivity2::class.java)) } ---->[MainActivity2#oncreate]------------------ val file = File(cacheDir, "cat.obj") val ois = ObjectInputStream(FileInputStream(file)) val cat = ois.readObject() as Cat//反序列化生成对象 ois.close() Log.e("MainActivity1", ": " + cat.name)//MainActivity1可以对猫为所欲为,IPC 通信完成 复制代码
可能到这你觉得IPC不就是传个对象吗?好像没什么大不了的
3.IPC之 Messenger通信
3-1:Messenger是什么?
从都构造函数来看,是和Binder有关
private final IMessenger mTarget; public Messenger(Handler target) { mTarget = target.getIMessenger(); } public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); } 复制代码
3-2:可以获取一个Ibinder对象
如此看来mTarget即IMessenger类很像一个AIDL接口
public IBinder getBinder() { return mTarget.asBinder(); } 复制代码
3-3:Messenger的使用
既然是Ibinder对象,可以用在绑定服务中。既然个公司(摸)的人都要猫,干脆来个服务端。
谁(客户端)想来摸一下都可以,核心是Messenger发送消息,Service里接收消息
---->[CatService]------------------ public class CatService extends Service { @Override public IBinder onBind(Intent intent) { return new Messenger(new Handler(msg -> { //接收客户端数据/信息/对象 String data = msg.getData().getString("request"); Log.e("MessengerActivity", "handleMessage: " + data); //向客户端发送数据/信息/对象 Messenger client = msg.replyTo; Message message = Message.obtain(); Cat cat = new CatManager().getCatAt(0); Bundle bundle = new Bundle();//创建Bundle对象 bundle.putParcelable("cat", cat);//把猫装到Bundle里,贴个标签cat message.setData(bundle); try { client.send(message); } catch (RemoteException e) { e.printStackTrace(); } return false; })).getBinder(); } } ---->[将服务单独放在一个进程]-------------- <service android:name=".CatService" android:process="com.toly1994.ipc.service.cat"/> ---->[MessengerActivity]------------------ public class MessengerActivity extends AppCompatActivity { private static final String TAG = "MessengerActivity"; private ServiceConnection conn; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); conn = new ServiceConnection() { private Messenger messenger; @Override public void onServiceConnected(ComponentName name, IBinder service) { messenger = new Messenger(service); Message message = Message.obtain(); Bundle bundle = new Bundle(); bundle.putString("request", "来自客户端:给我一只猫"); message.setData(bundle); message.replyTo = new Messenger(new Handler((msg) -> {//服务端回应监听 Bundle data = msg.getData(); data.setClassLoader(getClass().getClassLoader()); Cat cat = (Cat) (data.get("cat")); Log.e(TAG, "来自服务端: "+cat); return false; })); try { messenger.send(message); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; bindService(new Intent(this, CatService.class), conn, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { super.onDestroy(); unbindService(conn); } } 复制代码
注意这里有一个坑,一开始读不出来异常如下,看名字感觉是类加载器的锅,貌似找不到Cat类
解决: data.setClassLoader(getClass().getClassLoader());
流程基本如下,并不知道两个Handler和三个Messenger,还有皮球一样乱跑的Message有没有把你绕晕
4.IPC之 AIDL通信
这个不怎么想说,在 Android点将台:金科玉律[-AIDL-] 里已经讲得很详细了,为了完整一点,这里稍微再说一下吧。
4-1:定义接口: ICatService
简单一点,就定义一个喂养的方法
4-2:自动生成的类
类之间的关系基本如下:
package com.toly1994.ipc; public interface ICatService extends android.os.IInterface { public static abstract class Stub extends android.os.Binder implements com.toly1994.ipc.ICatService { private static final java.lang.String DESCRIPTOR = "com.toly1994.ipc.ICatService"; public Stub() { this.attachInterface(this, DESCRIPTOR); } //通过IBinder获取ICatService对象,绑定客户端时使用 public static com.toly1994.ipc.ICatService asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.toly1994.ipc.ICatService))) { return ((com.toly1994.ipc.ICatService) iin); } return new com.toly1994.ipc.ICatService.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override//此方法运行在服务端Binder线程池中,客户端发起跨进程请求时,远程请求通过系统底层封装后交由此方法处理 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { java.lang.String descriptor = DESCRIPTOR; switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(descriptor); return true; } case TRANSACTION_feed: { data.enforceInterface(descriptor); java.lang.String _arg0; _arg0 = data.readString(); this.feed(_arg0); reply.writeNoException(); return true; } default: { return super.onTransact(code, data, reply, flags); } } } //代理类,当客户端访问服务端时,客户端通过代理类生成一个ICatService对象 private static class Proxy implements com.toly1994.ipc.ICatService { 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 feed(java.lang.String aString) 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.writeString(aString); mRemote.transact(Stub.TRANSACTION_feed, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_feed = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } public void feed(java.lang.String aString) throws android.os.RemoteException; } 复制代码
4-3:使用ICatService创建FeedCatService
优势在于客户端绑定服务是通过: ICatService.Stub.asInterface(service)
获取ICatService对象
就可以调用ICatService接口方法。这样只暴露接口,可以限制客户端对小猫的操作,客户端即玩了,又不能为所欲为。
---->[FeedCatService]-------------------------------------- public class FeedCatService extends Service { private static final String TAG = "FeedCatService"; private Binder binder = new ICatService.Stub() { @Override public void feed(String aString) throws RemoteException { Log.e(TAG, "feed: 你已喂" + aString + "给小猫土土了"); } }; @Override public IBinder onBind(Intent intent) { return binder; } } ---->[将服务单独放在一个进程]--------------------------------- <service android:name=".FeedCatService" android:process="com.toly1994.ipc.service.feed.cat"/> ---->[AidlActivity]--------------------------------- public class AidlActivity extends AppCompatActivity { private ServiceConnection conn; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); conn = new ServiceConnection() { private ICatService catService; @Override public void onServiceConnected(ComponentName name, IBinder service) { catService = ICatService.Stub.asInterface(service); try { catService.feed("鱼"); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; bindService(new Intent(this, FeedCatService.class), conn, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { super.onDestroy(); unbindService(conn); } } 复制代码
5.IPC之 使用ContentProvider通信
5.1:数据库辅助
private static String DATABASE_NAME = "cat.db";//数据库名 private static int DATABASE_VERSION = 1;//数据库版本 private volatile static CatDatabaseHelper sInstance; //双检锁单例 public static synchronized CatDatabaseHelper getInstance(Context context) { if (sInstance == null) { synchronized (CatDatabaseHelper.class) { if (sInstance == null) { sInstance = new CatDatabaseHelper(context); } } } return sInstance; } public CatDatabaseHelper(@Nullable Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { createSwordTable(db); } /** * 创建sword表 * * @param db SQLiteDatabase */ private void createSwordTable(SQLiteDatabase db) { db.execSQL("CREATE TABLE cat (" + "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + "name VARCHAR(32) NOT NULL," + "color VARCHAR(32) NOT NULL" + "); "); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } 复制代码
5.2:继承ContentProvider
public class CatContentProvider extends ContentProvider { private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); private static final int CAT_QUERY = 0; private static final int CAT_INSERT = 1; private static final int CAT_UPDATE = 2; private static final int CAT_DELETE = 3; private static final String TABLE_NAME = "cat"; static { //给当前sUriMatcher添加匹配规则 sUriMatcher.addURI("toly1994.com.cat", "query", CAT_QUERY); sUriMatcher.addURI("toly1994.com.cat", "insert", CAT_INSERT); sUriMatcher.addURI("toly1994.com.cat", "update", CAT_UPDATE); sUriMatcher.addURI("toly1994.com.cat", "delete", CAT_DELETE); } private SQLiteOpenHelper mOpenHelper; @Override public boolean onCreate() { mOpenHelper = CatDatabaseHelper.getInstance(getContext()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { //进行uri匹配 int result = sUriMatcher.match(uri); if (result == CAT_QUERY) { SQLiteDatabase db = mOpenHelper.getReadableDatabase(); return db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder); } else { throw new IllegalStateException(" query Uri 错误"); } } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { //进行uri匹配 int result = sUriMatcher.match(uri); if (result == CAT_INSERT) { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); Long insert = db.insert(TABLE_NAME, null, values); //uri:数据发送变化,通过uri判断调用哪个内容观察者 //第二个参数:内容观察者对象 如果传null 则注册了整个uri的内容观察者皆可以收到通知 getContext().getContentResolver().notifyChange(uri, null); db.close(); return Uri.parse(String.valueOf(insert)); } else { throw new IllegalStateException("insert Uri 错误"); } } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { //进行uri匹配 int result = sUriMatcher.match(uri); if (result == CAT_DELETE) { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); int delete = db.delete(TABLE_NAME, selection, selectionArgs); db.close(); return delete; } else { throw new IllegalStateException("delete Uri 错误"); } } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { //进行uri匹配 int result = sUriMatcher.match(uri); if (result == CAT_UPDATE) { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); int update = db.update(TABLE_NAME, values, selection, selectionArgs); db.close(); return update; } else { throw new IllegalStateException("update Uri 错误"); } } } ---->[配置]--------------------------------------------- <provider android:authorities="toly1994.com.cat" android:name=".CatContentProvider" android:exported="true" android:process="com.toly1994.ipc.provider.cat" /> 复制代码
5.3:使用
这样不同的进程就可以通过getContentResolver来操作数据库了
public class ProviderActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); insert(getContentResolver()); } /** * 插入测试 * * @param resolver */ private void insert(ContentResolver resolver) { Uri uri = Uri.parse("content://toly1994.com.cat/insert"); ContentValues values = new ContentValues(); values.put("name", "土土"); values.put("color", "灰色"); resolver.insert(uri, values); } } 复制代码
6.使用Socket
Socket可以让两个设备间的通信,两个进程自然也不在话下
这里不深入,只是客户端发一句话,服务端接收一下
---->[SocketService]--------------------------------- public class SocketService extends Service { private static final String TAG = "SocketService"; private boolean quit = false; @Override public void onCreate() { Log.e(TAG, "onCreate: "); FeedServer feedServer = new FeedServer(); new Thread(feedServer).start(); super.onCreate(); } @Override public IBinder onBind(Intent intent) { return null; } private class FeedServer implements Runnable { @Override public void run() { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(8080); Log.e(TAG, "serverSocket onCreate: "); } catch (IOException e) { e.printStackTrace(); } while (!quit) { try { Socket socket = serverSocket.accept(); //接收客户端消息 BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream())); String s = br.readLine(); Log.e(TAG, "来自客户端: " + socket.getInetAddress() + "说:" + s); br.close(); } catch (IOException e) { e.printStackTrace(); } } } } } ---->[将服务单独放在一个进程]--------------------------------- <service android:name=".SocketService" android:process="com.toly1994.ipc.service.socket.feed.cat"/> ---->[ServerActivity]--------------------------------- public class ServerActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startService(new Intent(this, SocketService.class)); findViewById(R.id.to_one).setOnClickListener(v -> { new Thread(() -> { Socket socket = null; try { socket = new Socket("localhost", 8080); //客户端请求 BufferedWriter bw = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream())); bw.write("我要猫"); bw.flush(); bw.close(); } catch (IOException e) { e.printStackTrace(); } }).start(); }); } } 复制代码
OK,本文就先这样, 《Android开发艺术探索》
是本不错的书,有多瞄几眼的价值。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Android点将台:烽火狼烟[-Handler-]
- Android点将台:济世儒侠[-ContentProvider-]
- Android点将台:颜值担当[-Activity-]
- Android点将台:绝命暗杀官[-Service-]
- Android点将台:金科玉律[-AIDL-]
- Android点将台:传令官[-BroadcastReciver-](使用级)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
iOS编程实战
[美] Rob Napier、[美] Mugunth Kumar / 美团移动 / 人民邮电出版社 / 2014-9 / 79.00元
本书深入介绍iOS 7新特性和新功能,涵盖iOS 7大部分新增特性,包括新的后台操作、Core Bluetooth、UIKit动力学以及TextKit。另外还介绍了如何处理新的扁平化UI,并新增了一章你可能不知道的“小技巧”。如果读者熟练掌握C和C++,读完本书即可创建性能优异的iPhone、iPad和iPod touch应用。 本书主要内容包括: iOS 7新特性和新功能概览; ......一起来看看 《iOS编程实战》 这本书的介绍吧!