Android跨进程通信Binder机制与AIDL实例_android的binder通信 aidl接口-程序员宅基地

技术标签: Binder  终端安全  AIDL  

在操作系统中,进程与进程间的内存和数据都是不共享的。这样做的目的,是为了避免进程间相互操作数据的现象发生,从而引起各自的安全问题。为了实现进程隔离,采用了虚拟地址空间,两个进程各自的虚拟地址不同,从逻辑上来实现彼此间的隔离。两个进程之间要进行通信,就需要采用特殊的通信机制:进程间通信(IPC:Inter-Process Communication,即进程间通信或跨进程通信,简称 IPC)。

进程通信

1.1 进程空间划分

一个进程空间分为 用户空间 & 内核空间(Kernel),即把进程内 用户 & 内核 隔离开来。二者的区别:

  1. 进程间用户空间的数据不可共享,所以用户空间 = 不可共享空间
  2. 进程间内核空间的数据可共享,所以内核空间 = 可共享空间,所有进程共用1个内核空间

进程内 用户空间 & 内核空间 进行交互 需通过 系统调用,主要通过函数:

  1. copy_from_user():将用户空间的数据拷贝到内核空间
  2. copy_to_user():将内核空间的数据拷贝到用户空间

在这里插入图片描述

1.2 跨进程通信IPC

  • 进程隔离:为了保证 安全性 & 独立性,一个进程 不能直接操作或者访问另一个进程,即 Android 的进程是相互独立、隔离的
  • 跨进程通信(IPC:Inter-Process Communication):即进程间需进行数据交互、通信。

跨进程通信的基本原理:
在这里插入图片描述

1.3 Linux跨进程通信

我们知道,Android系统就是基于Linux内核实现的,咱们先简单了解一下Linux系统的IPC方式。虽然不同的资料对各种方式的名称和种类说法不完全相同,但是主要来说有如下6种方式:(1)管道 Pipe;(2)信号Signal;(3)信号量Semaphore;(4)消息队列Message Queue;(5)共享内存Shared Memmory;(6)套接字Socket。读者若想深入了解这些方式,可以自行查阅,这里不展开介绍,只需要知道有这六种方式即可。

1.4 Android进程通信

Android IPC的方式在不同的资料中有不同的说法,但从大方向上看,可以归纳为如下四种(这里仅对各种方式做简单介绍和优劣对比,对具体如何使用,不做讲解):

1、Activity方式

Activity是四大组件中使用最频繁的,咱们先从它说起。使用Activity方式实现,就是使用startActivity()来启动另外一个进程的Activity。我们在使用App的使用,往往会遇到如下几种情形:(1)浏览器中看到一篇比较不错的文章,分享到微信朋友圈或者微博;(2)在某个App中点击某个网址,然后界面跳转到浏览器中进行阅读;(3)使用美团外卖app,看到店家的电话,点击联系商家时,跳转到了电话拨打界面…这样的操作再频繁不过了。这些就是通过startActivity的方式从一个App,跳转到了另外一个App的Activity,从而实现了跨进程通信。

我们知道,在调用startActivity(Intent intent)的时候,intent有两个类型:显式Intent和隐式Intent。显式Intent的使用方式如下,用于进程内组件间通信:

Intent intent = new Intent(this,OtherActivity.class);
startActivity(intent);

隐式intent的使用方式如下,用于IPC:

Intent intent = new Intent();
intent.setAction(Intent.ACTION_CALL);
startActivity(intent); //startActivityForResult()同样,这里不赘述

Intent.ACTION_CALL就是字符串常量“android.intent.action.CALL”,这种方式通过setAction的方式来启动目标app的Activity,上述代码就是启动电话app的拨号界面,有时候还可以带上电话号码等参数。由上可知,Activity实现跨进程通信的方式,适合于不同App之间功能界面的跳转。

2、Content provider(后面简称CP)方式

