Carpeta de comunicación entre procesos

Descripción general del marco de carpeta

Binder es una arquitectura que proporciona tres módulos: interfaz de servidor, controlador de Binder e interfaz de cliente, como se muestra en la figura.
Insertar descripción de la imagen aquí

Servidor

Veamos primero el servidor. Un servidor Binder es en realidad un objeto de la clase Binder. Una vez que se crea el objeto, se inicia internamente un hilo oculto .

Luego, el hilo recibirá el mensaje enviado por el controlador Binder y , después de recibir el mensaje, ejecutará la función onTransact () en el objeto Binder y ejecutará diferentes funciones de servicio de acuerdo con los parámetros de la función . Por lo tanto, para implementar un servicio Binder, debe sobrecargar el método onTransact().

El contenido principal de la función sobrecargada onTransact()es onTransact()convertir los parámetros de la función en los parámetros de la función de servicio. La fuente de los parámetros de la función es ingresada onTransact()por el cliente al llamar a la función. Por lo tanto, si hay una entrada de formato fijo , entonces habrá una salida en formato fijo.transact()transact()onTransact()

conductor de carpeta

Cuando se crea cualquier objeto Binder del lado del servidor, también se creará un objeto mRemote en el controlador Binder. El tipo de este objeto también es la clase Binder. Cuando el cliente quiere acceder a servicios remotos, siempre utiliza el objeto mRemote.

cliente

Si el cliente desea acceder al servicio remoto deberá obtener la referencia mRemote correspondiente al servicio remoto en el controlador de Binder. Después de obtener el objeto mRemote, puede llamar a su método transact(). En el controlador Binder, el objeto mRemote también sobrecarga el método transact(). El contenido sobrecargado incluye principalmente los siguientes elementos.

  • En el modo de comunicación de mensajes entre subprocesos, los parámetros pasados ​​por el cliente se envían al servidor.

  • Suspenda el hilo actual, que es el hilo del cliente, y espere la notificación después de que el hilo del servidor termine de ejecutar la función de servicio especificada.

  • Después de recibir la notificación del hilo del servidor, continúe ejecutando el hilo del cliente y regrese al área de código del cliente.

Se puede ver desde aquí que para los desarrolladores de aplicaciones, el cliente parece llamar directamente al Binder correspondiente al servicio remoto, pero en realidad se transfiere a través del controlador de Binder. Es decir, hay dos objetos Binder, uno es el objeto Binder en el lado del servidor y el otro es el objeto Binder en el controlador Binder, la diferencia es que el objeto en el controlador Binder no generará un hilo adicional.

¿Cómo obtiene el cliente la referencia mRemote correspondiente en el controlador de Binder?
¿Cómo envía mensajes el controlador de Binder al servidor?

Servidor y cliente de diseño

Servidor de diseño

El servidor es un objeto de clase Binder, simplemente cree una nueva clase de servidor basada en la clase Binder. A continuación se toma como ejemplo el diseño de una clase MusicPlayerService.

Suponiendo que el Servicio solo proporciona dos métodos: start (String filePath) y stop (), entonces el código de esta clase puede ser el siguiente:

public class MusicPlayerService extends Binder {
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
     	switch (code) {
            case 1000:
                data.enforceInterface("MusicPlayerService");
                String filePath = data.readString();
                start(filePath);
                // replay.writeXXX();
                break;
        }
    	return super.onTransact(code, data, reply, flags);
    }
 
    public void start(String filePath) {
 
    }
 
    public void stop() {
        
    }
}

La variable de código se acuerda entre el cliente y el servidor y se utiliza para identificar qué función en el servidor espera llamar el cliente.

Aquí se supone que 1000 es el valor acordado por ambas partes para llamar a la función start().

enforceInterface() es para algún tipo de verificación, que corresponde al writeInterfaceToken() del cliente.

readString() se utiliza para recuperar una cadena del paquete. Después de eliminar la variable filePath, puede llamar a la función start() en el lado del servidor.

Si el cliente espera que el servidor devuelva algunos resultados, puede llamar a las funciones relacionadas proporcionadas por Parcel en la respuesta del paquete de devolución para escribir los resultados correspondientes.

