Android 手写Binder 教你理解android中的进程间通信
关于Binder,我就不解释的太多了,网上一搜资料一堆,但是估计还是很多人理解的有困难。今天就教你如何从 app层面来理解好Binder。
其实就从我们普通app开发者的角度来看,仅仅对于android应用层的话,Binder就是客户端和服务端进行通信的媒介。
AIDL就是我们理解Binder 最好的事例。
我们都知道 我们写好aidl 文件以后,开发工具 会自动帮我们生成好代码。实际上 我们最终apk里面 是只有这些代码的,我们写的aidl文件
是不会被打包进去的,也就是说aidl文件 实际上 就是我们用来 生成 实际binder代码用的。所以 我们只要能够分析好,ide自动帮我们生成的
代码,就可以自己手写binder,从而在app层面上真正理解binder的用法和含义 以及原理。
首先我先来定义一个实体类:Person.java
package com.example.administrator.writebindercodeexample; import android.os.Parcel;
import android.os.Parcelable; /**
* Created by Administrator on 2016/1/27.
*/
public class Person implements Parcelable { private String name; public void setName(String name) {
this.name = name;
} public void setGender(int gender) {
this.gender = gender;
} public int getGender() {
return gender;
} public String getName() {
return name;
} private int gender; @Override
public int describeContents() {
return 0;
} @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeInt(this.gender);
} public Person() {
} protected Person(Parcel in) {
this.name = in.readString();
this.gender = in.readInt();
} public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
public Person createFromParcel(Parcel source) {
return new Person(source);
} public Person[] newArray(int size) {
return new Person[size];
}
};
}
注意看 我们这个person 类 是实现了android自带的序列化接口的,所以 如果你要在aidl里使用这个类,那你必须要额外在aidl里生命下 这个类。
// Person.aidl.aidl
package com.example.administrator.writebindercodeexample; // Declare any non-default types here with import statements
parcelable Person;
// IPersonManager.aidl
package com.example.administrator.writebindercodeexample; // Declare any non-default types here with import statements
import com.example.administrator.writebindercodeexample.Person;
interface IPersonManager {
List<Person> getPersonList();
//关于这个参数in 其实你不加也是可以编译通过的,这里我就先加一下 具体参数的意义 以后会说
void addPerson(in Person person);
}
好,然后给你们看一下 文件结构:
好 这里就是一个典型的 应用aidl 技术的 一个例子,我们现在 让studio 编译这个project,然后看看生成的binder代码。 把这份binder代码 分析好了,我们以后就可以不借助ide 来自己手写binder了。
我们来看看 生成的代码在哪里:
最后我们来看一下 这个生成的代码 是啥样的:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: C:\\Users\\Administrator\\WriteBinderCodeExample\\app\\src\\main\\aidl\\com\\example\\administrator\\writebindercodeexample\\IPersonManager.aidl
*/
package com.example.administrator.writebindercodeexample;
public interface IPersonManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.administrator.writebindercodeexample.IPersonManager
{
private static final java.lang.String DESCRIPTOR = "com.example.administrator.writebindercodeexample.IPersonManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.administrator.writebindercodeexample.IPersonManager interface,
* generating a proxy if needed.
*/
public static com.example.administrator.writebindercodeexample.IPersonManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.administrator.writebindercodeexample.IPersonManager))) {
return ((com.example.administrator.writebindercodeexample.IPersonManager)iin);
}
return new com.example.administrator.writebindercodeexample.IPersonManager.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_getPersonList:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.example.administrator.writebindercodeexample.Person> _result = this.getPersonList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addPerson:
{
data.enforceInterface(DESCRIPTOR);
com.example.administrator.writebindercodeexample.Person _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.administrator.writebindercodeexample.Person.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addPerson(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.administrator.writebindercodeexample.IPersonManager
{
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 java.util.List<com.example.administrator.writebindercodeexample.Person> getPersonList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.administrator.writebindercodeexample.Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.administrator.writebindercodeexample.Person.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
//关于这个参数in 其实你不加也是可以编译通过的,这里我就先加一下 具体参数的意义 以后会说 @Override public void addPerson(com.example.administrator.writebindercodeexample.Person person) 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 ((person!=null)) {
_data.writeInt(1);
person.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.example.administrator.writebindercodeexample.Person> getPersonList() throws android.os.RemoteException;
//关于这个参数in 其实你不加也是可以编译通过的,这里我就先加一下 具体参数的意义 以后会说 public void addPerson(com.example.administrator.writebindercodeexample.Person person) throws android.os.RemoteException;
}
看上去呢,杂乱无章, 但其实也就是100多行,所以 我调整了一下 这个代码的顺序 ,你们可以看的更清楚,同时也增加了注释:
package com.example.administrator.aidlmessagetest; //为了让大家看的更清楚 我把生成的binder代码 给拷贝到另外一个工程下面了,并且用ide 给他format
//所以包名和我们一开始前面的代码都不一样,大家理解意思就行。 //从前面几行就能看出来 生成的代码是一个 interface ,只不过这个interface是 android.os.IInterface 的子类!
public interface IPersonManager extends android.os.IInterface { //并且这个接口里 有一个静态的抽象类Stub(注意这个名字是固定的 永远都是Stub 不会是其他)
//并且这个Stub是Binder的子类,并且实现了IPersonManager 这个接口
public static abstract class Stub extends android.os.Binder implements com.example.administrator.aidlmessagetest.IPersonManager {
//这个东西就是唯一的binder标示 可以看到就是IPersonManager的全路径名
private static final java.lang.String DESCRIPTOR = "com.example.administrator.aidlmessagetest.IPersonManager"; /**
* 这个就是Stub的构造方法,回顾一下 我们如果写好aidl文件以后 写的service里面 是怎么写的?
*
* private final IPersonManager.Stub mBinder = new IPersonManager.Stub() {}
* 我们都是这么写的 对吧~~所以想想我们的service里面的代码 就能辅助理解 这里的代码了
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
} //这个方法 其实就做了一件事,如果是同一个进程,那么就返回Stub对象本身
//如果不是同一个进程,就返回Stub.Proxy这个代理对象了
public static com.example.administrator.aidlmessagetest.IPersonManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//如果是同1个进程,也就是说进程内通信的话 我们就返回括号内里的对象
if (((iin != null) && (iin instanceof com.example.administrator.aidlmessagetest.IPersonManager))) {
return ((com.example.administrator.aidlmessagetest.IPersonManager) iin);
}
//如果不是同一进程,是2个进程之间相互通信,那我们就得返回这个Stub.Proxy 看上去叫Stub 代理的对象了
return new com.example.administrator.aidlmessagetest.IPersonManager.Stub.Proxy(obj);
} //返回当前对象
@Override
public android.os.IBinder asBinder() {
return this;
} //只有在多进程通信的时候 才会调用这个方法 ,同一个进程是不会调用的。 //首先 我们要明白 这个方法 一般情况下 都是返回true的,也只有返回true的时候才有意义,如果返回false了 就代表这个方法执行失败,
//所以我们通常是用这个方法来做权限认证的,其实也很好理解,既然是多进程通信,那么我们服务端的进程当然不希望谁都能过来调用
//所以权限认证是必须的,关于权限认证的代码 以后我再讲 先略过。 //除此之外 ,onTransact 这个方法 就是运行在Binder线程池中的,一般就是客户端发起请求,然后android底层代码把这个客户端发起的
//请求 封装成3个参数 来调用这个onTransact方法,第一个参数code 就代表客户端想要调用服务端 方法的 标志位。
//其实也很好理解 服务端可能有n个方法 每个方法 都有一个对应的int值来代表,这个code就是这个int值,用来标示客户端想调用的服务端的方法
//data就是方法参数,reply就是方法返回值。都很好理解 //其实隐藏了很重要的一点,这个方法既然是运行在binder线程池中的,所以在这个方法里面调用的服务器方法也是运行在Binder线程池中的,
//所以我们要记得 如果你的服务端程序 有可能和多个客户端相联的话,你方法里使用的那些参数 必须要是支持异步的,否则的话
//值就会错乱了!这点一定要记住!结论就是Binder方法 一定要是同步方法!!!!!!
@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_getPersonList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.example.administrator.aidlmessagetest.Person> _result = this.getPersonList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addPerson: {
data.enforceInterface(DESCRIPTOR);
com.example.administrator.aidlmessagetest.Person _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.administrator.aidlmessagetest.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addPerson(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
} //注意这里的Proxy 这个类名也是不变的,从前文我们知道 只有在多进程通信的情况下 才会返回这个代理的对象
private static class Proxy implements com.example.administrator.aidlmessagetest.IPersonManager {
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;
} //这里我们一共有2个方法 一个getPersonList 一个addPerson 我们就分析一个方法就可以了
//并且要知道 这2个方法运行在客户端!!!!!!!!!!!!!!!!
//首先就是创建了3个对象_data 输入对象,_reply输出对象,_result返回值对象
//然后把参数信息 写入到_data里,接着就调用了transact这个方法 来发送rpc请求,然后接着
//当前线程挂起, 服务端的onTransace方法才被调用,调用结束以后 当前线程继续执行,直到
//从_reply中取出rpc的返回结果 然后返回_reply的数据 //所以这里我们就要注意了,客户端发起调用远程请求时,当前客户端的线程就会被挂起了,
//所以如果一个远程方法 很耗时,我们客户端就一定不能在ui main线程里在发起这个rpc请求,不然就anr了。
@Override
public java.util.List<com.example.administrator.aidlmessagetest.Person> getPersonList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.administrator.aidlmessagetest.Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.administrator.aidlmessagetest.Person.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
} //你看自动生成binder代码的时候 连你的注释也一起拷贝过来了。。。。。是不是很有趣
//关于这个参数in 其实你不加也是可以编译通过的,这里我就先加一下 具体参数的意义 以后会说 @Override
public void addPerson(com.example.administrator.aidlmessagetest.Person person) 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 ((person != null)) {
_data.writeInt(1);
person.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
} static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
} public java.util.List<com.example.administrator.aidlmessagetest.Person> getPersonList() throws android.os.RemoteException;
//关于这个参数in 其实你不加也是可以编译通过的,这里我就先加一下 具体参数的意义 以后会说 public void addPerson(com.example.administrator.aidlmessagetest.Person person) throws android.os.RemoteException;
}
到这里 相信大家 至少在应用层上面,就对Binder就一个很直观的理解了,对于进程间通信来说,具体的流程就分为如下几步:
1.Client 发起远程调用请求 也就是RPC 到Binder。同时将自己挂起,挂起的原因是要等待RPC调用结束以后返回的结果
2.Binder 收到RPC请求以后 把参数收集一下,调用transact方法,把RPC请求转发给service端。
3.service端 收到rpc请求以后 就去线程池里 找一个空闲的线程去走service端的 onTransact方法 ,实际上也就是真正在运行service端的 方法了,等方法运行结束 就把结果 写回到binder中。
4.Binder 收到返回数据以后 就唤醒原来的Client 线程,返回结果。至此,一次进程间通信 的过程就结束了
搞明白以后 我们就可以来尝试着 手下一下Binder:(前面我们aidl 帮我们生成的binder 是人,也就是person,那这次我们自己写的时候 就用狗吧,用DOG)
首先定义一个Dog.java: 实际上和person 一样的 所以这里暂时把代码折叠起来。
package com.example.administrator.writebindercodeexample; import android.os.Parcel;
import android.os.Parcelable; /**
* Created by Administrator on 2016/1/27.
*/
public class Dog implements Parcelable { public int getGender() {
return gender;
} public String getName() {
return name;
} public void setGender(int gender) {
this.gender = gender;
} public void setName(String name) {
this.name = name;
} private int gender;
private String name; @Override
public int describeContents() {
return 0;
} @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.gender);
dest.writeString(this.name);
} public Dog() {
} protected Dog(Parcel in) {
this.gender = in.readInt();
this.name = in.readString();
} public static final Parcelable.Creator<Dog> CREATOR = new Parcelable.Creator<Dog>() {
public Dog createFromParcel(Parcel source) {
return new Dog(source);
} public Dog[] newArray(int size) {
return new Dog[size];
}
};
}
然后写一个接口IDogManager
package com.example.administrator.writebindercodeexample; import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException; import java.util.List; /**
* Created by Administrator on 2016/1/27.
*/
public interface IDogManager extends IInterface { static final String DESCRIPTOR = "com.example.administrator.writebindercodeexample.IDogManager";
static final int TRANSACTION_getDogList = IBinder.FIRST_CALL_TRANSACTION + 0;
static final int TRANSACTION_addDog = IBinder.FIRST_CALL_TRANSACTION + 1; public List<Dog> getDogList() throws RemoteException; public void addDog(Dog dog) throws RemoteException; }
然后写我们的binder,注意我们的binder 我这里是写的抽象类,因为你写成实体类的话 就必须要实现IDogManger里的2个方法 ,然而为了结构清晰 我们并不准备把binder 放在service里 实现。
所以这里binder 我们还是用抽象类来做,然后在service里 实现 getDogList和addDog方法即可。
package com.example.administrator.writebindercodeexample; import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException; /**
* Created by Administrator on 2016/1/27.
*/
public abstract class DogManagerImpl extends Binder implements IDogManager { public DogManagerImpl() {
this.attachInterface(this, DESCRIPTOR);
} public static com.example.administrator.writebindercodeexample.IDogManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//如果是同1个进程,也就是说进程内通信的话 我们就返回括号内里的对象
if (((iin != null) && (iin instanceof com.example.administrator.writebindercodeexample.IDogManager))) {
return ((com.example.administrator.writebindercodeexample.IDogManager) iin);
}
//如果不是同一进程,是2个进程之间相互通信,那我们就得返回这个Stub.Proxy 看上去叫Stub 代理的对象了
return new com.example.administrator.writebindercodeexample.DogManagerImpl.Proxy(obj);
} @Override
public IBinder asBinder() {
return this;
} @Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getDogList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.example.administrator.writebindercodeexample.Dog> _result = this.getDogList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addDog: {
data.enforceInterface(DESCRIPTOR);
com.example.administrator.writebindercodeexample.Dog _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.administrator.writebindercodeexample.Dog.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addDog(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
} private static class Proxy extends DogManagerImpl {
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 java.util.List<com.example.administrator.writebindercodeexample.Dog> getDogList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.administrator.writebindercodeexample.Dog> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(DogManagerImpl.TRANSACTION_getDogList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.administrator.writebindercodeexample.Dog.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
} @Override
public void addDog(com.example.administrator.writebindercodeexample.Dog dog) 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 ((dog != null)) {
_data.writeInt(1);
dog.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(DogManagerImpl.TRANSACTION_addDog, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
} }
到这,我们的手写binder 就完成了,然后看看 service 以及客户端 怎么调用。
先看service:
package com.example.administrator.writebindercodeexample; import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log; import java.util.ArrayList;
import java.util.List; public class RemoteService extends Service { private List<Dog> mDogsList = new ArrayList<Dog>(); private final DogManagerImpl mBinder = new DogManagerImpl() {
@Override
public List<Dog> getDogList() throws RemoteException {
return mDogsList;
} @Override
public void addDog(Dog dog) throws RemoteException {
mDogsList.add(dog);
}
}; @Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
然后看看 启动如何在客户端bind 这个service:
private IDogManager mService; private ServiceConnection sc = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = DogManagerImpl.asInterface(service); } @Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
到这 就基本写完了,手写binder的好处就是 你可以自己在binder方法里 写一些log,能够更加深刻的认识到 Binder 作为 进程间通信 媒介的重要作用以及原理。
熟悉以后,还是用aidl 的方法 自动生成代码 最好。
Android 手写Binder 教你理解android中的进程间通信的更多相关文章
- 【开源项目】Android 手写记事 App(半成品)
该项目已上传到 CSDN 的 Git 平台中 项目地址:https://code.csdn.net/gd920129/whiteboard GIT SSH:git@code.csdn.net:gd92 ...
- android 手写万能adapter适配器
android开发中,我们离不开adapter,每个项目都有很多地方需要adapter,那么我们如何让自己少写adapter代码呢?那就是封装adapter,让我们的adapter成为万能的adapt ...
- Android系统编程入门系列之服务Service中的进程间通信
在上篇文章以线程间的通信方式Handler类结尾,服务Service还支持的进程间通信,又是具体怎么实现的呢?这就要用到加载服务一文中提到的AIDL语言规范了. AIDL是 Android Inter ...
- 十分钟教你理解TypeScript中的泛型
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者.原文出处:https://blog.bitsrc.io/understanding-generics-in-t ...
- Android深入浅出之Binder机制(转)
Android深入浅出之Binder机制 一 说明 Android系统最常见也是初学者最难搞明白的就是Binder了,很多很多的Service就是通过Binder机制来和客户端通讯交互的.所以搞明白B ...
- [深入理解Android卷一全文-第四章]深入理解zygote
由于<深入理解Android 卷一>和<深入理解Android卷二>不再出版,而知识的传播不应该由于纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的所有内容. ...
- [深入理解Android卷一全文-第八章]深入理解Surface系统
由于<深入理解Android 卷一>和<深入理解Android卷二>不再出版.而知识的传播不应该由于纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容. ...
- [深入理解Android卷一全文-第七章]深入理解Audio系统
由于<深入理解Android 卷一>和<深入理解Android卷二>不再出版,而知识的传播不应该由于纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容. ...
- [深入理解Android卷一全文-第三章]深入理解init
因为<深入理解Android 卷一>和<深入理解Android卷二>不再出版,而知识的传播不应该因为纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容. ...
随机推荐
- lintcode: 最长无重复字符的子串
题目 最长无重复字符的子串给定一个字符串,请找出其中无重复字符的最长子字符串. 例如,在"abcabcbb"中,其无重复字符的最长子字符串是"abc",其长度为 ...
- 8 simple things that will make you sexy
8 simple things that will make you sexy8种方法教你不动声色的性感What makes a women sexy? Is it her body? Is it t ...
- Linux Tomcat必须知道的命令
查看java相关的进程号:ps -ef|grep java 杀死进程:kill -s 9(进程号,9优先级最高) 预启动tomcat: ./catalina.sh run (可查看启动状态) 启动to ...
- SPRING IN ACTION 第4版笔记-第十一章Persisting data with object-relational mapping-001-使用Hibernate(@Inject、@EnableTransactionManagement、@Repository、PersistenceExceptionTranslationPostProcessor)
一.结构 二.Repository层 1. package spittr.db; import java.util.List; import spittr.domain.Spitter; /** * ...
- 图解TCP/IP读书笔记(一)
图解TCP/IP读书笔记(一) 第一章 网络基础知识 本学期的信安概论课程中有大量的网络知识,其中TCP/IP占了相当大的比重,让我对上学期没有好好学习计算机网络这门课程深感后悔.在老师的推荐下开始阅 ...
- [iOS]为什么不要在init初始化方法里调用self.view
首先.如果你调用self.view的时候,就会调用view的getter方法, 这个时候,view是空的,那么系统就会自动给你创建一个view,然后就会触发ViewDidLoad方法.那么这个时候,如 ...
- 【重走Android之路】【路线篇(二)】知识点归纳
[重走Android之路][路线篇(二)]知识点归纳 参考:http://blog.csdn.net/xujing81/article/details/7313507 第一阶段:Java面向对 ...
- Java:编码的详解
ASCII:美国信息标准信息码,用一个字节的7为表示. ISO8859-1:拉丁码表 欧洲码表 ,用一个字节的8位表示. GB2312:中国的中文编码表. GBK:中国的中文编码表升级,融合了更多的中 ...
- HDU 4607 Park Visit 两次DFS求树直径
两次DFS求树直径方法见 这里. 这里的直径是指最长链包含的节点个数,而上一题是指最长链的路径权值之和,注意区分. K <= R: ans = K − 1; K > R: ans = ...
- IntelliJ IDEA12.1.1 使用 相关记录
最近在切换ide 从eclipse平台上转移到 IDEA 其它的都比较容易,让我烦恼的是使用idea 发布工程,使用tomcat 调试 热部署.期间碰到各种问题,纠结好久了:终于成功完成了,特意记 ...