当我们开发App需要用到联系人,多媒体信息等数据的时候,往往会通过系统提供Uri,采用CP的方式去获取。Android系统中,数据主要存储在自带的SqlLite数据库中。应用要共享SqlLite中的数据给其他App操作(增、删、改、查),就要用到CP,也就是说,CP主要用于跨进程数据库共享。Android系统提供了很多的CP来供其它App使用,如多媒体信息、联系人、日历等。如下图显示了Android系统提供的CP,包名都是以"com.android.providers“开头的:
在这里插入图片描述
这些用于共享的数据其实都是存储在系统数据库中的,如下显示了meida CP中的数据库:
在这里插入图片描述

App开发者也可以自定义CP,把自己的数据提供给其它app使用,也可以自己定义操作权限,如只允许其它app读取自己的数据,而不允许修改等。CP的使用场景,是提供数据共享。CP本质上还是在操作数据库,数据存储在sdcard中,所以建立连接和操作数据都是耗时操作,所以注意开辟子线程去操作。当数据库中数据有变化时,Content Observer监听到数据库变化也是有一定的滞后。

3、Broadcase方式

Broadcast使用非常简单,注册好广播,添加上action,就可以等着接收其他进程发出的广播。发送和接收广播时,还可以借助Intent来携带数据。但是广播的使用存在很多问题,被很多程序员吐槽,甚至鄙夷,所以选择用广播进行跨进程通信,是下下策。下面盘点一下Broadcast的槽点:

  1. Broadcast是一种单向的通信方式。当一个程序发送广播后,其他应用只能被动地接收,无法向发送者反馈。
  2. Broadcast非常消耗系统资源,会导致系统性能下降。
  3. 速度慢,容易造成系统ANR。且除了Parall Broadcast外,无法保证接收到的时间,甚至不一定能收得到。
  4. 如果使用Ordered Broadcast,一个Receiver执行时间过长,会影响后面接收者的接收时间,甚至还有可能被中间某个Receiver拦截,导致后面Receiver无法接收到。
  5. 发送者无法确定谁会接收该广播,而接收者也无发确认是谁发来的广播。
  6. 如果是静态注册的广播,一个没有开启的进程,都有可能被该广播激活。

总而言之,言而总之,使用Broadcast来实现跨进程通信,是下下之策!

4、Service方式

启动Service的方式有多种,有的用于跨进程通信,有的用于进程内部模块之间的通信,下面仅简单介绍一下跨进程通信的方式。

(1)startService()方式

Intent startIntent = new Intent ();
ComponentName componentName = new ComponentName(string packageName,string serviceClassName);
startIntent.setComponent(componentName );
startService( startIntent);

该方式启动远程Service实现跨进程通信,耦合度比较低,功能及代码结构清晰,但是存在以下缺点:

  • 没有好的机制立即返回执行结果,往往Service完成任务后,还需要其他方式向Client端反馈。

  • Service端无法识别Client端是谁,只知道有启动命令,但无法知道是谁下的命令。

  • 在已经创建好Service情况下,每次调用startService,就会执行onStartCommand()生命周期方法,相比于bindService,效率低下。

  • 如果Client端忘记调用stopService()了,那么该Service会一直运行下去,这也是一个隐患。

    所以,针对上述缺点,往往建议startService()方式用于同一个App内,跨进程的方式用后面将要讲到的AIDL来实现。

(2)bindService、Handler、Messager结合

这种方式也可以实现跨进程通信,据说和AIDL具有相同的功效,但比AIDL使用简单,详细可以阅读博文【Android总结篇系列:Android Service】。

(3)AIDL

这种方式也是 bindService() 启动方式的一种使用情况,也是广受程序员们推崇的方式。前面说 startService() 和 Broadcast 如何不好,就是为了衬托 AIDL 如何的好!这是本文的主角,本文后面会专门详细讲解,这里不赘述。

【总结】从如上的介绍来看,其实 Android 中跨进程通信的实现,就是利用四大组件来实现的。对方式的选择,我们总结一下:

  • 如果跨进程需要界面上的交互操作,用隐式startActivity()方式实现。
  • 如果需要共享数据,用Content Provider方式实现。
  • 排除前两种情形,就用AIDL。
  • 仅仅为了完成功能,又确实不会用AIDL的,就用Broadcast吧!!!虽然很low,但比实现不了功能还是强多了。

