0-Android使用Ashmem机制进行跨进程共享内存
Android使用Ashmem机制进行跨进程共享内存
导语:
在Android系统中,提供了独特的匿名共享内存子系统Ashmem(Anonymous Shared Memory),它以驱动程序的形式实现在内核空间中。它有两个特点:
1.一是能够辅助内存管理系统来有效地管理不再使用的内存块
2.二是它通过Binder进程间通信机制来实现进程间的内存共享。
本文中,我们将通过实例来简要介绍Android系统的匿名共享内存的使用方法,使得我们对Android系统的匿名共享内存机制有一个感性的认识,为进一步学习它的源代码实现打下基础。
案例原理:
Android系统的匿名共享内存子系统的主体是以驱动程序的形式存在。在系统运行时库层和应用程序框架层提供了访问接口,其中,在系统运行时库层提供了C/C++调用接口,而在应用程序框架层提供了Java调用接口。
这里,我们将直接通过应用程序框架层提供的Java调用接口来说明匿名共享内存子系统Ashmem的使用方法,毕竟我们在Android开发应用程序时,是基于Java语言的,而实际上,应用程序框架层的Java调用接口是通过JNI方法来调用系统运行时库层的C/C++调用接口,最后进入到内核空间的Ashmem驱动程序去的。
我们在这里举的例子是一个名为Ashmem的应用程序,它包含了一个Server端和一个Client端实现,
1. Server端是以Service的形式实现的,在这里Service里面,创建一个匿名共享内存文件
2. Client是一个Activity,这个Activity通过Binder进程间通信机制获得前面这个Service创建的匿名共享内存文件的句柄,从而实现共享。
在Android应用程序框架层,提供了一个MemoryFile接口来封装了匿名共享内存文件的创建和使用,它实现在frameworks/base/core/java/android/os/MemoryFile.java文件中。
下面,我们就来看看Server端是如何通过MemoryFile类来创建匿名共享内存文件的以及Client是如何获得这个匿名共享内存文件的句柄的。
在MemoryFile类中,提供了两种创建匿名共享内存的方法,我们通过MemoryFile类的构造函数来看看这两种使用方法:
public class MemoryFile
{
......
/**
* Allocates a new ashmem region. The region is initially not purgable.
*
* @param name optional name for the file (can be null).
* @param length of the memory file in bytes.
* @throws IOException if the memory file could not be created.
*/
public MemoryFile(String name, int length) throws IOException {
mLength = length;
mFD = native_open(name, length);
mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
mOwnsRegion = true;
}
/**
* Creates a reference to an existing memory file. Changes to the original file
* will be available through this reference.
* Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail.
*
* @param fd File descriptor for an existing memory file, as returned by
* {@link #getFileDescriptor()}. This file descriptor will be closed
* by {@link #close()}.
* @param length Length of the memory file in bytes.
* @param mode File mode. Currently only "r" for read-only access is supported.
* @throws NullPointerException if <code>fd</code> is null.
* @throws IOException If <code>fd</code> does not refer to an existing memory file,
* or if the file mode of the existing memory file is more restrictive
* than <code>mode</code>.
*
* @hide
*/
public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException {
if (fd == null) {
throw new NullPointerException("File descriptor is null.");
}
if (!isMemoryFile(fd)) {
throw new IllegalArgumentException("Not a memory file.");
}
mLength = length;
mFD = fd;
mAddress = native_mmap(mFD, length, modeToProt(mode));
mOwnsRegion = false;
}
......
} viewcopy
两个构造函数的主要区别是第一个参数
1.第一种构造方法是以指定的字符串调用JNI方法native_open来创建一个匿名共享内存文件,得到一个文件描述符,接着就以这个文件描述符为参数调用JNI方法natvie_mmap把这个匿名共享内存文件映射在进程空间中,然后就可以通过这个映射后得到的地址空间来直接访问内存数据了;
2.第二种构造方法是以指定的文件描述符来直接调用JNI方法natvie_mmap把这个匿名共享内存文件映射在进程空间中,然后进行访问,而这个文件描述符就必须要是一个匿名共享内存文件的文件描述符,这是通过一个内部函数isMemoryFile来验证的,而这个内部函数isMemoryFile也是通过JNI方法调用来进一步验证的。前面所提到的这些JNI方法调用,最终都是通过系统运行时库层进入到内核空间的Ashmem驱动程序中去,不过这里我们不关心这些JNI方法、系统运行库层调用以及Ashmem驱动程序的具体实现,在接下来的两篇文章中,我们将会着重介绍,这里我们只关注MemoryFile这个类的使用方法。
前面我们说到,我们在这里举的例子包含了一个Server端和一个Client端实现,
1. Server端就是通过第一个构造函数来创建一个匿名共享内存文件
2. Client端过Binder进程间通信机制来向Server请求获取这个匿名共享内存的文件描述符,有了这个文件描述符之后,就可以通过后面一个构造函数来共享这个内存文件了。 然后Client和Server之间就可以通过这个这个匿名内存共享数据了。
案例实现:
首先在源代码工程的packages/experimental目录下创建一个应用程序工程目录Ashmem。它定义了一个路径为shy.luo.ashmem的package,这个例子的源代码主要就是实现在这里了。
将会逐一介绍这个package里面的文件。这里要用到的Binder进程间通信接口定义在src/shy/luo/ashmem/IMemoryService.java文件中:
package shy.luo.ashmem;
import android.util.Log;
import android.os.IInterface;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
public interface IMemoryService extends IInterface {
public static abstract class Stub extends Binder implements IMemoryService {
private static final String DESCRIPTOR = "shy.luo.ashmem.IMemoryService";
public Stub() {
attachInterface(this, DESCRIPTOR);
}
public static IMemoryService asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IInterface iin = (IInterface)obj.queryLocalInterface(DESCRIPTOR);
if (iin != null && iin instanceof IMemoryService) {
return (IMemoryService)iin;
}
return new IMemoryService.Stub.Proxy(obj);
}
public IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getFileDescriptor: {
data.enforceInterface(DESCRIPTOR);
ParcelFileDescriptor result = this.getFileDescriptor();
reply.writeNoException();
if (result != null) {
reply.writeInt(1);
result.writeToParcel(reply, 0);
} else {
reply.writeInt(0);
}
return true;
}
case TRANSACTION_setValue: {
data.enforceInterface(DESCRIPTOR);
int val = data.readInt();
setValue(val);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements IMemoryService {
private IBinder mRemote;
Proxy(IBinder remote) {
mRemote = remote;
}
public IBinder asBinder() {
return mRemote;
}
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
public ParcelFileDescriptor getFileDescriptor() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
ParcelFileDescriptor result;
try {
data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getFileDescriptor, data, reply, 0);
reply.readException();
if (0 != reply.readInt()) {
result = ParcelFileDescriptor.CREATOR.createFromParcel(reply);
} else {
result = null;
}
} finally {
reply.recycle();
data.recycle();
}
return result;
}
public void setValue(int val) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
data.writeInt(val);
mRemote.transact(Stub.TRANSACTION_setValue, data, reply, 0);
reply.readException();
} finally {
reply.recycle();
data.recycle();
}
}
}
static final int TRANSACTION_getFileDescriptor = IBinder.FIRST_CALL_TRANSACTION + 0;
static final int TRANSACTION_setValue = IBinder.FIRST_CALL_TRANSACTION + 1;
}
public ParcelFileDescriptor getFileDescriptor() throws RemoteException;
public void setValue(int val) throws RemoteException;
} view pcopy
这里主要是定义了IMemoryService接口,它里面有两个调用接口:
public ParcelFileDescriptor getFileDescriptor() throws RemoteException;
public void setValue(int val) throws RemoteException;
同时,还分别定义了用于Server端实现的IMemoryService.Stub基类和用于Client端使用的代理IMemoryService.Stub.Proxy类。
有了Binder进程间通信接口之后,接下来就是要在Server端实现一个本地服务了。这里,Server端实现的本地服务名为MemoryService,实现在src/shy/luo/ashmem/MemoryService.java文件中:
package shy.luo.ashmem;
import java.io.FileDescriptor;
import java.io.IOException;
import android.os.Parcel;
import android.os.MemoryFile;
import android.os.ParcelFileDescriptor;
import android.util.Log;
public class MemoryService extends IMemoryService.Stub {
private final static String LOG_TAG = "shy.luo.ashmem.MemoryService";
private MemoryFile file = null;
public MemoryService() {
try {
file = new MemoryFile("Ashmem", 4);
setValue(0);
}
catch(IOException ex) {
Log.i(LOG_TAG, "Failed to create memory file.");
ex.printStackTrace();
}
}
public ParcelFileDescriptor getFileDescriptor() {
Log.i(LOG_TAG, "Get File Descriptor.");
ParcelFileDescriptor pfd = null;
try {
pfd = file.getParcelFileDescriptor();
} catch(IOException ex) {
Log.i(LOG_TAG, "Failed to get file descriptor.");
ex.printStackTrace();
}
return pfd;
}
public void setValue(int val) {
if(file == null) {
return;
}
byte[] buffer = new byte[4];
buffer[0] = (byte)((val >>> 24) & 0xFF);
buffer[1] = (byte)((val >>> 16) & 0xFF);
buffer[2] = (byte)((val >>> 8) & 0xFF);
buffer[3] = (byte)(val & 0xFF);
try {
file.writeBytes(buffer, 0, 0, 4);
Log.i(LOG_TAG, "Set value " + val + " to memory file. ");
}
catch(IOException ex) {
Log.i(LOG_TAG, "Failed to write bytes to memory file.");
ex.printStackTrace();
}
}
} view pcopy
1、这里的MemoryService类实现了IMemoryService.Stub类,表示这是一个Binder服务的本地实现。
2、在构造函数中,通过指定文件名和文件大小来创建了一个匿名共享内存文件,即创建MemoryFile的一个实例,并保存在类成员变量file中。
3、这个匿名共享内存文件名为"Ashmem",大小为4个节字,刚好容纳一个整数,我们这里举的例子就是要说明如果创建一个匿名共享内存来在两个进程间实现共享一个整数了。当然,在实际应用中,可以根据需要创建合适大小的共享内存来共享有意义的数据。
这里还实现了IMemoryService.Stub的两个接口getFileDescriptor和setVal,一个用来获取匿名共享内存文件的文件描述符,一个来往匿名共享内存文件中写入一个整数,其中,接口getFileDescriptor的返回值是一个ParcelFileDescriptor。在Java中,是用FileDescriptor类来表示一个文件描述符的,而ParcelFileDescriptor是用来序列化FileDescriptor的,以便在进程间调用时传输。
定义好本地服务好,就要定义一个Server来启动这个服务了。这里定义的Server实现在src/shy/luo/ashmem/Server.java文件中:
package shy.luo.ashmem;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.os.ServiceManager;
public class Server extends Service {
private final static String LOG_TAG = "shy.luo.ashmem.Server";
private MemoryService memoryService = null;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
Log.i(LOG_TAG, "Create Memory Service...");
memoryService = new MemoryService();
try {
ServiceManager.addService("AnonymousSharedMemory", memoryService);
Log.i(LOG_TAG, "Succeed to add memory service.");
} catch (RuntimeException ex) {
Log.i(LOG_TAG, "Failed to add Memory Service.");
ex.printStackTrace();
}
}
@Override
public void onStart(Intent intent, int startId) {
Log.i(LOG_TAG, "Start Memory Service.");
}
@Override
public void onDestroy() {
Log.i(LOG_TAG, "Destroy Memory Service.");
}
}
这个Server继承了Android系统应用程序框架层提供的Service类,当它被启动时,运行在一个独立的进程中。当这个Server被启动时,它的onCreate函数就会被调用,然后它就通过ServiceManager的addService接口来添加MemoryService了:
这样,当这个Server成功启动了,Client就可以通过ServiceManager的getService接口来获取这个MemoryService了。接着,我们就来看Client端的实现。Client端是一个Activity,实现在src/shy/luo/ashmem/Client.java文件中:
package shy.luo.ashmem;
import java.io.FileDescriptor;
import java.io.IOException;
import shy.luo.ashmem.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.MemoryFile;
import android.os.ParcelFileDescriptor;
import android.os.ServiceManager;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class Client extends Activity implements OnClickListener {
private final static String LOG_TAG = "shy.luo.ashmem.Client";
IMemoryService memoryService = null;
MemoryFile memoryFile = null;
private EditText valueText = null;
private Button readButton = null;
private Button writeButton = null;
private Button clearButton = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
IMemoryService ms = getMemoryService();
if(ms == null) {
startService(new Intent("shy.luo.ashmem.server"));
} else {
Log.i(LOG_TAG, "Memory Service has started.");
}
valueText = (EditText)findViewById(R.id.edit_value);
readButton = (Button)findViewById(R.id.button_read);
writeButton = (Button)findViewById(R.id.button_write);
clearButton = (Button)findViewById(R.id.button_clear);
readButton.setOnClickListener(this);
writeButton.setOnClickListener(this);
clearButton.setOnClickListener(this);
Log.i(LOG_TAG, "Client Activity Created.");
}
@Override
public void onResume() {
super.onResume();
Log.i(LOG_TAG, "Client Activity Resumed.");
}
@Override
public void onPause() {
super.onPause();
Log.i(LOG_TAG, "Client Activity Paused.");
}
@Override
public void onClick(View v) {
if(v.equals(readButton)) {
int val = 0;
MemoryFile mf = getMemoryFile();
if(mf != null) {
try {
byte[] buffer = new byte[4];
mf.readBytes(buffer, 0, 0, 4);
val = (buffer[0] << 24) | ((buffer[1] & 0xFF) << 16) | ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF);
} catch(IOException ex) {
Log.i(LOG_TAG, "Failed to read bytes from memory file.");
ex.printStackTrace();
}
}
String text = String.valueOf(val);
valueText.setText(text);
} else if(v.equals(writeButton)) {
String text = valueText.getText().toString();
int val = Integer.parseInt(text);
IMemoryService ms = getMemoryService();
if(ms != null) {
try {
ms.setValue(val);
} catch(RemoteException ex) {
Log.i(LOG_TAG, "Failed to set value to memory service.");
ex.printStackTrace();
}
}
} else if(v.equals(clearButton)) {
String text = "";
valueText.setText(text);
}
}
private IMemoryService getMemoryService() {
if(memoryService != null) {
return memoryService;
}
memoryService = IMemoryService.Stub.asInterface(
ServiceManager.getService("AnonymousSharedMemory"));
Log.i(LOG_TAG, memoryService != null ? "Succeed to get memeory service." : "Failed to get memory service.");
return memoryService;
}
private MemoryFile getMemoryFile() {
if(memoryFile != null) {
return memoryFile;
}
IMemoryService ms = getMemoryService();
if(ms != null) {
try {
ParcelFileDescriptor pfd = ms.getFileDescriptor();
if(pfd == null) {
Log.i(LOG_TAG, "Failed to get memory file descriptor.");
return null;
}
try {
FileDescriptor fd = pfd.getFileDescriptor();
if(fd == null) {
Log.i(LOG_TAG, "Failed to get memeory file descriptor.");
return null;
}
memoryFile = new MemoryFile(fd, 4, "r");
} catch(IOException ex) {
Log.i(LOG_TAG, "Failed to create memory file.");
ex.printStackTrace();
}
} catch(RemoteException ex) {
Log.i(LOG_TAG, "Failed to get file descriptor from memory service.");
ex.printStackTrace();
}
}
return memoryFile;
}
} view pcopy
Client端的界面主要包含了三个按钮Read、Write和Clear,以及一个用于显示内容的文本框。
这个Activity在onCreate时,会通过startService接口来启动我们前面定义的Server进程。调用startService时,需要指定要启动的服务的名称,这里就是"shy.luo.ashmem.server"了,后面我们会在程序的描述文件AndroidManifest.xml看到前面的Server类是如何和名称"shy.luo.ashmem.server"关联起来的。关于调用startService函数来启动自定义服务的过程,可以参考Android系统在新进程中启动自定义服务过程(startService)的原理分析一文。
内部函数getMemoryService用来获取IMemoryService。如果是第一次调用该函数,则会通过ServiceManager的getService接口来获得这个IMemoryService接口,然后保存在类成员变量memoryService中,以后再调用这个函数时,就可以直接返回memoryService了。
内部函数getMemoryFile用来从MemoryService中获得匿名共享内存文件的描述符。同样,如果是第一次调用该函数,则会通过IMemoryService的getFileDescriptor接口来获得MemoryService中的匿名共享内存文件的描述符,然后用这个文件描述符来创建一个MemoryFile实例,并保存在类成员变量memoryFile中,以后再调用这个函数时,就可以直接返回memoryFile了。
有了memoryService和memoryFile后,我们就可以在Client端访问Server端创建的匿名共享内存了。点击Read按钮时,就通过memoryFile的readBytes接口把共享内存中的整数读出来,并显示在文本框中;点击Write按钮时,就通过memoryService这个代理类的setVal接口来调用MemoryService的本地实现类的setVal服务,从而把文本框中的数值写到Server端创建的匿名共享内存中去;点击Clear按钮时,就会清空文本框的内容。这样,我们就可以通过Read和Write按钮来验证我们是否在Client和Server两个进程中实现内存共享了。
现在,我们再来看看Client界面的配置文件,它定义在res/layout/main.xml文件中:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:gravity="center">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/value">
- </TextView>
- <EditText
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:id="@+id/edit_value"
- android:hint="@string/hint">
- </EditText>
- </LinearLayout>
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center">
- <Button
- android:id="@+id/button_read"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/read">
- </Button>
- <Button
- android:id="@+id/button_write"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/write">
- </Button>
- <Button
- android:id="@+id/button_clear"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/clear">
- </Button>
- </LinearLayout>
- </LinearLayout>
相关的字符串定义在res/values/strings.xml文件中:
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <string name="app_name">Ashmem</string>
- <string name="value">Value</string>
- <string name="hint">Please input a value...</string>
- <string name="read">Read</string>
- <string name="write">Write</string>
- <string name="clear">Clear</string>
- </resources>
这样,界面的相关配置文件就介绍完了。
我们还要再来看程序描述文件AndroidManifest.xml的相关配置,它位于Ashmem目录下:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="shy.luo.ashmem"
- android:sharedUserId="android.uid.system"
- android:versionCode="1"
- android:versionName="1.0">
- <application android:icon="@drawable/icon" android:label="@string/app_name">
- <activity android:name=".Client"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <service
- android:enabled="true"
- android:name=".Server"
- android:process=".Server" >
- <intent-filter>
- <action android:name="shy.luo.ashmem.server"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </service>
- </application>
- </manifest>
这里我们可以看到,下面的配置项把服务名称"shy.luo.ashmem.server"和本地服务类Server关联了起来:
- <service
- android:enabled="true"
- android:name=".Server"
- android:process=".Server" >
- <intent-filter>
- <action android:name="shy.luo.ashmem.server"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </service>
这样,我们就可以通过startService(new Intent("shy.luo.ashmem.server"))来启动这个Server了。不过,在Android中,启动服务是需要权限的,所以,下面这一行配置获取了启动服务需要的相应权限:
- android:sharedUserId="android.uid.system"
最后,我们来看工程的编译脚本文件Android.mk,它位于Ashmem目录下:
- LOCAL_PATH:= $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE_TAGS := optional
- LOCAL_SRC_FILES += $(call all-subdir-java-files)
- LOCAL_PACKAGE_NAME := Ashmem
- LOCAL_CERTIFICATE := platform
- include $(BUILD_PACKAGE)
这里又有一个关键的地方:
- LOCAL_CERTIFICATE := platform
因为我们需要在程序中启动Service,所以要配置这一行,并且要把源代码工程放在Android源代码平台中进行编译。
这样,整个例子的源代码实现就介绍完了,接下来就要编译了。有关如何单独编译Android源代码工程的模块,以及如何打包system.img,请参考如何单独编译Android源代码中的模块一文。
执行以下命令进行编译和打包:
- USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/Ashmem
- USER-NAME@MACHINE-NAME:~/Android$ make snod
这样,打包好的Android系统镜像文件system.img就包含我们前面创建的Ashmem应用程序了。
再接下来,就是运行模拟器来运行我们的例子了。关于如何在Android源代码工程中运行模拟器,请参考在Ubuntu上下载、编译和安装Android最新源代码一文。
执行以下命令启动模拟器:
- USER-NAME@MACHINE-NAME:~/Android$ emulator
模拟器启动起,就可以在Home Screen上看到Ashmem应用程序图标了:

点击Ashmem图标,启动Ashmem应用程序,界面如下:

这样,我们就可以验证程序的功能了,看看是否实现了在两个进程中通过使用Android系统的匿名共享内存机制来共享内存数据的功能。
通过这个例子的学习,相信读者对Android系统匿名共享内存子系统Ashmem有了一个大概的认识,但是,这种认识还是停留在表面上。我们在文章开始时就提到,Android系统匿名共享内存子系统Ashmem两个特点,一是能够辅助内存管理系统来有效地管理不再使用的内存块,二是它通过Binder进程间通信机制来实现进程间的内存共享。第二个特点我们在上面这个例子中看到了,但是似乎还不够深入,我们知道,在Linux系统中,文件描述符其实就是一个整数,它是用来索引进程保存在内核空间的打开文件数据结构的,而且,这个文件描述符只是在进程内有效,也就是说,在不同的进程中,相同的文件描述符的值,代表的可能是不同的打开文件,既然是这样,把Server进程中的文件描述符传给Client进程,似乎就没有用了,但是不用担心,在传输过程中,Binder驱动程序会帮我们处理好一切,保证Client进程拿到的文件描述符是在本进程中有效的,并且它指向就是Server进程创建的匿名共享内存文件。至于第一个特点,我们也准备在后续学习Android系统匿名共享内存子系统Ashmem时,再详细介绍。
导语:
在Android系统中,提供了独特的匿名共享内存子系统Ashmem(Anonymous Shared Memory),它以驱动程序的形式实现在内核空间中。它有两个特点:
1.一是能够辅助内存管理系统来有效地管理不再使用的内存块
2.二是它通过Binder进程间通信机制来实现进程间的内存共享。
本文中,我们将通过实例来简要介绍Android系统的匿名共享内存的使用方法,使得我们对Android系统的匿名共享内存机制有一个感性的认识,为进一步学习它的源代码实现打下基础。
案例原理:
Android系统的匿名共享内存子系统的主体是以驱动程序的形式存在。在系统运行时库层和应用程序框架层提供了访问接口,其中,在系统运行时库层提供了C/C++调用接口,而在应用程序框架层提供了Java调用接口。
这里,我们将直接通过应用程序框架层提供的Java调用接口来说明匿名共享内存子系统Ashmem的使用方法,毕竟我们在Android开发应用程序时,是基于Java语言的,而实际上,应用程序框架层的Java调用接口是通过JNI方法来调用系统运行时库层的C/C++调用接口,最后进入到内核空间的Ashmem驱动程序去的。
我们在这里举的例子是一个名为Ashmem的应用程序,它包含了一个Server端和一个Client端实现,
1. Server端是以Service的形式实现的,在这里Service里面,创建一个匿名共享内存文件
2. Client是一个Activity,这个Activity通过Binder进程间通信机制获得前面这个Service创建的匿名共享内存文件的句柄,从而实现共享。
在Android应用程序框架层,提供了一个MemoryFile接口来封装了匿名共享内存文件的创建和使用,它实现在frameworks/base/core/java/android/os/MemoryFile.java文件中。
下面,我们就来看看Server端是如何通过MemoryFile类来创建匿名共享内存文件的以及Client是如何获得这个匿名共享内存文件的句柄的。
在MemoryFile类中,提供了两种创建匿名共享内存的方法,我们通过MemoryFile类的构造函数来看看这两种使用方法:
public class MemoryFile{....../*** Allocates a new ashmem region. The region is initially not purgable.** @param name optional name for the file (can be null).* @param length of the memory file in bytes.* @throws IOException if the memory file could not be created.*/public MemoryFile(String name, int length) throws IOException {mLength = length;mFD = native_open(name, length);mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);mOwnsRegion = true;}/*** Creates a reference to an existing memory file. Changes to the original file* will be available through this reference.* Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail.** @param fd File descriptor for an existing memory file, as returned by* {@link #getFileDescriptor()}. This file descriptor will be closed* by {@link #close()}.* @param length Length of the memory file in bytes.* @param mode File mode. Currently only "r" for read-only access is supported.* @throws NullPointerException if <code>fd</code> is null.* @throws IOException If <code>fd</code> does not refer to an existing memory file,* or if the file mode of the existing memory file is more restrictive* than <code>mode</code>.** @hide*/public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException {if (fd == null) {throw new NullPointerException("File descriptor is null.");}if (!isMemoryFile(fd)) {throw new IllegalArgumentException("Not a memory file.");}mLength = length;mFD = fd;mAddress = native_mmap(mFD, length, modeToProt(mode));mOwnsRegion = false;}......}viewcopy
两个构造函数的主要区别是第一个参数
1.第一种构造方法是以指定的字符串调用JNI方法native_open来创建一个匿名共享内存文件,得到一个文件描述符,接着就以这个文件描述符为参数调用JNI方法natvie_mmap把这个匿名共享内存文件映射在进程空间中,然后就可以通过这个映射后得到的地址空间来直接访问内存数据了;
2.第二种构造方法是以指定的文件描述符来直接调用JNI方法natvie_mmap把这个匿名共享内存文件映射在进程空间中,然后进行访问,而这个文件描述符就必须要是一个匿名共享内存文件的文件描述符,这是通过一个内部函数isMemoryFile来验证的,而这个内部函数isMemoryFile也是通过JNI方法调用来进一步验证的。前面所提到的这些JNI方法调用,最终都是通过系统运行时库层进入到内核空间的Ashmem驱动程序中去,不过这里我们不关心这些JNI方法、系统运行库层调用以及Ashmem驱动程序的具体实现,在接下来的两篇文章中,我们将会着重介绍,这里我们只关注MemoryFile这个类的使用方法。
前面我们说到,我们在这里举的例子包含了一个Server端和一个Client端实现,
1. Server端就是通过第一个构造函数来创建一个匿名共享内存文件
2. Client端过Binder进程间通信机制来向Server请求获取这个匿名共享内存的文件描述符,有了这个文件描述符之后,就可以通过后面一个构造函数来共享这个内存文件了。 然后Client和Server之间就可以通过这个这个匿名内存共享数据了。
案例实现:
首先在源代码工程的packages/experimental目录下创建一个应用程序工程目录Ashmem。它定义了一个路径为shy.luo.ashmem的package,这个例子的源代码主要就是实现在这里了。
将会逐一介绍这个package里面的文件。这里要用到的Binder进程间通信接口定义在src/shy/luo/ashmem/IMemoryService.java文件中:
package shy.luo.ashmem;import android.util.Log;import android.os.IInterface;import android.os.Binder;import android.os.IBinder;import android.os.Parcel;import android.os.ParcelFileDescriptor;import android.os.RemoteException;public interface IMemoryService extends IInterface {public static abstract class Stub extends Binder implements IMemoryService {private static final String DESCRIPTOR = "shy.luo.ashmem.IMemoryService";public Stub() {attachInterface(this, DESCRIPTOR);}public static IMemoryService asInterface(IBinder obj) {if (obj == null) {return null;}IInterface iin = (IInterface)obj.queryLocalInterface(DESCRIPTOR);if (iin != null && iin instanceof IMemoryService) {return (IMemoryService)iin;}return new IMemoryService.Stub.Proxy(obj);}public IBinder asBinder() {return this;}@Overridepublic boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws android.os.RemoteException {switch (code) {case INTERFACE_TRANSACTION: {reply.writeString(DESCRIPTOR);return true;}case TRANSACTION_getFileDescriptor: {data.enforceInterface(DESCRIPTOR);ParcelFileDescriptor result = this.getFileDescriptor();reply.writeNoException();if (result != null) {reply.writeInt(1);result.writeToParcel(reply, 0);} else {reply.writeInt(0);}return true;}case TRANSACTION_setValue: {data.enforceInterface(DESCRIPTOR);int val = data.readInt();setValue(val);reply.writeNoException();return true;}}return super.onTransact(code, data, reply, flags);}private static class Proxy implements IMemoryService {private IBinder mRemote;Proxy(IBinder remote) {mRemote = remote;}public IBinder asBinder() {return mRemote;}public String getInterfaceDescriptor() {return DESCRIPTOR;}public ParcelFileDescriptor getFileDescriptor() throws RemoteException {Parcel data = Parcel.obtain();Parcel reply = Parcel.obtain();ParcelFileDescriptor result;try {data.writeInterfaceToken(DESCRIPTOR);mRemote.transact(Stub.TRANSACTION_getFileDescriptor, data, reply, 0);reply.readException();if (0 != reply.readInt()) {result = ParcelFileDescriptor.CREATOR.createFromParcel(reply);} else {result = null;}} finally {reply.recycle();data.recycle();}return result;}public void setValue(int val) throws RemoteException {Parcel data = Parcel.obtain();Parcel reply = Parcel.obtain();try {data.writeInterfaceToken(DESCRIPTOR);data.writeInt(val);mRemote.transact(Stub.TRANSACTION_setValue, data, reply, 0);reply.readException();} finally {reply.recycle();data.recycle();}}}static final int TRANSACTION_getFileDescriptor = IBinder.FIRST_CALL_TRANSACTION + 0;static final int TRANSACTION_setValue = IBinder.FIRST_CALL_TRANSACTION + 1;}public ParcelFileDescriptor getFileDescriptor() throws RemoteException;public void setValue(int val) throws RemoteException;}view pcopy
public ParcelFileDescriptor getFileDescriptor() throws RemoteException;public void setValue(int val) throws RemoteException;
package shy.luo.ashmem;import java.io.FileDescriptor;import java.io.IOException;import android.os.Parcel;import android.os.MemoryFile;import android.os.ParcelFileDescriptor;import android.util.Log;public class MemoryService extends IMemoryService.Stub {private final static String LOG_TAG = "shy.luo.ashmem.MemoryService";private MemoryFile file = null;public MemoryService() {try {file = new MemoryFile("Ashmem", 4);setValue(0);}catch(IOException ex) {Log.i(LOG_TAG, "Failed to create memory file.");ex.printStackTrace();}}public ParcelFileDescriptor getFileDescriptor() {Log.i(LOG_TAG, "Get File Descriptor.");ParcelFileDescriptor pfd = null;try {pfd = file.getParcelFileDescriptor();} catch(IOException ex) {Log.i(LOG_TAG, "Failed to get file descriptor.");ex.printStackTrace();}return pfd;}public void setValue(int val) {if(file == null) {return;}byte[] buffer = new byte[4];buffer[0] = (byte)((val >>> 24) & 0xFF);buffer[1] = (byte)((val >>> 16) & 0xFF);buffer[2] = (byte)((val >>> 8) & 0xFF);buffer[3] = (byte)(val & 0xFF);try {file.writeBytes(buffer, 0, 0, 4);Log.i(LOG_TAG, "Set value " + val + " to memory file. ");}catch(IOException ex) {Log.i(LOG_TAG, "Failed to write bytes to memory file.");ex.printStackTrace();}}}view pcopy
这里还实现了IMemoryService.Stub的两个接口getFileDescriptor和setVal,一个用来获取匿名共享内存文件的文件描述符,一个来往匿名共享内存文件中写入一个整数,其中,接口getFileDescriptor的返回值是一个ParcelFileDescriptor。在Java中,是用FileDescriptor类来表示一个文件描述符的,而ParcelFileDescriptor是用来序列化FileDescriptor的,以便在进程间调用时传输。
定义好本地服务好,就要定义一个Server来启动这个服务了。这里定义的Server实现在src/shy/luo/ashmem/Server.java文件中:
package shy.luo.ashmem;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.util.Log;import android.os.ServiceManager;public class Server extends Service {private final static String LOG_TAG = "shy.luo.ashmem.Server";private MemoryService memoryService = null;@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {Log.i(LOG_TAG, "Create Memory Service...");memoryService = new MemoryService();try {ServiceManager.addService("AnonymousSharedMemory", memoryService);Log.i(LOG_TAG, "Succeed to add memory service.");} catch (RuntimeException ex) {Log.i(LOG_TAG, "Failed to add Memory Service.");ex.printStackTrace();}}@Overridepublic void onStart(Intent intent, int startId) {Log.i(LOG_TAG, "Start Memory Service.");}@Overridepublic void onDestroy() {Log.i(LOG_TAG, "Destroy Memory Service.");}}
package shy.luo.ashmem;import java.io.FileDescriptor;import java.io.IOException;import shy.luo.ashmem.R;import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.os.MemoryFile;import android.os.ParcelFileDescriptor;import android.os.ServiceManager;import android.os.RemoteException;import android.util.Log;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.EditText;public class Client extends Activity implements OnClickListener {private final static String LOG_TAG = "shy.luo.ashmem.Client";IMemoryService memoryService = null;MemoryFile memoryFile = null;private EditText valueText = null;private Button readButton = null;private Button writeButton = null;private Button clearButton = null;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);IMemoryService ms = getMemoryService();if(ms == null) {startService(new Intent("shy.luo.ashmem.server"));} else {Log.i(LOG_TAG, "Memory Service has started.");}valueText = (EditText)findViewById(R.id.edit_value);readButton = (Button)findViewById(R.id.button_read);writeButton = (Button)findViewById(R.id.button_write);clearButton = (Button)findViewById(R.id.button_clear);readButton.setOnClickListener(this);writeButton.setOnClickListener(this);clearButton.setOnClickListener(this);Log.i(LOG_TAG, "Client Activity Created.");}@Overridepublic void onResume() {super.onResume();Log.i(LOG_TAG, "Client Activity Resumed.");}@Overridepublic void onPause() {super.onPause();Log.i(LOG_TAG, "Client Activity Paused.");}@Overridepublic void onClick(View v) {if(v.equals(readButton)) {int val = 0;MemoryFile mf = getMemoryFile();if(mf != null) {try {byte[] buffer = new byte[4];mf.readBytes(buffer, 0, 0, 4);val = (buffer[0] << 24) | ((buffer[1] & 0xFF) << 16) | ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF);} catch(IOException ex) {Log.i(LOG_TAG, "Failed to read bytes from memory file.");ex.printStackTrace();}}String text = String.valueOf(val);valueText.setText(text);} else if(v.equals(writeButton)) {String text = valueText.getText().toString();int val = Integer.parseInt(text);IMemoryService ms = getMemoryService();if(ms != null) {try {ms.setValue(val);} catch(RemoteException ex) {Log.i(LOG_TAG, "Failed to set value to memory service.");ex.printStackTrace();}}} else if(v.equals(clearButton)) {String text = "";valueText.setText(text);}}private IMemoryService getMemoryService() {if(memoryService != null) {return memoryService;}memoryService = IMemoryService.Stub.asInterface(ServiceManager.getService("AnonymousSharedMemory"));Log.i(LOG_TAG, memoryService != null ? "Succeed to get memeory service." : "Failed to get memory service.");return memoryService;}private MemoryFile getMemoryFile() {if(memoryFile != null) {return memoryFile;}IMemoryService ms = getMemoryService();if(ms != null) {try {ParcelFileDescriptor pfd = ms.getFileDescriptor();if(pfd == null) {Log.i(LOG_TAG, "Failed to get memory file descriptor.");return null;}try {FileDescriptor fd = pfd.getFileDescriptor();if(fd == null) {Log.i(LOG_TAG, "Failed to get memeory file descriptor.");return null;}memoryFile = new MemoryFile(fd, 4, "r");} catch(IOException ex) {Log.i(LOG_TAG, "Failed to create memory file.");ex.printStackTrace();}} catch(RemoteException ex) {Log.i(LOG_TAG, "Failed to get file descriptor from memory service.");ex.printStackTrace();}}return memoryFile;}}view pcopy
这个Activity在onCreate时,会通过startService接口来启动我们前面定义的Server进程。调用startService时,需要指定要启动的服务的名称,这里就是"shy.luo.ashmem.server"了,后面我们会在程序的描述文件AndroidManifest.xml看到前面的Server类是如何和名称"shy.luo.ashmem.server"关联起来的。关于调用startService函数来启动自定义服务的过程,可以参考Android系统在新进程中启动自定义服务过程(startService)的原理分析一文。
内部函数getMemoryService用来获取IMemoryService。如果是第一次调用该函数,则会通过ServiceManager的getService接口来获得这个IMemoryService接口,然后保存在类成员变量memoryService中,以后再调用这个函数时,就可以直接返回memoryService了。
内部函数getMemoryFile用来从MemoryService中获得匿名共享内存文件的描述符。同样,如果是第一次调用该函数,则会通过IMemoryService的getFileDescriptor接口来获得MemoryService中的匿名共享内存文件的描述符,然后用这个文件描述符来创建一个MemoryFile实例,并保存在类成员变量memoryFile中,以后再调用这个函数时,就可以直接返回memoryFile了。
有了memoryService和memoryFile后,我们就可以在Client端访问Server端创建的匿名共享内存了。点击Read按钮时,就通过memoryFile的readBytes接口把共享内存中的整数读出来,并显示在文本框中;点击Write按钮时,就通过memoryService这个代理类的setVal接口来调用MemoryService的本地实现类的setVal服务,从而把文本框中的数值写到Server端创建的匿名共享内存中去;点击Clear按钮时,就会清空文本框的内容。这样,我们就可以通过Read和Write按钮来验证我们是否在Client和Server两个进程中实现内存共享了。
现在,我们再来看看Client界面的配置文件,它定义在res/layout/main.xml文件中:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:gravity="center">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/value">
- </TextView>
- <EditText
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:id="@+id/edit_value"
- android:hint="@string/hint">
- </EditText>
- </LinearLayout>
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center">
- <Button
- android:id="@+id/button_read"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/read">
- </Button>
- <Button
- android:id="@+id/button_write"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/write">
- </Button>
- <Button
- android:id="@+id/button_clear"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/clear">
- </Button>
- </LinearLayout>
- </LinearLayout>
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <string name="app_name">Ashmem</string>
- <string name="value">Value</string>
- <string name="hint">Please input a value...</string>
- <string name="read">Read</string>
- <string name="write">Write</string>
- <string name="clear">Clear</string>
- </resources>
这样,界面的相关配置文件就介绍完了。
我们还要再来看程序描述文件AndroidManifest.xml的相关配置,它位于Ashmem目录下:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="shy.luo.ashmem"
- android:sharedUserId="android.uid.system"
- android:versionCode="1"
- android:versionName="1.0">
- <application android:icon="@drawable/icon" android:label="@string/app_name">
- <activity android:name=".Client"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <service
- android:enabled="true"
- android:name=".Server"
- android:process=".Server" >
- <intent-filter>
- <action android:name="shy.luo.ashmem.server"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </service>
- </application>
- </manifest>
- <service
- android:enabled="true"
- android:name=".Server"
- android:process=".Server" >
- <intent-filter>
- <action android:name="shy.luo.ashmem.server"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </service>
- android:sharedUserId="android.uid.system"
- LOCAL_PATH:= $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE_TAGS := optional
- LOCAL_SRC_FILES += $(call all-subdir-java-files)
- LOCAL_PACKAGE_NAME := Ashmem
- LOCAL_CERTIFICATE := platform
- include $(BUILD_PACKAGE)
- LOCAL_CERTIFICATE := platform
这样,整个例子的源代码实现就介绍完了,接下来就要编译了。有关如何单独编译Android源代码工程的模块,以及如何打包system.img,请参考如何单独编译Android源代码中的模块一文。
执行以下命令进行编译和打包:
- USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/Ashmem
- USER-NAME@MACHINE-NAME:~/Android$ make snod
再接下来,就是运行模拟器来运行我们的例子了。关于如何在Android源代码工程中运行模拟器,请参考在Ubuntu上下载、编译和安装Android最新源代码一文。
执行以下命令启动模拟器:
- USER-NAME@MACHINE-NAME:~/Android$ emulator

