关于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;@Overridepublic int describeContents() {return 0;}@Overridepublic 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里生命下 这个类。

1 // Person.aidl.aidl
2 package com.example.administrator.writebindercodeexample;
3
4 // Declare any non-default types here with import statements
5 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);}//返回当前对象@Overridepublic android.os.IBinder asBinder() {return this;}//只有在多进程通信的时候 才会调用这个方法 ,同一个进程是不会调用的。//首先 我们要明白 这个方法 一般情况下 都是返回true的,也只有返回true的时候才有意义,如果返回false了 就代表这个方法执行失败,//所以我们通常是用这个方法来做权限认证的,其实也很好理解,既然是多进程通信,那么我们服务端的进程当然不希望谁都能过来调用//所以权限认证是必须的,关于权限认证的代码 以后我再讲 先略过。//除此之外 ,onTransact 这个方法 就是运行在Binder线程池中的,一般就是客户端发起请求,然后android底层代码把这个客户端发起的//请求 封装成3个参数 来调用这个onTransact方法,第一个参数code 就代表客户端想要调用服务端 方法的 标志位。//其实也很好理解 服务端可能有n个方法 每个方法 都有一个对应的int值来代表,这个code就是这个int值,用来标示客户端想调用的服务端的方法//data就是方法参数,reply就是方法返回值。都很好理解//其实隐藏了很重要的一点,这个方法既然是运行在binder线程池中的,所以在这个方法里面调用的服务器方法也是运行在Binder线程池中的,//所以我们要记得 如果你的服务端程序 有可能和多个客户端相联的话,你方法里使用的那些参数 必须要是支持异步的,否则的话//值就会错乱了!这点一定要记住!结论就是Binder方法 一定要是同步方法!!!!!!@Overridepublic 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;}@Overridepublic 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了。@Overridepublic 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 其实你不加也是可以编译通过的,这里我就先加一下 具体参数的意义 以后会说@Overridepublic 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;@Overridepublic int describeContents() {return 0;}@Overridepublic 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);}@Overridepublic IBinder asBinder() {return this;}@Overrideprotected 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;}@Overridepublic android.os.IBinder asBinder() {return mRemote;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}@Overridepublic 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;}@Overridepublic 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() {@Overridepublic List<Dog> getDogList() throws RemoteException {return mDogsList;}@Overridepublic void addDog(Dog dog) throws RemoteException {mDogsList.add(dog);}};@Overridepublic IBinder onBind(Intent intent) {return mBinder;}
}

然后看看 启动如何在客户端bind 这个service:

private IDogManager mService;private ServiceConnection sc = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {mService = DogManagerImpl.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {mService = null;}};

到这 就基本写完了,手写binder的好处就是 你可以自己在binder方法里 写一些log,能够更加深刻的认识到 Binder 作为 进程间通信 媒介的重要作用以及原理。

熟悉以后,还是用aidl 的方法 自动生成代码 最好。

个人评价:

其实对我而言,讲了和没讲差不多,我想知道的东西他貌似都绕过去了。比如INTERFACE_TRANSACTION到底起到什么作用之类的。