Binder跨进程通信

在这里插入图片描述

传统的跨进程通信需拷贝数据2次,但 Binder 机制只需1次,主要是使用到了内存映射,具体下面会详细说明。

跨进程通信的核心原理:内存映射,具体请看文章:操作系统:图文详解 内存映射

对比 Linux (Android基于Linux)上的其他进程通信方式(管道、消息队列、共享内存、信号量、Socket),Binder 机制的优点有:

在这里插入图片描述

2.1 Binder简介

Binder 跨进程通信机制 模型 基于 Client - Server 模式:
在这里插入图片描述
在这里插入图片描述

此处重点讲解 Binder 驱动的作用和 原理:

在这里插入图片描述

2.2 Binder驱动

在这里插入图片描述

2.3 Binder原理

在这里插入图片描述

Client进程、Server进程 & Service Manager 进程之间的交互 都必须通过Binder驱动(使用 open 和 ioctl文件操作函数),而非直接交互。具体原因:

  • Client进程、Server进程 & Service Manager进程属于进程空间的用户空间,不可进行进程间交互;
  • Binder驱动 属于 进程空间的 内核空间,可进行进程间 & 进程内交互。

所以,原理图可表示为以下(虚线表示并非直接交互—):

在这里插入图片描述

Binder驱动 & Service Manager进程 属于 Android基础架构(即系统已经实现好了);而Client 进程 和 Server 进程 属于Android应用层(需要开发者自己实现)。所以,在进行跨进程通信时,开发者只需自定义Client & Server 进程并显式使用上述3个步骤,最终借助 Android的基本架构功能就可完成进程间通信:
在这里插入图片描述

AIDL编程Demo

前面我们讲到,为了克服 Linux 中 IPC 各种方式的缺点,在Android中引入了Binder机制。但是当说起Binder在Android中的使用时,几乎所有的资料都是在说 AIDL 的使用。AIDL 的全称是 Android Interface Definition Language,即Android接口定义语言,是 Binder 机制实现 Android IPC 时使用比较广泛的工具。

本节将以一个 Demo 演示一下 AIDL 的基本实现步骤。源码地址:https://pan.baidu.com/s/1CyE_8-T9TDQLVQ1TDAEX2A 提取码:4auk 。如下两个图展示了该 Demo 的结构图和 AIDL 关键文件:

在这里插入图片描述

建立两个App,分别为Client端和Server端。

​ 这个比较 好理解,Server端就是包含了Service真正干活的那一端;Client端就是通过远程操控指挥的那一端,分别在不同的App中。如下图所示:

在这里插入图片描述

3.1 服务端

1、在Server端main目录下建立aidl文件夹以及.aidl文件,并copy一份到Client端,如图6.1中②处所示结构。注意,Client端和Server端②处是一模一样的。另外,AS中提供了快捷方式创建aidl文件,在main处点击右键 > New > AIDL > AIDL File文件,按照提示给aidl文件命名即可自动创建完成,可以看到文件路径也是该项目的包名。

在这里插入图片描述

这里给aidl命名为IDemoService.aidl,这里需要注意的是命名规范,一般都是以“I”开头,表示是一个接口,其内容如下:

//========== IDemoService.aidl========
package com.songwei.aidldemoserver;
// Declare any non-default types here with import statements
interface IDemoService {
    
    void setName(String name);
    String getName();
}

2、Server端创建Service文件 AidlService.java,如图6.1中③处所示,代码如下:

package com.songwei.aidldemoserver;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class AidlService extends Service {
    
    private final static String TAG = "aidlDemo";

    public AidlService() {
    
    }

    @Override
    public void onCreate() {
    
        super.onCreate();
        Log.i(TAG, "server:[onCreate]");
    }

    @Override
    public IBinder onBind(Intent intent) {
    
        // TODO: Return the communication channel to the service.
        Log.i(TAG, "server:[onBind]");
        return new MyBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
    
        Log.i(TAG, "server:[onUnbind]");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
    
        super.onDestroy();
        Log.i(TAG, "server:[onDestroy]");
    }

    class MyBinder extends IDemoService.Stub {
    
        private String mName = "";

        public void setName(String name) throws RemoteException{
    
            Log.i(TAG, "server:[setName]");
            mName = name;
        }

        @Override
        public String getName() throws RemoteException {
    
            Log.i(TAG, "server:[getName]");
            return mName;
        }
    }
}

