zoukankan      html  css  js  c++  java
  • 初涉IPC,了解AIDL的工作原理及用法

    初涉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 statements
    
    interface 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 statements
    
    interface 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
         */
        @Override
        public IBinder onBind(Intent intent) {
            //绑定之后就计算
            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;
            }
        };
    }
    
    

    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() {
            //连接上
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
    
                //拿到远程服务
                aidl = ServiceAidlInterface.Stub.asInterface(iBinder);
    
            }
            //断开时
            @Override
            public void onServiceDisconnected(ComponentName componentName) {
    
                //回收
                aidl = null;
            }
        };
    
        @Override
        protected 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);
        }
    
        @Override
        public 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);
        }
    
        /**
         * 销毁服务
         */
        @Override
        protected 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;
        }
    
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel parcel, int i) {
            parcel.writeString(name);
            parcel.writeInt(age);
        }
    
        public static final Creator<Person> CREATOR = new Creator<Person>() {
            @Override
            public Person createFromParcel(Parcel in) {
                return new Person(in);
            }
    
            @Override
            public 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
         */
        @Override
        public 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(){
    
            @Override
            public 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() {
            //连接上
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
    
                //拿到远程服务
                aidl = ServiceAidlInterface.Stub.asInterface(iBinder);
                imy = IMyAidlInterface.Stub.asInterface(iBinder);
    
            }
            //断开时
            @Override
            public void onServiceDisconnected(ComponentName componentName) {
    
                //回收
                aidl = null;
                imy = null;
            }
        };
    
        @Override
        protected 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);
        }
    
        @Override
        public 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);
        }
    
        /**
         * 销毁服务
         */
        @Override
        protected 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

  • 相关阅读:
    Windows下对函数打桩,及Linux类似技术
    WIN10重启后,在任务栏下添加快捷工具栏消失问题修复
    VS2012下std::function的BUG解决办法
    【转载】inno setup 水波纹效果,检测安装vcredist_x86.exe等
    ArchLinux下XFCE的一个问题修复:thunar加载的环境变量不正确
    Daliy Algorithm (GPLT)-- day 94
    Daliy Algorithm (greedy , hash )-- day 93
    Daliy Algorithm (tarjan, greedy, bfs )-- day 92
    Daliy Algorithm (heap,greedy , IQ )-- day 91
    Daliy Algorithm (cf , GPLT )-- day 90
  • 原文地址:https://www.cnblogs.com/clnchanpin/p/7220935.html
Copyright © 2011-2022 走看看