点击Ashmem图标,启动Ashmem应用程序,界面如下:

这样,我们就可以验证程序的功能了,看看是否实现了在两个进程中通过使用Android系统的匿名共享内存机制来共享内存数据的功能。
通过这个例子的学习,相信读者对Android系统匿名共享内存子系统Ashmem有了一个大概的认识,但是,这种认识还是停留在表面上。我们在文章开始时就提到,Android系统匿名共享内存子系统Ashmem两个特点,一是能够辅助内存管理系统来有效地管理不再使用的内存块,二是它通过Binder进程间通信机制来实现进程间的内存共享。第二个特点我们在上面这个例子中看到了,但是似乎还不够深入,我们知道,在Linux系统中,文件描述符其实就是一个整数,它是用来索引进程保存在内核空间的打开文件数据结构的,而且,这个文件描述符只是在进程内有效,也就是说,在不同的进程中,相同的文件描述符的值,代表的可能是不同的打开文件,既然是这样,把Server进程中的文件描述符传给Client进程,似乎就没有用了,但是不用担心,在传输过程中,Binder驱动程序会帮我们处理好一切,保证Client进程拿到的文件描述符是在本进程中有效的,并且它指向就是Server进程创建的匿名共享内存文件。至于第一个特点,我们也准备在后续学习Android系统匿名共享内存子系统Ashmem时,再详细介绍。
0-Android使用Ashmem机制进行跨进程共享内存的更多相关文章
- 基于xposed实现android注册系统服务,解决跨进程共享数据问题
昨花了点时间,参考github issues 总算实现了基于xposed的系统服务注入,本文目的是为了“解决应用之间hook后数据共享,任意app ServiceManager.getService就 ...
- android不需要Socket的跨进程推送消息AIDL!
上篇介绍了跨进程实时通讯http://www.cnblogs.com/xiaoxiaing/p/5818161.html 但是他有个缺点就是服务端无法推送消息给客户端,今天这篇文章主要说的就是服务器推 ...
- Android中使用ContentProvider进行跨进程方法调用
原文同一时候发表在我的博客 点我进入还能看到很多其它 需求背景 近期接到这样一个需求,须要和别的 App 进行联动交互,比方下载器 App 和桌面 App 进行联动.桌面的 App 能直接显示下载器 ...
- android 史上最简单易懂的跨进程通讯(Messenger)!
不需要AIDL也不需要复杂的ContentProvider,也不需要SharedPreferences或者共享存储文件! 只需要简单易懂的Messenger,它也称为信使,通过它可以在不同进程中传递m ...
- 【读书笔记】Android的Ashmem机制学习
Ashmem是安卓在linux基础上添加的驱动模块,就是说安卓有linux没有的功能. Ashmem模块在内核层面上实现,在运行时库和应用程序框架层提供了访问接口.在运行时库层提供的是C++接口,在应 ...
- ContentProvider跨进程共享数据
借用ContentResolver类访问ContentProvider中共享的数据.通过getContentResolver()方法获得该类的实例. ContentResolver中的方法:inser ...
- Android 跨进程数据共享
Android 开发过程中,基于功能隔离.进程安全.进程保活等等考虑,我们经常需要为应用划分进程,然后不得不面临跨进程通信和跨进程共享数据的挑战. 跨进程通信 相对来说,跨进程通信比较简单,常用的方式 ...
- Android跨进程通信的四种方式
由于android系统中应用程序之间不能共享内存.因此,在不同应用程序之间交互数据(跨进程通讯)就稍微麻烦一些.在android SDK中提供了4种用于跨进程通讯的方式.这4种方式正好对应于andro ...
- android 跨进程通信
转自:http://www.androidsdn.com/article/show/137 由于android系统中应用程序之间不能共享内存.因此,在不同应用程序之间交互数据(跨进程通讯)就稍微麻烦一 ...
随机推荐
- python基础一 day17 初识递归
#递归函数 # 了解什么是递归 : 在函数中调用自身函数 # 最大递归深度默认是997/998 —— 是python从内存角度出发做得限制 # 能看懂递归 # 能知道递归的应用场景 # 初识递归 —— ...
- vue-awesome-swiper实现轮播图
1.首先通过npm安装vue-awesome-swiper,我在项目中用的是2.6.7版本 npm install vue-awesome-swiper@2.6.7 –save 2. 在main.js ...
- Drop it-freecodecamp算法题目
Drop it 1.要求 丢弃数组(arr)的元素,从左边开始,直到回调函数return true就停止. 第二个参数,func,是一个函数.用来测试数组的第一个元素,如果返回fasle,就从数组中抛 ...
- 1061: [Noi2008]志愿者招募
Time Limit: 20 Sec Memory Limit: 162 MBSubmit: 5742 Solved: 3449[Submit][Status][Discuss] Descript ...
- APCInject
#include <iostream> #include <Windows.h> #include <TlHelp32.h> using namespace std ...
- JS - Object.create(prototype)方法
用Object.create(prototype)方法创建一个对象,这个对象的原型将指向这个传入的prototype参数
- node操作mogondb数据库的封装
注:摘自网络 上面的注释都挺详细的,我使用到了nodejs的插件mongoose,用mongoose操作mongodb其实蛮方便的. 关于mongoose的安装就是 npm install -g mo ...
- Scrapy-redis分布式爬虫爬取豆瓣电影详情页
平时爬虫一般都使用Scrapy框架,通常都是在一台机器上跑,爬取速度也不能达到预期效果,数据量小,而且很容易就会被封禁IP或者账号,这时候可以使用代理IP或者登录方式爬,然而代理IP很多时候都很鸡肋, ...
- 51nod 1267二分+优化试验场
最初,最开始的时候,万能的学姐曾经警告过我们,千万别用什么老狮子MAP,手撸map或者字典树...当时不甚理解...今天...这题直接卡掉了我的MAP,但是使用朴素方法进行二分...不加优化,,都不需 ...
- Java集合---简介
概念 集合可以理解为一个动态的对象数组,不同的是集合中的对象内容可以任意扩充.Java最基本的集合接口:Collection接口 集合的特点 性能高 容易扩展和修改 Collection的常用子类 L ...

