为了下文分析流程及生命周期,在其中各个方法中都添加了Log。同时,在Server端的AndroidManifest.xml文件中添加该Service的注册信息(注意exported属性值,如果有“intent-filter”,则默认值为true,否则为false。所以这里其实可以去掉,因为有“intent-filter”,其默认值就是true):

<service
    android:name=".AidlService"
    android:exported="true">
    <intent-filter>
         <action android:name="com.songwei.aidl" />
    </intent-filter>
</service>	

3、编译Sever端和Client端App,生成IDemoService.java文件。

​ 当编译的时候,AS会自动为我们生成IDemoService.java文件,如图6.1和图6.2中④处所示。当你打开该文件的时候,是不是看到了如下场景?
在这里插入图片描述

惊不惊喜?意不意外?是不是一脸懵逼,大惊,卧槽,这尼玛啥玩意啊?

AIDL 是 Android 接口定义语言,IDemoService.java 是一个java中的interface(接口),现在是不是若有所思了呢?AIDL 正是定义了 IDemoService.java 这个接口!!! 这个接口文件就是 AIDL 帮助咱们生成的 Binder 相关代码,这些代码就是用来帮助实现 Client 端和 Server 端通信的。前面第2步中提到的IDemoService.aidl文件,其作用就是作为原料通过AIDL来生成这些你貌似看不懂的代码的,第3步中的 AidlService.java 和后续在 Client 端App连接Server端App的时候,其实这个aidl文件就从来没有出现过,也就是说,它已经没有什么价值了。所以说,AIDL 的作用就是用来自动生成 Binder 相关接口代码的,而不需要开发者手动编写。有些教程中说,可以不使用AIDL而手动编写这份Binder代码,AIDL不是Binder实现通信所必需的,笔者也没有尝试过手动编写,如果读者您想挑战,可以尝试一下!

咱们继续!打开IDemoService.java文件后,点击主菜单兰Code > Reformat Code (或 Ctrl + Alt +L快捷健),你会发现画面变成了下面这个样子:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\ASWorkspace\\testDemo\\aidldemoclient\\src\\main\\aidl\\com\\songwei\\aidldemoserver\\IDemoService.aidl
 */
package com.songwei.aidldemoserver;