Cuando desee iniciar el servicio, solo necesita inicializar un objeto MusicPlayerService. Por ejemplo, puede inicializar un MusicPlayerService en la actividad principal y luego ejecutarlo. En este momento, puede encontrar que se está ejecutando un subproceso más, Binder-Thread3.

Si no se crea MusicPlayerService, solo hay 2 subprocesos correspondientes a los objetos Binder. Es decir, Binder-Thread1 y Binder-Thread2.

¿De dónde vinieron estos dos procesos?

diseño del cliente

Para utilizar el servidor, primero debe obtener una referencia a la variable mRemote correspondiente al servidor en el controlador de Binder. Una vez que tenga una referencia a la variable, puede llamar al método transact() de la variable. El prototipo de función de este método es el siguiente:

public final boolean transact(int code, Parcel data, Parcel reply,int flags) 

Entre ellos, los datos representan el paquete (Parcel) que se pasará al servicio remoto de Binder, y los parámetros requeridos por la función del servicio remoto deben colocarse en este paquete. Solo se pueden colocar variables de tipos específicos en el paquete, que incluyen tipos atómicos de uso común, como String, int, long, etc. Además de las variables atómicas generales, Parcel también proporciona un método writeParcel() que puede incluir un pequeño paquete en el paquete. Por lo tanto, al llamar al servicio remoto de Binder, el parámetro de la función de servicio debe ser una clase atómica o debe heredar de la clase Parcel ; de lo contrario, no se puede pasar.

Por lo tanto, para el cliente de MusicPlayerService, el método transact() se puede llamar de la siguiente manera.

        IBinder mRemote = null;
        String filePath = "/sdcard/music/heal_the_world.mp3";
        int code = 1000;
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken("MusicPlayerService");
        data.writeString(filePath);
        mRemote.transact(code, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();

En primer lugar, el cliente no crea el paquete, sino que lo solicita llamando a Parcel.obtain().
El cliente proporciona tanto los datos como las variables de respuesta, y el servidor coloca la variable de respuesta en los resultados devueltos.

writeInterfaceToken() marca el nombre del servicio remoto. Teóricamente, este nombre no es necesario porque dado que el cliente ha obtenido la referencia de Binder del servicio remoto especificado, no llamará a otros servicios remotos. El controlador de Binder utilizará este nombre para garantizar que el cliente realmente quiera llamar al servidor especificado.

writeString() se utiliza para agregar una variable de cadena al paquete. onTransactTenga en cuenta que el contenido agregado en el paquete está en orden. Este orden debe ser acordado por el cliente y el servidor con anticipación. Las variables se eliminarán en el método () del servidor en el orden acordado .

Luego llame al método transact(). Después de llamar a este método,

Cuando el hilo del cliente ingresa al controlador de Binder, el controlador de Binder suspenderá el hilo actual y enviará un mensaje al servicio remoto, que contiene el paquete pasado por el cliente. Una vez que el servidor recibe el paquete, lo desensamblará y luego ejecutará la función de servicio especificada. Una vez completada la ejecución, el resultado de la ejecución se colocará en el paquete de respuesta proporcionado por el cliente. Luego, el servidor envía un mensaje de notificación al controlador de Binder, lo que hace que el hilo del cliente regrese del área de código del controlador de Binder al área de código del cliente.

El significado del último parámetro de transact() es el modo de ejecutar llamadas IPC, que se divide en dos tipos: uno es bidireccional, representado por la constante 0, lo que significa que el servidor devolverá ciertos datos después de ejecutar el servicio especificado; el otro Es unidireccional, representado por la constante 1, lo que significa que no se devuelven datos.

Finalmente, el cliente puede analizar los datos devueltos de la respuesta. Del mismo modo, los datos contenidos en el paquete de devolución también deben estar en orden, y este orden debe ser acordado por adelantado entre el servidor y el cliente.

Carpeta y servicio

Hay dos problemas importantes en el proceso anterior de escribir manualmente el servidor y el cliente de Binder.
Primero, ¿cómo obtiene el cliente la referencia del objeto Binder del servidor?

En segundo lugar, el cliente y el servidor deben acordar dos cosas de antemano:

  • El orden de los parámetros de la función del servidor en el paquete.

  • Identificadores de tipo int de diferentes funciones en el lado del servidor. Ese es el valor del parámetro de código en el método de transacción.

Servicio

Entonces, ¿cómo resuelve la clase Servicio las dos cuestiones importantes planteadas al principio de esta sección?

En primer lugar, AmS proporciona la función startService () para iniciar el servicio al cliente. Para el cliente, puede usar las dos funciones siguientes para establecer una conexión con un servicio, y su prototipo está en la clase android.app.ContextImpl.

    @Override
    public ComponentName startService(Intent service) {
        try {
            ComponentName cn = ActivityManagerNative.getDefault().startService(
                mMainThread.getApplicationThread(), service,
                service.resolveTypeIfNeeded(getContentResolver()));
            if (cn != null && cn.getPackageName().equals("!")) {
                throw new SecurityException(
                        "Not allowed to start service " + service
                        + " without permission " + cn.getClassName());
            }
            return cn;
        } catch (RemoteException e) {
            return null;
        }
    }

Esta función se utiliza para iniciar intentel servicio especificado. Después del inicio, el cliente aún no tiene una referencia de Binder del servidor, por lo que todavía no puede llamar a ninguna función de servicio.

    @Override
    public boolean bindService(Intent service, ServiceConnection conn,
            int flags) {
        IServiceConnection sd;
        if (mPackageInfo != null) {
            sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
                    mMainThread.getHandler(), flags);
        } else {
            throw new RuntimeException("Not supported in system context");
        }
        try {
            int res = ActivityManagerNative.getDefault().bindService(
                mMainThread.getApplicationThread(), getActivityToken(),
                service, service.resolveTypeIfNeeded(getContentResolver()),
                sd, flags);
            if (res < 0) {
                throw new SecurityException(
                        "Not allowed to bind to service " + service);
            }
            return res != 0;
        } catch (RemoteException e) {
            return false;
        }
    }

