初涉IPC,了解AIDL的工作原理及用法


今天来讲讲AIDL。这个神奇的AIDL,也是近期在学习的,看了某课大神的解说写下的blog,希望结合自己的看法给各位同价通俗易懂的解说

官方文档:http://developer.android.com/guide/components/aidl.html

一.What is AIDL?(什么是AIDL)

AIDL:Android Interface Definition Language (Android接口定义语言)

首先,我们要知道。进程1和进程2,我们要怎样让他通讯?

在Android系统的虚拟机中,每个app执行都是在一个独立的沙箱里面的,这种话。一个应用崩溃也不会影响到其它应用,这样我们就提出了一个问题。跨进程如怎样进行通讯?怎样传递数据?事实上两个进程是不能直接通讯的。他须要Android系统底层的帮助来进行通讯!那就是我们每个人都知道的四大组件

我们首先还是先进Google的API看看

他大概的意思是他同意你去定义一个自己的标准。使用的是IPC(进程间通讯)进制,跨进程通讯,有兴趣的能够去翻译一下,只是值得注意的是他的第二段标记

这里提到了两个东西

  • Binder
  • Messenger

我们继续往下看话就知道

  • Binder
  • Messenger

翻译:假设您不须要执行并发IPC在不同的应用程序中 你就用Binder ,或者假设你想执行IPC,但不须要处理多线程,实现你的接口Messenger。不管怎样,确保你了解实现AIDL之前绑定服务。

所以我们就能理清晰AIDL的概念了

  • AIDL //IPC 多应用 多线程

二.Defining an AIDL Interface(语法)

文档中强调。你必须定义一个.aidl的文件,那我们怎样去创建尼?

1. Create the .aidl file(创建一个.aidl文件)

官方提供的语法

// IRemoteService.aidl
package 包名(com.lgl.android);// Declare any non-default types here with import statements/** Example service interface  接口*/
interface IRemoteService {/** Request the process ID of this service, to do evil things with it.  方法*/int getPid();/** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
}

我们来做一个简单的演示

我们要使用的编译工具在我们的SDK/buidl-tools/android版本号/aidl.bat,只是实际开发中也不须要手动编译,我们新建一个项目ForAIDL,这里我们使用的开发工具是Android Studio,事实上Eclipse可能更加让人熟悉,只是用AS也是大势所趋了。并且AS的文件夹结构我也非常喜欢

我们直接java-new - folder - AIDL folder

然后你就会发现多了一个aidl的文件夹

我们继续新建一个包名,新建一个文件文件new - AIDL - file

大致的内容

// IMyAidlInterface.aidl
package com.lgl.foraidl;// Declare any non-default types here with import statementsinterface IMyAidlInterface {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
}

AS默认是不会去又一次构建Gradle,我们点击一下构建button

然后我们就能够在MainActivity中调用了

三.AIDL Client And Service(client和服务端)

如今就好玩了。我们先来理理思路,通常是这种,我们一个软件有某个功能。也就是服务端,然后client通过AIDL去訪问

这里能够看到服务端仅仅是处理一些操作,我们client去请求,这种话,那我创建一个AIDLService,相同的。我们须要去创建AIDL文件。命名-ServiceAidlInterface

ServiceAidlInterface

// ServiceAidlInterface.aidl
package com.lgl.aidlservice;// Declare any non-default types here with import statementsinterface ServiceAidlInterface {/**计算两数之和*/int add(int num1,int num2);
}

如今我们处理的就不是默认的东西了,AIDL的原理就是你自己定义语言接口,对的,我们也来

2. Implement the interface(实现一个AIDL)

依据我们的Google文档,第一步Create the .aidl file已经完毕了,如今就来进行第二步了,我们这里须要使用到Service,看文档

这种话我们新建一个IRemoteService继承service,我们实现计算的逻辑处理