Android 手写Binder 教你理解android中的进程间通信相关推荐

  1. Android手写笔应用的实现,android手写笔思路

    工作需要,对这方面做了一些了解 一般的手写对android canvas有点理解的应该都知道,只需要单纯的使用drawPath就可以在view上画画. 而手写笔的关键则是要让path的strokeWi ...

  2. android 手写签批_Android手写签批功能实现(适配Android6

    Android手写签批功能的实现在于三个点,mupdf,偏移量的计算,droidText0.5.jar 实际步骤: 使用muPdf将PDF加载出来 弹出透明的popwindow,popWindow使用 ...

  3. Android手写签名 附带背景图设置

    android手写签名的demo网上其实有很多,但是大多没考虑到签名用在哪里的问题,所以就需要背景图设置,保存电子签名的时候  会连同背景图一起保存   这才达到了电子签名的效果,背景可以放置合同,请 ...

  4. Android手写签名功能(包含画米字格,人名和书写轨迹)

    本文主要介绍Android手写签名的功能实现,效果如下图 1.根据人名的个数绘制人的名称 这个逻辑分几个步骤:首先创建画笔,然后根据一个字,创建一个字的矩形框,然后根据矩形框获取到画这个字的宽高. / ...

  5. Android手写优化

    Android手写优化-平滑的签名效果实现 字数1552  阅读1800  评论2  喜欢4 前言   这是一篇从squareup翻译来的文章,原文在这,之前有人在TIEYE上翻译过这篇文章,但现在链 ...

  6. android 手写 流畅,提高Android应用手写流畅度(基础篇)

    在使用android类的手写应用时,整体上都有这样一个印象:android的手写不流畅.不自然,和苹果应用比起来相差太远.本文结合作者亲身经历,介绍一下有效提高手写流畅度的几种方法: 1.未做任何处理 ...

  7. Android手写签批功能实现(适配Android6.0及以上)

     Android手写签批功能的实现在于三个点,mupdf,偏移量的计算,droidText0.5.jar 实际步骤: 使用muPdf将PDF加载出来 弹出透明的popwindow,popWindow使 ...

  8. android 手写签名

    Android 手写签名是指使用手指在 Android 设备上绘制的签名.它通常用于数字签名或签署电子文件.可以通过使用手写输入 API 和触摸事件 API 来实现手写签名功能.这可以通过开发自定义视 ...

  9. 【手写系列】透彻理解MyBatis设计思想之手写实现

    前言 MyBatis,曾经给我的感觉是一个很神奇的东西,我们只需要按照规范写好XXXMapper.xml以及XXXMapper.java接口.要知道我们并没有提供XXXMapper.java的实现类, ...

最新文章

  1. 取消MySQL timestamp列默认ON UPDATE CURRENT_TIMESTAMP
  2. MPDU 和 MSDU 的区别及关系
  3. Java实现gbk utf8 转换_java项目编码格式转换(如GBK转UTF-8)
  4. 基于silerlight for embedd 视频播放器的之一的问题
  5. Rancher添加主机docker命令
  6. 关于SOCKET中send和recv函数工作原理总结
  7. thinkphp5.0学习(九):TP5.0视图和模板
  8. 如何使YouTube视频连续循环播放
  9. c++函数为什么带imp_二次函数含参最值问题,老师怎么讲学生都不明白,试试这九张动图...
  10. Elasticsearch安装X-Pack插件
  11. AppCode开发,一个智能开发环境
  12. 常见后端数据存储问题解决方案
  13. 超级实用的软著申请源代码材料格式文档生成辅助工具——软著源代码工具
  14. android中AudioRecord使用详解
  15. access open 知乎_必备技能!国际汇款SCI Open Access费用
  16. 基础实验5-2.2 电话聊天狂人(Map的使用+例题)
  17. NAT转换技术(SNAT、MASQUERADE、DNAT策略)及代理服务(squid服务)
  18. 运动神经元有哪些特征 容易与哪些疾病混淆
  19. 抖音播放量突然被限流降权是什么原因
  20. Linux-USB驱动笔记(五)--主机控制器驱动框架

热门文章

  1. libevent 获取多线程结构体变量加锁方法
  2. linux php-fpm启动失败,linux运行php-fpm遇到问题
  3. java 对象的属性_java-更新对象属性
  4. python基础知识笔记简书_Python基础学习笔记
  5. mysql5_pn卸载_windows mySql(5.7.30) 卸载及重装
  6. c语言源程序的下载,编程(C语言源程序代码)
  7. java 同步块 抛出异常_不把 wait 放在同步块中,为啥这种情况不会抛出 IllegalMonitorStateException?...
  8. “最粉嫩”的JVM垃圾回收器及算法,王者笔记!
  9. 【深度学习】基于Pytorch进行深度神经网络计算(二)
  10. python【蓝桥杯vip练习题库】ADV-96复数求和