Esta función se utiliza para vincular un servicio, que es la clave de la primera pregunta importante. El segundo parámetro es una clase de interfaz y la definición de la interfaz se muestra en el siguiente código:

/**
 * Interface for monitoring the state of an application service.  See
 * {@link android.app.Service} and
 * {@link Context#bindService Context.bindService()} for more information.
 * <p>Like many callbacks from the system, the methods on this class are called
 * from the main thread of your process.
 */
public interface ServiceConnection {
    /**
     * Called when a connection to the Service has been established, with
     * the {@link android.os.IBinder} of the communication channel to the
     * Service.
     *
     * @param name The concrete component name of the service that has
     * been connected.
     *
     * @param service The IBinder of the Service's communication channel,
     * which you can now make calls on.
     */
    public void onServiceConnected(ComponentName name, IBinder service);
    public void onServiceDisconnected(ComponentName name);
}

Tenga en cuenta la segunda variable Servicio en el método onServiceConnected() en esta interfaz. Cuando el cliente solicita a AmS que inicie un Servicio, si el Servicio se inicia normalmente, AmS llamará de forma remota al objeto ApplicationThread en la clase ActivityThread. Los parámetros de la llamada contendrán la referencia de Binder del Servicio, y luego ApplicationThread devolverá la llamada al interfaz bindService.conn. Por lo tanto, en el cliente, su parámetro Servicio se puede guardar como una variable global en el método onServiceConnected (), de modo que se pueda llamar al servicio remoto en cualquier momento y en cualquier lugar del cliente. Esto resuelve el primer problema importante, que es cómo el cliente obtiene la referencia de Binder del servicio remoto.

Insertar descripción de la imagen aquí

AIDL garantiza el orden de los parámetros dentro del paquete.

Con respecto a la segunda pregunta, el SDK de Android proporciona una herramienta Aidl que puede convertir un archivo Aidl en un archivo de clase Java. En el archivo de clase Java, los métodos transact y onTransact () se sobrecargan al mismo tiempo, unificando el almacenamiento. y leer los parámetros del paquete permite al diseñador centrarse en el código de servicio en sí.