package com.lgl.aidlservice;import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;/**** 监听服务* Created by lgl on 16/3/13.*/
public class IRemoteService extends Service{/*** 当client绑定到该服务的时候启动onBind* @param intent* @return*/@Overridepublic IBinder onBind(Intent intent) {//绑定之后就计算return iBinder;}/*** 開始处理结果*/private IBinder iBinder = new ServiceAidlInterface.Stub(){@Overridepublic int add(int num1, int num2) throws RemoteException {Log.i("AIDL","收到了请求,参數1"+num1+"参數2"+num2);return num1+num2;}};
}

3. Expose the interface to clients(client的实现)

OK,写完了服务端就能够写client了,我们直接new一个Module-AIDLClients

我們先按剛才的逻辑,把界面写了

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:gravity="center"android:padding="15dp"><TextView
        android:text="AIDL"android:textSize="50sp"android:layout_width="wrap_content"android:layout_height="wrap_content" /><EditText
       android:hint="请输入数字1"android:id="@+id/et_num1"android:layout_width="match_parent"android:layout_height="wrap_content" /><TextView
        android:layout_marginBottom="15dp"android:layout_marginTop="15dp"android:textSize="20sp"android:text="+"android:layout_width="wrap_content"android:layout_height="wrap_content" /><EditText
        android:hint="请输入数字2"android:id="@+id/et_num2"android:layout_width="match_parent"android:layout_height="wrap_content" /><TextView
        android:layout_marginBottom="15dp"android:layout_marginTop="15dp"android:textSize="20sp"android:text="="android:layout_width="wrap_content"android:layout_height="wrap_content" /><TextView
        android:id="@+id/tv_result"android:text="结果:"android:layout_width="wrap_content"android:layout_height="wrap_content" /><Button
        android:textColor="#fff"android:background="@android:color/holo_blue_light"android:layout_marginTop="30dp"android:id="@+id/btn_ok"android:text="计算"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout>

我们要想client调用服务端的内容。那么就一定要定义标准的语言。所以client的aidl和服务端必须一致

记住,每次不通过的时候先编译一遍

MainActivity(client)

package com.lgl.aidlclients;import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;import com.lgl.aidlservice.ServiceAidlInterface;/*** AIDL client* @author lgl*/
public class MainActivity extends AppCompatActivity implements View.OnClickListener {//输入private EditText et_num1,et_num2;//结果private TextView tv_result;//AIDL远程訪问private Button btn_ok;private ServiceAidlInterface aidl;private ServiceConnection conn = new ServiceConnection() {//连接上@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {//拿到远程服务aidl = ServiceAidlInterface.Stub.asInterface(iBinder);}//断开时@Overridepublic void onServiceDisconnected(ComponentName componentName) {//回收aidl = null;}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();bindServices();}private void initView() {et_num1 = (EditText) findViewById(R.id.et_num1);et_num2 = (EditText) findViewById(R.id.et_num2);tv_result = (TextView) findViewById(R.id.tv_result);btn_ok = (Button) findViewById(R.id.btn_ok);btn_ok.setOnClickListener(this);}@Overridepublic void onClick(View view) {switch (view.getId()){case R.id.btn_ok:int num1 = Integer.parseInt(et_num1.getText().toString());int num2 = Integer.parseInt(et_num2.getText().toString());try {//结果int res = aidl.add(num1,num2);tv_result.setText("结果:"+res);} catch (RemoteException e) {e.printStackTrace();}break;}}/*** 开启服务*/private void bindServices() {//获取服务端Intent intent = new Intent();//5.0之后的改变intent.setComponent(new ComponentName("com.lgl.aidlservice","com.lgl.aidlservice.IRemoteService"));//绑定服务bindService(intent,conn, Context.BIND_AUTO_CREATE);}/*** 销毁服务*/@Overrideprotected void onDestroy() {super.onDestroy();unbindService(conn);}
}

这里逻辑也是十分的清晰。你创建了之后启动服务。我调用方法来计算,我们来演示一下,我们先启动服务端,再启动client

记得注冊