public interface IDemoService extends android.os.IInterface {
    
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.songwei.aidldemoserver.IDemoService {
    
        private static final java.lang.String DESCRIPTOR = "com.songwei.aidldemoserver.IDemoService";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
    
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.songwei.aidldemoserver.IDemoService interface,
         * generating a proxy if needed.
         */
        public static com.songwei.aidldemoserver.IDemoService asInterface(android.os.IBinder obj) {
    
            if ((obj == null)) {
    
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.songwei.aidldemoserver.IDemoService))) {
    
                return ((com.songwei.aidldemoserver.IDemoService) iin);
            }
            return new com.songwei.aidldemoserver.IDemoService.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_setName: {
    
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    this.setName(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getName: {
    
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _result = this.getName();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.songwei.aidldemoserver.IDemoService {
    
            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 void setName(java.lang.String name) throws android.os.RemoteException {
    
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
    
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(name);
                    mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
                    _reply.readException();
                } finally {
    
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public java.lang.String getName() throws android.os.RemoteException {
    
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
    
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
    
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public void setName(java.lang.String name) throws android.os.RemoteException;

    public java.lang.String getName() throws android.os.RemoteException;

}

惊不惊喜?意不意外?这下是不是有种似曾相识的赶脚?这就是一个很普通的java中的接口文件而已,结构也非常简单。

在这里插入图片描述

神秘的面纱揭开一层了吧!后面在讲完Client端和Server端的连接及通信后,还会继续深入剖析这个文件。

3.2 客户端

Client端通过ClientActivity活动类来连接Server端AidlService并通信。ClientActivity.java 的内容如下,布局文件在此省略,比较简单,就两个按钮,一个用于绑定,一个用于解绑,看Button命名也很容易分辨:

package com.songwei.aidldemoclient;

import android.content.ComponentName;
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.util.Log;
import android.view.View;
import android.widget.Button;

import com.songwei.aidldemoserver.IDemoService;

public class ClientActivity extends AppCompatActivity {
    

    private final static String TAG = "aidlDemo";
    private Button mBindBtn, mUnBindBtn;
    private IDemoService mDemoService;
    private boolean mIsBinded = false;
    private ServiceConnection mConn = new ServiceConnection() {
    
        //当与远程Service绑定后,会回调该方法。
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder binder) {
    
            Log.i(TAG, "client:[onServiceConnected]componentName=" + componentName);
            mIsBinded = true;
            //得到一个远程Service中的Binder代理,而不是该Binder实例
            mDemoService = IDemoService.Stub.asInterface(binder);
            try {
    
                //远程控制设置name值
                mDemoService.setName("Andy Song");
                //远程获取设置的name值
                String myName = mDemoService.getName();
                Log.i(TAG, "client:[onServiceConnected]myName=" + myName);
            } catch (RemoteException e) {
    
                e.printStackTrace();
            }
        }

        //该回调方法一般不会调用,如果在解绑的时候,发现该方法没有调用,不要惊慌,因为该方法的调用时机是Service被意外销毁时,比如内存不足时。
        @Override
        public void onServiceDisconnected(ComponentName name) {
    
            Log.i(TAG, "client:[onServiceDisconnected]");
            mIsBinded = false;
            mDemoService = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBindBtn = (Button) findViewById(R.id.btn_bind);
        mBindBtn.setOnClickListener(new View.OnClickListener() {
    
            @Override
            public void onClick(View v) {
    
                //Android5.0及以后,出于对安全的考虑,Android系统对隐式启动Service做了限制,需要带上包名或者类名,这一点需要注意。
                Intent intent = new Intent();
                intent.setAction("com.songwei.aidl");
                intent.setPackage("com.songwei.aidldemoserver");
                bindService(intent, mConn, BIND_AUTO_CREATE);
            }
        });
        mUnBindBtn = (Button) findViewById(R.id.btn_unbind);
        mUnBindBtn.setOnClickListener(new View.OnClickListener() {
    
            @Override
            public void onClick(View v) {
    
                //解除绑定,当调用unbindService时,一定要判断当前service是否是binded的,如果没有,就会报错。
                if (mIsBinded) {
    
                    unbindService(mConn);
                    mDemoService = null;
                    mIsBinded = false;
                }
            }
        });
    }
}

代码中对一些关键和容易忽略的地方做了注释,可以结合起来进行理解。

End 运行

​ 运行的时候,需要先启动Service端进程,才能在Client端中点击“绑定”的时候绑定成功。完成一次“绑定”和“解绑”,得到的log如下所示:

01-08 15:29:43.109 13532-13532/com.songwei.aidldemoserver I/aidlDemo: server:[onCreate]
01-08 15:29:43.110 13532-13532/com.songwei.aidldemoserver I/aidlDemo: server:[onBind]
01-08 15:29:43.113 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]componentName=ComponentInfo{
    com.songwei.aidldemoserver/com.songwei.aidldemoserver.AidlService}
01-08 15:29:43.114 13532-13547/com.songwei.aidldemoserver I/aidlDemo: server:[setName]
01-08 15:29:43.114 13532-13546/com.songwei.aidldemoserver I/aidlDemo: server:[getName]
01-08 15:29:43.114 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]myName=Andy Song
01-08 15:36:07.570 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceDisconnected]

可以结合前面的 ClientActivity.java 和 AidlService.java 代码中的添加的log,来理解一下这个流程。当然,最好是能够按照上面的步骤,亲自动手实现一遍,比看10遍更有效果。

参考文章:

  1. Android性能篇之(七)Android跨进程通信篇
  2. Android跨进程通信:图文详解 Binder机制 原理
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39190897/article/details/121708963

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法