A continuación, veamos qué hace la herramienta Aidl. Como se muestra en el ejemplo de la primera sección de este capítulo, aquí todavía se supone que se va a escribir un servicio MusicPlayerService, que contiene dos funciones de servicio, a saber, start() y stop(). Luego, primero puede escribir un archivo IMusicPlayerService.aidl. Como se muestra en el siguiente código:

    package com.haiii.android.client;  
    interface IMusicPlayerService{  
        boolean start(String filePath);  
        void stop();  
    }  

El nombre del archivo debe seguir ciertas especificaciones. La primera letra "I" no es necesaria. Sin embargo, para unificar el estilo del programa, el significado de "I" es la clase IInterface, es decir, esta es una clase que puede proporcionar acceso a servicios remotos. El nombre posterior: MusicPlayerService corresponde al nombre de clase del servicio, que puede ser arbitrario, pero la herramienta Aidl nombrará la clase Java de salida con este nombre.

La sintaxis del archivo Aidl es básicamente similar a la de Java: el paquete especifica el nombre del paquete correspondiente al archivo Java de salida . Si el archivo necesita hacer referencia a otras clases de Java, puede usar la palabra clave import, pero debe tenerse en cuenta que solo se pueden escribir los siguientes tres tipos de contenido en el paquete:

  • Tipos atómicos de Java, como int, long, String y otras variables.
  • Referencia de carpeta.
  • Objeto que implementa Parcelable.

Por lo tanto, básicamente, las clases Java a las que se hace referencia mediante la importación solo pueden ser de los tres tipos anteriores.

La interfaz es una palabra clave, a veces se agrega un unidireccional delante de la interfaz, lo que significa que los métodos proporcionados por el servicio no tienen valor de retorno, es decir, todos son de tipo vacío.

Echemos un vistazo al código del archivo IMusicPlayerService.java generado por Aidl. Como sigue:

package com.haiii.client;
 
public interface IMusicPlayerService extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder
            implements com.haiii.client.IMusicPlayerService {
        private static final java.lang.String DESCRIPTOR =
                "com.haiii.client.IMusicPlayerService";
 
        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
 
        /**
         * Cast an IBinder object into an com.haiii.client.IMusicPlayerService interface,
         * generating a proxy if needed.
         */
        public static com.haiii.client.IMusicPlayerService
        asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
 
            android.os.IInterface iin =
                    (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR);
 
            if (((iin != null) && (iin instanceof com.haiii.client.IMusicPlayerService))) {
                return ((com.haiii.client.IMusicPlayerService) iin);
            }
            return new com.haiii.client.IMusicPlayerService.Stub.Proxy(obj);
        }
 
        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_start: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    boolean _result = this.start(_arg0);
                    reply.writeNoException();
                    reply.writeInt(((_result) ? (1) : (0)));
                    return true;
                }
                case TRANSACTION_stop: {
                    data.enforceInterface(DESCRIPTOR);
                    this.stop();
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
 
        private static class Proxy implements com.haiii.client.IMusicPlayerService {
            private android.os.IBinder mRemote;
 
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
 
            public android.os.IBinder asBinder() {
                return mRemote;
            }
 
            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }
 
            public boolean start(java.lang.String filePath) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                boolean _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(filePath);
                    mRemote.transact(Stub.TRANSACTION_start, _data, _reply, 0);
                    _reply.readException();
                    _result = (0 != _reply.readInt());
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
 
            public void stop() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_stop, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
 
        static final int TRANSACTION_start = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
 
    public boolean start(java.lang.String filePath) throws android.os.RemoteException;
 
    public void stop() throws android.os.RemoteException;
}  

Estos códigos realizan principalmente las siguientes tres tareas.

Servicio IMusicPlayer

Defina uno Java interface, que contiene la función de servicio declarada en el archivo Aidl. El nombre de la clase es IMusicPlayerServicey la clase se basa en IInterfacela interfaz, es decir, asBinderse debe proporcionar una función ().

Apoderado

Defina una clase Proxy, que servirá como proxy para que los programas cliente accedan al servidor. El llamado proxy sirve principalmente para la segunda cuestión importante mencionada anteriormente: unificar el orden de escritura de los parámetros en el paquete.