 <service android:name=".IRemoteService"android:process=":remote"android:exported="true" ><intent-filter><category android:name="android.intent.category.DEFAULT" /><action android:name="com.lgl.aidlservice.IRemoteService" /></intent-filter></service>

写到这里才看到玉刚也写了相关的信息,这边android:exported=”true”为权限问题。详细能够看下:
android跨进程通信(IPC):使用AIDL

只是这终究也仅仅是一些小菜,我们项目中也不可能用到这么low的方法。毕竟AIDL还是非常强大的,既然这样,那我们来玩玩高级一点的

四.AIDL数据传递

我们不能传递非常大的数据这是总所周知的,那AIDL默认支持什么类型的尼?我们可不能够自己定义尼?当然能够

1.默认数据类型

我们能够看一下官方文档

  • 1.基本数据类型
  • 2.String
  • 3.CharSequence
  • 4.List
  • 5.Map
  • 6.Parcelable(序列化)

我们新建一个aidl文件,他都会自己生成一个方法

void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);

意指的就是基础类型
然而在我们的实际測试中,你会发现他是不支持short的,我们做这种小測试

 void basicTypes(byte b,short s, int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);

然后编译

/Users/lgl/Documents/Android/ASCode/AIDLService/app/src/main/aidl/com/lgl/aidlservice/IMyAidlInterface.aidl:11 parameter s (2) unknown type short

他报的错误就是不支持short

再比方说,你想写一个List< String>list的数据。你必须指定他是in还是out类型的

in List< String>list

2.自己定义类型

这里我们先在服务端新建一个aidl文件-IMyAidlInterface,然后写入我们的方法

     //我们返回的数据是一个List集合List<Person>add(in Person person);

这里报错是毋庸置疑的,由于他不识别我们的Person,可是就算我们新建一个Person类也是没用的。由于aidl不支持这个类型,上面也说了,他仅仅支持基本类型和其它有限的几种数据类型。这种话。我们就须要自己定义了,怎么来呢?让他implements Parcelable就能够

package com.lgl.aidlservice;import android.os.Parcel;
import android.os.Parcelable;/*** 序列化实体类* Created by lgl on 16/3/15.*/
public class Person implements Parcelable{private String name;private int age;//构造方法public Person(String name,int age){this.name =name;this.age = age;}protected Person(Parcel in) {name = in.readString();age = in.readInt();}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel parcel, int i) {parcel.writeString(name);parcel.writeInt(age);}public static final Creator<Person> CREATOR = new Creator<Person>() {@Overridepublic Person createFromParcel(Parcel in) {return new Person(in);}@Overridepublic Person[] newArray(int size) {return new Person[size];}};
}

这里值得注意的就是读写部分了,你是怎么写的就得怎么读。顺序错了就没用了。那我们编译一下,你会发现他还是提示错误,依旧是位置的List数据类型

/Users/lgl/Documents/Android/ASCode/AIDLService/app/src/main/aidl/com/lgl/aidlservice/IMyAidlInterface.aidl:9 unknown return type List<Person>

这里就须要我们手动导入这个Person了不然人家也不认识啊。那要怎么做?还是须要创建一个Person的AIDL文件。内容十分的简单。说明文件

// IMyAidlInterface.aidl
package com.lgl.aidlservice;parcelable Person;

能够看到,里面就一句话,好的,如今编译通过了,那我们又一次来改动一下IRemoteService了,这次就不是返回计算数据的和了