Talón

Defina una clase Stub, que es una clase abstracta basada en la clase Binder e implementa IMusicPlayerServicela interfaz, utilizada principalmente por el servidor. La razón por la que esta clase se define como una clase abstracta es porque las funciones de servicio específicas deben ser implementadas por programadores, por lo que IMusicPlayerServicelas funciones definidas en la interfaz no necesitan implementarse específicamente en la clase Stub. Al mismo tiempo, el método () está sobrecargado en la clase Stub onTransact. Dado que transactel orden de escritura de los parámetros en el paquete dentro del método () está definido por la herramienta Aidl, en el onTransactmétodo (), la herramienta Aidl naturalmente sabe en qué orden. debería estar adentro. Obtenga los parámetros correspondientes del paquete.

Algunas constantes int también se definen en la clase Stub, como TRANSACTION_start, estas constantes corresponden a las funciones de servicio, de aquí provienen los valores del primer código de parámetro de los métodos transact() y onTransact().

En la clase Stub, además de las tareas mencionadas anteriormente, Stub también proporciona una función asInterface(). Lo que hace esta función se proporciona de la siguiente manera:

La razón para proporcionar esta función es que, además de otros procesos, los servicios proporcionados por el servidor también pueden ser utilizados por otras clases dentro del proceso de servicio. Para este último, obviamente no es necesario llamarlo a través de IPC, pero puede ser se llama directamente dentro del proceso. , y hay una función queryLocalInterface (descripción de cadena) dentro de Binder, que determina si el objeto Binder es una referencia local de Binder según la cadena de entrada.

Insertar descripción de la imagen aquí
Al crear un servicio, se crea un objeto Binder dentro del proceso del servidor y también se crea un objeto Binder en el controlador de Binder. Si el Binder del proceso del servidor se obtiene del proceso del cliente, solo se devolverá el objeto Binder en el controlador de Binder. Si el objeto Binder se obtiene dentro del proceso del servidor, se obtendrá el objeto Binder del propio servidor.

Por lo tanto, la función asInterface() utiliza el método queryLocalInterface() para proporcionar una interfaz unificada. Ya sea un cliente remoto o un proceso interno del servidor, después de obtener el objeto Binder, puede usar el objeto Binder obtenido como parámetro de asInterface () para devolver una interfaz IMusicPlayerService, que usa la clase Proxy o usa directamente la uno implementado por Stub. Función de servicio correspondiente.

Objeto de carpeta en el servicio del sistema

En las aplicaciones, getSystemService(String serviceName)a menudo se utilizan métodos para obtener un servicio del sistema. Entonces, ¿cómo se pasan las referencias de Binder de estos servicios del sistema al cliente?

Cabe señalar que los servicios del sistema no se startService()inician con . getSystemService()La función se implementa en la clase ContextImpl. Esta función devuelve muchos servicios. Consulte el código fuente para obtener más detalles. Estos Servicios generalmente son administrados por ServiceManager.

Servicios gestionados por ServiceManger

ServiceManager es un proceso independiente. Su función es como se muestra en el nombre. Administra varios servicios del sistema. La lógica de administración es la siguiente:
Insertar descripción de la imagen aquí
ServiceManager en sí también es un servicio. El marco proporciona una función del sistema para obtener la referencia de Binder correspondiente al servicio. , eso esBinderInternal.getContextObject()。

Después de que la función estática devuelva ServiceManager, puede obtener la referencia de Binder de otros servicios del sistema a través de los métodos proporcionados por ServiceManager.

Cuando se inician otros servicios del sistema, primero pasan sus objetos Binder a ServiceManager, que es el llamado registro (addService).

Consulte a continuación para obtener un Servicio{IMPUT_METHOD_SERVICE}:

if (INPUT_METHOD_SERVICE.equals(name)) {
            return InputMethodManager.getInstance(this);
static public InputMethodManager getInstance(Looper mainLooper) {
        synchronized (mInstanceSync) {
            if (mInstance != null) {
                return mInstance;
            }
            IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
            IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
            mInstance = new InputMethodManager(service, mainLooper);
        }
        return mInstance;
    }

Es decir, al ServiceManagerobtener InputMethod Serviceel objeto Binder b correspondiente y luego usar el objeto Binder como IInputMethodManager.Stub.asInterface()parámetro, IInputMethodManagerse devuelve una interfaz unificada.

ServiceManager.getService()El código es el siguiente:

public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

Es decir, primero sCacheverifique si hay un objeto Binder correspondiente en el caché. Si lo hay, se devolverá. Si no, se llamará getIServiceManager().getService(name). La función getIServiceManager()se utiliza para devolver el único ServiceManagerBinder correspondiente en el sistema. El código es como sigue:

private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }
        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }

BinderInternal.getContextObject()La función estática se utiliza para devolver el objeto Binder global correspondiente a ServiceManager, esta función no requiere ningún parámetro porque su función es fija. El proceso de todos los demás servicios del sistema obtenidos a través de ServiceManager es básicamente similar al anterior, la única diferencia es que el nombre del servicio pasado a ServiceManager es diferente, porque ServiceManager guarda diferentes objetos Binder según el nombre del servicio (tipo String).

Agregar un servicio a ServiceManager usando addService() generalmente se completa cuando se inicia el proceso SystemService.

Entendiendo al Pesebre

Todos los servicios gestionados por ServiceManager se devuelven al cliente con el Manager correspondiente, por lo que aquí hay una breve descripción de la semántica de Manager en el Framework.

En Android, el significado de Manager debe traducirse como intermediario. El objeto administrado por Manager es el servicio en sí, porque cada servicio específico generalmente proporciona múltiples interfaces API, y son estas API las que administra Manager.

El cliente generalmente no puede acceder a servicios específicos directamente a través de la referencia de Binder. Primero debe obtener la referencia de Binder del servicio remoto a través de ServiceManager y luego usar esta referencia de Binder para construir un intermediario al que el cliente pueda acceder localmente, como el IInputMethodManager anterior. y luego el cliente puede acceder a servicios remotos a través de este corredor.

El diagrama modelo para acceder a servicios remotos a través del Administrador local es el siguiente:
Insertar descripción de la imagen aquí

  1. El nuevo diseño de la interfaz brindará una nueva experiencia de escritura;
  2. Configure su estilo de resaltado de código favorito en el centro de creación y Markdown mostrará el fragmento de código en el estilo de resaltado seleccionado ;
  3. Se agregó la función de arrastrar y soltar imágenes , puede arrastrar imágenes locales directamente al área de edición para mostrarlas directamente;
  4. Nueva sintaxis de fórmula matemática KaTeX ;
  5. Se agregó la función de sintaxis de sirena 1 que admite diagramas de Gantt ;
  6. Se agregó la función de editar artículos de Markdown en múltiples pantallas;
  7. Funciones agregadas como modo de escritura de enfoque, modo de vista previa, modo de escritura concisa, configuración de rueda sincronizada del área izquierda y derecha, etc. El botón de función está ubicado entre el área de edición y el área de vista previa;
  8. Se agregó la funcionalidad de lista de verificación .

Teclas de acceso directo a funciones

Deshacer: Ctrl/Command+ Z
Rehacer: Ctrl/Command+ Y
Negrita: Ctrl/Command+ Cursiva B
: Ctrl/Command+ I
Título: Ctrl/Command+ Shift+ H
Lista desordenada: Ctrl/Command+ Shift+ U
Lista ordenada: Ctrl/Command+ Shift+ O
Lista de verificación: + +Insertar código Ctrl/Command: Shift+ C
+ Ctrl/CommandInsertar Shiftenlace K
: Ctrl/Command+ Shift+ L
Insertar imagen: Ctrl/Command+ Shift+ G
Buscar: Ctrl/Command+ F
Reemplazar: Ctrl/Command+G

Cree títulos correctamente para ayudar a crear una tabla de contenido.

Ingréselo directamente una vez #y presiónelo, spacey se generará un título de nivel 1.
Luego de ingresarlo dos veces #y presionarlo space, se generará un título de nivel 2.
Por analogía, admitimos títulos de nivel 6. TOCAyuda a generar una tabla de contenidos perfecta utilizando la sintaxis.

Cómo cambiar el estilo del texto

Énfasis en el texto Énfasis en el texto

texto en negrita texto en negrita

marcar texto

Eliminar texto

texto citado

El H2O es un líquido.

El resultado de la operación 2 10 es 1024.

Insertar enlaces e imágenes

Enlace: enlace .

imagen:Alt.

Imágenes con dimensiones:Alt.

Imagen centrada:Alt.

Imagen centrada y dimensionada:Alt.

Por supuesto, para hacerlo más conveniente para los usuarios, hemos agregado la función de arrastrar y soltar imágenes.

Cómo insertar un hermoso fragmento de código

Vaya a la página de configuración del blog y elija un estilo de resaltado de fragmento de código que le guste. El mismo estilo de resaltado se muestra a continuación 代码片.

// An highlighted block
var foo = 'bar';

Genera una lista que funcione para ti

  • proyecto
    • proyecto
      • proyecto
  1. Proyecto 1
  2. Proyecto 2
  3. Proyecto 3
  • Tareas programadas
  • misión cumplida

Crear un formulario

Una tabla simple se crea así:

proyecto Valor
computadora $1600
Teléfono móvil $12
catéter $1

Establecer contenido en el centro, izquierda o derecha

Utilice :---------:el centro
. Utilice :----------la izquierda.
Utilice ----------:la derecha.

primera fila la segunda columna tercera columna
La primera columna de texto está centrada. La segunda columna de texto está a la derecha. La tercera columna de texto está a la izquierda.

Pantalones inteligentes

SmartyPants convierte caracteres de puntuación ASCII en entidades HTML de puntuación tipográfica "inteligentes". Por ejemplo:

TIPO ASCII HTML
comillas simples 'Isn't this fun?' '¿No es divertido?'
Citas "Isn't this fun?" "¿No es divertido?"
guiones -- is en-dash, --- is em-dash – es en el tablero, — es en el tablero

Crear una lista personalizada

Reducción
Herramienta de conversión de texto a HTML
Autores
John
lucas

Cómo crear un pie de página

Un texto con notas a pie de página. 2

Los comentarios también son esenciales.

Markdown convierte texto a HTML .

Fórmula matemática KaTeX

Puedes renderizar expresiones matemáticas de LaTeX usando KaTeX :

Infraestructura gamma (n) = (n − 1)! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb NC ( n )=( n.1 )!norteN es la integral vía Euler

Γ ( z ) = ∫ 0 ∞ tz − 1 mi − tdt . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,.C ( z )=0tz - 1 mit dt.

Puede encontrar más información sobre las expresiones matemáticas de LaTeX aquí .

Nueva función de diagrama de Gantt para enriquecer tus artículos

2014-01-07 2014-01-09 2014-01-11 2014-01-13 2014-01-15 2014-01-17 2014-01-19 2014-01-21 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
  • Con respecto a la sintaxis del diagrama de Gantt , consulte aquí .

diagrama UML

Se pueden utilizar diagramas UML para renderizar. Sirena ... Por ejemplo, el diagrama de secuencia se genera a continuación:

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

Esto producirá un diagrama de flujo. :

链接
长方形
圆角长方形
菱形
  • Con respecto a la sintaxis de Mermaid , consulte aquí ,

diagrama de flujo

Seguiremos admitiendo diagramas de flujo de diagramas de flujo:

Created with Raphaël 2.3.0 开始 我的操作 确认? 结束 yes no
  • Con respecto a la sintaxis del diagrama de flujo , consulte aquí .

Exportar e importar

Exportar

Si quieres probar a usar este editor, puedes editar lo que quieras en este artículo. Cuando termine de escribir un artículo, busque la exportación del artículo en la barra de herramientas superior y genere un archivo .md o .html para guardarlo localmente.

importar

Si desea cargar un archivo .md que haya escrito, puede seleccionar la función de importación en la barra de herramientas superior para importar el archivo con la extensión correspondiente y
continuar con su creación.


  1. descripción de sintaxis de sirena↩︎

  2. Explicación de la nota al pie↩︎

Supongo que te gusta

Origin blog.csdn.net/jxq1994/article/details/132609102
Recomendado
Clasificación