package com.lgl.aidlservice;import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;import java.util.ArrayList;
import java.util.List;/**** 监听服务* Created by lgl on 16/3/13.*/
public class IRemoteService extends Service{private ArrayList<Person>persons;/*** 当client绑定到该服务的时候启动onBind* @param intent* @return*/@Overridepublic IBinder onBind(Intent intent) {persons = new ArrayList<Person>();//绑定之后就计算return iBinder;}/*** 開始处理结果*/
//    private IBinder iBinder = new ServiceAidlInterface.Stub(){//
//        @Override
//        public int add(int num1, int num2) throws RemoteException {//            Log.i("AIDL","收到了请求,参數1"+num1+"参數2"+num2);
//            return num1+num2;
//        }
//    };/*** 序列化*/private IBinder iBinder = new IMyAidlInterface.Stub(){@Overridepublic List<Person> add(Person person) throws RemoteException {//每添加一个人都能得到返回persons.add(person);return persons;}};
}

好的,服务端部分写完了,刚開始可能是有点混乱的。只是当你确实已经理解之后你会发现,也就这么点东西的逻辑,那我们继续来写client,看他是怎么进行序列化的处理的。

还是一样,我们把Person.aidl和IMyAidlInterface.aidl复制过去。顺便把Person类拷贝过来。这里要注意的事就是包名要一致

然后我们就开启服务了

package com.lgl.aidlclients;import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;import com.lgl.aidlservice.IMyAidlInterface;
import com.lgl.aidlservice.Person;
import com.lgl.aidlservice.ServiceAidlInterface;import java.util.ArrayList;/*** AIDL client* @author lgl*/
public class MainActivity extends AppCompatActivity implements View.OnClickListener {//输入private EditText et_num1,et_num2;//结果private TextView tv_result;//AIDL远程訪问private Button btn_ok;private ServiceAidlInterface aidl;private IMyAidlInterface imy;private ServiceConnection conn = new ServiceConnection() {//连接上@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {//拿到远程服务aidl = ServiceAidlInterface.Stub.asInterface(iBinder);imy = IMyAidlInterface.Stub.asInterface(iBinder);}//断开时@Overridepublic void onServiceDisconnected(ComponentName componentName) {//回收aidl = null;imy = null;}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);bindServices();initView();}private void initView() {et_num1 = (EditText) findViewById(R.id.et_num1);et_num2 = (EditText) findViewById(R.id.et_num2);tv_result = (TextView) findViewById(R.id.tv_result);btn_ok = (Button) findViewById(R.id.btn_ok);btn_ok.setOnClickListener(this);}@Overridepublic void onClick(View view) {switch (view.getId()){case R.id.btn_ok://                int n1 = Integer.parseInt(et_num1.getText().toString());
//                int n2 = Integer.parseInt(et_num2.getText().toString());
//
//                try {//                    //结果
//                    int res = aidl.add(n1,n2);
//                    tv_result.setText("结果:"+res);
//                } catch (RemoteException e) {//                    e.printStackTrace();
//                    tv_result.setText("Error");
//                }try{ArrayList<Person>persons = (ArrayList<Person>) imy.add(new Person("name",21));Log.i("Person",persons.toString());}catch (RemoteException e){e.printStackTrace();}break;}}/*** 开启服务*/private void bindServices() {//获取服务端Intent intent = new Intent();//5.0之后的改变intent.setComponent(new ComponentName("com.lgl.aidlservice","com.lgl.aidlservice.IRemoteService"));//绑定服务bindService(intent,conn, Context.BIND_AUTO_CREATE);}/*** 销毁服务*/@Overrideprotected void onDestroy() {super.onDestroy();unbindService(conn);}
}

这种话,仅仅要我们点击了计算button,Log打印一条消息,那事实是如此吗?我们执行一下

不会发现,我们每一次加入都是多添加了一行,相当于我们远程加入了一行

五.AIDL的工作原理

我们每次编译AIDL的文件的时候,都会编译成一个JAVA文件,详细是什么,我们一起研究研究。借用大神的一张图,你如今可能看不懂,等讲完原理你就懂了

编译好的JAVA文件在

/** This file is auto-generated.  DO NOT MODIFY.* Original file: /Users/lgl/Documents/Android/ASCode/AIDLService/aidlclients/src/main/aidl/com/lgl/aidlservice/IMyAidlInterface.aidl*/
package com.lgl.aidlservice;
//他是继承系统的接口。也就是说他本身就是一个接口
public interface IMyAidlInterface extends android.os.IInterface
{/** 存根。并且实现了自己的接口*/
public static abstract class Stub extends android.os.Binder implements com.lgl.aidlservice.IMyAidlInterface
{private static final java.lang.String DESCRIPTOR = "com.lgl.aidlservice.IMyAidlInterface";
/**自己的构造方法*/
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/*** Cast an IBinder object into an com.lgl.aidlservice.IMyAidlInterface interface,* generating a proxy if needed.*/
public static com.lgl.aidlservice.IMyAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.lgl.aidlservice.IMyAidlInterface))) {
return ((com.lgl.aidlservice.IMyAidlInterface)iin);
}
return new com.lgl.aidlservice.IMyAidlInterface.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{//返回的this是Stub
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_add:
{
data.enforceInterface(DESCRIPTOR);
com.lgl.aidlservice.Person _arg0;
if ((0!=data.readInt())) {//假设拿到传过来数值封装成一个data
_arg0 = com.lgl.aidlservice.Person.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
java.util.List<com.lgl.aidlservice.Person> _result = this.add(_arg0);
reply.writeNoException();//最后通过这个又写回去了,这样就完毕了整个的通讯
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}//代理,他是Stud的内部类
private static class Proxy implements com.lgl.aidlservice.IMyAidlInterface
{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;
}
//我们返回的数据是一个List集合@Override public java.util.List<com.lgl.aidlservice.Person> add(com.lgl.aidlservice.Person person) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.lgl.aidlservice.Person> _result;
try {//写入了类名
_data.writeInterfaceToken(DESCRIPTOR);
if ((person!=null)) {//写了一个1,同一时候add数据,当你拿到了远程服务的时候,事实上你拿到的仅仅是远程服务的代理
_data.writeInt(1);
person.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}//传入到onTransact
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.lgl.aidlservice.Person.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
//我们返回的数据是一个List集合//我们创建AIDL命名的这种方法
public java.util.List<com.lgl.aidlservice.Person> add(com.lgl.aidlservice.Person person) throws android.os.RemoteException;
}

Ok,AIDL这边算是讲完了,假设有兴趣的话。能够再去深入研究一下。我这边也仅仅是学习了大神的视频,然后做的一个小总结和小笔记

Demo下载:http://download.csdn.net/detail/qq_26787115/9462515

初涉IPC,了解AIDL的工作原理及用法相关推荐

  1. 从 Android 6.0 源码的角度剖析 Binder 工作原理 | CSDN 博文精选

    在从Android 6.0源码的角度剖析Activity的启动过程一文(https://blog.csdn.net/AndrExpert/article/details/81488503)中,我们了解 ...

  2. Android 10.0 PackageManagerService(一)工作原理及启动流程-[Android取经之路]

    摘要:PackageManagerService是Android系统核心服务之一,在Android中的非常重要,主要负责APK.jar包等的管理. 阅读本文大约需要花费50分钟. 文章的内容主要还是从 ...

  3. 史上最全!图解浏览器的工作原理

    可能每一个前端工程师都想要理解浏览器的工作原理. 我们希望知道从在浏览器地址栏中输入 url 到页面展现的短短几秒内浏览器究竟做了什么: 我们希望了解平时常常听说的各种代码优化方案是究竟为什么能起到优 ...

  4. 浏览器工作原理与实践学习笔记

    浏览器工作原理与实践 参考来源: 极客时间-李兵专栏 李兵简介: 08年,在 Chromium 和 IE 发布了一款双核浏览器:太阳花,国内第一款双核浏览器 ,支持chrome并兼容IE,日活达到了2 ...

  5. 2019 Android 高级面试题总结 从java语言到AIDL使用与原理

    说下你所知道的设计模式与使用场景 a.建造者模式: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 使用场景比如最常见的AlertDialog,拿我们开发过程中举例,比如C ...

  6. AIDL使用以及原理分析

    AIDL使用以及IPC原理分析(进程间通信) 概要 为了大家能够更好的理解android的进程间通信原理,以下将会从以下几个方面讲解跨进程通讯信: 1. 必要了解的概念 2. 为什么要使用aidl进程 ...

  7. <<浏览器工作原理与实践>>读书笔记

    1. 进程和线程的区别 概念: 进程:操作系统进行资源分配和调度的基本单位:程序的运行实例:(在我们启动一个程序的时候,操作系统会为该程序创建一块内存空间,用来存放代码,运行数据,和执行任务的主线程, ...

  8. 《浏览器工作原理与实践》学习笔记

    浏览器原理 前言 本文是学习李兵老师的<浏览器工作原理与实践>过程中记录笔记,详细链接见文末 进程vs线程 进程:一个应用程序的运行实例就是一个进程,详细来说就是:启动一个应用程序的时候, ...

  9. docker 基础工作原理(一)

    注:以下博文来源于我的独立博客网站:http://www.chenbiaolong.com/,由于原网站是用markdown写的,复制到这边格式有点问题. 以后博文将主要迁移到www.chenbiao ...

  10. RemoteViews的作用和工作原理

    RemoteViews简介 RemoteViews,根据字面意思应该是一种远程的View,其实RemoteView表示的是一个View结构,它可以在其他进程中显示,由于它在其他进程中显示,为了能够更新 ...

最新文章

  1. 格式化_icecream_python
  2. vue项目打包之后原本好的样式变得不好了的原因分析
  3. 电脑销售技巧_汝州市,如何查询楼盘详细销售数据?官方公布,精确到房间
  4. OpenCASCADE:绘制测试线束之基本命令
  5. AVG杀毒软件添加信任程序
  6. 新手必看一位老司机的十年开车经验
  7. 矩阵论思维导图_《实变函数论》 江泽坚 3rd 思维导图与笔记整理
  8. Linux进阶之路————CentOS网络配置
  9. Python魔法方法(magic method)细解几个常用魔法方法(下)
  10. python的神经网络模块接法图解_图神经网络库PyTorch geometric
  11. 复旦新生计算机考试及格率,复旦大学本科新生《计算机办公自动化》课程入学考试考核大.doc...
  12. access ea 可以联网吗_如何看待EA在STEAM上推出EA Play(原EA Access会员)?
  13. 华为数通NA-NP学习笔记(个人精简)
  14. c++串口配置及DCB结构体
  15. 三.N32G003 系统性能测试--dhrystone (IAR环境)
  16. 2021年中式烹调师(中级)考试题库及中式烹调师(中级)报名考试
  17. 区块链熊市交易量不减,市场为何需要OKEx合约交易?
  18. 2021浙大计算机学硕,浙江大学2021考研分数线已公布
  19. Creo 工程图 圆的中心引线设置
  20. CPU(中央处理器)和GPU(图像处理器)区别大揭秘

热门文章

  1. 10个新鲜出炉的JQuery插件(ITEye)
  2. 视觉注意力的循环神经网络模型(Recurrent Models of Visual Attention)
  3. 【从C到C++学习笔记】内敛函数/内敛函数与带参数宏的区别/新的类型转换运算符
  4. 遥感数字图像处理------第二章---数字图像存储与读取
  5. 链表的相关面试题(完整)(C语言)
  6. 快应用开发教程【02】--项目配置教程
  7. Android Multimedia框架总结(九)Stagefright框架之数据处理及到OMXCodec过程
  8. FFmpeg在Linux下安装编译过程
  9. python标准库——math模块
  10. java paint方法_在Java中,为了使paint()方法执行,以下各项中,()是最佳的调用方法