プロセス間通信 - バインダー

バインダーフレームワークの概要

Binder は、図に示すように、サーバー インターフェイス、Binder ドライバー、クライアント インターフェイスの 3 つのモジュールを提供するアーキテクチャです。
ここに画像の説明を挿入します

サーバ

まずサーバーを見てみましょう。Binder サーバーは実際には Binder クラスのオブジェクトであり、オブジェクトが作成されると、内部で隠しスレッドが開始されます

スレッドは、Binder ドライバーによって送信されたメッセージを受信し、メッセージを受信した後、Binder オブジェクトの onTransact() 関数を実行し、関数のパラメーターに従ってさまざまなサービス関数を実行しますしたがって、Binder サービスを実装するには、onTransact() メソッドをオーバーロードする必要があります。

オーバーロードされた関数の主な内容は、関数のパラメータをサービス関数のパラメータに変換することonTransact()です。関数のパラメータのソースは、関数呼び出し時にクライアントによって入力されます。そのため、固定形式の入力がある場合、の場合、固定形式の出力が得られます。onTransact()onTransact()transact()transact()onTransact()

バインダードライバー

サーバー側の Binder オブジェクトが作成されると、Binder ドライバーにも mRemote オブジェクトが作成されます。このオブジェクトの型も Binder クラスです。クライアントがリモート サービスにアクセスする場合は、常に mRemote オブジェクトを使用します。

クライアント

クライアントがリモート サービスにアクセスしたい場合は、Binder ドライバーでリモート サービスに対応する mRemote 参照を取得する必要があります。mRemote オブジェクトを取得した後、その transact() メソッドを呼び出すことができます。Binder ドライバーでは、mRemote オブジェクトは transact() メソッドもオーバーロードします。オーバーロードされる内容には主に次の項目が含まれます。

  • スレッド間メッセージ通信モードでは、クライアントから渡されたパラメータがサーバーに送信されます。

  • 現在のスレッド (クライアント スレッド) を一時停止し、サーバー スレッドが指定されたサービス関数の実行を終了した後の通知を待ちます。

  • サーバー スレッドから通知を受信した後、クライアント スレッドの実行を継続し、クライアント コード領域に戻ります。

ここから、アプリケーション開発者にとって、クライアントはリモートサービスに対応する Binder を直接呼び出しているように見えますが、実際には Binder ドライバーを介して転送されることがわかります。つまり、サーバー側の Binder オブジェクトと、Binder ドライバー内の Binder オブジェクトの 2 つの Binder オブジェクトが存在しますが、Binder ドライバー内のオブジェクトは追加のスレッドを生成しない点が異なります。

クライアントは、Binder ドライバー内の対応する mRemote 参照をどのように取得しますか?
Binder ドライバーはどのようにサーバーにメッセージを送信しますか?

サーバーとクライアントの設計

デザインサーバー

サーバーは Binder クラスのオブジェクトなので、Binder クラスをベースにして新しい Server クラスを作成するだけです。以下では、例として MusicPlayerService クラスの設計を取り上げます。

サービスが start(String filePath) と stop() の 2 つのメソッドのみを提供すると仮定すると、このクラスのコードは次のようになります。

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() {
        
    }
}

コード変数はクライアントとサーバーの間で合意され、クライアントがサーバー上のどの関数を呼び出すかを識別するために使用されます。

ここでは、start() 関数を呼び出すために双方が合意した値が 1000 であると仮定します。

enforceInterface() は、クライアントの writeInterfaceToken() に対応する、ある種の検証用です。

readString() は、パッケージから文字列を取得するために使用されます。filePath 変数を取り出した後、サーバー側で start() 関数を呼び出すことができます。

クライアントがサーバーが何らかの結果を返すことを期待している場合は、返されるパッケージ応答で Parcel によって提供される関連関数を呼び出して、対応する結果を書き込むことができます。

サービスを開始する場合は、MusicPlayerService オブジェクトを初期化するだけで済みます。たとえば、メイン アクティビティで MusicPlayerService を初期化して実行すると、もう 1 つのスレッド Binder-Thread3 が実行されていることがわかります。

MusicPlayerService が作成されていない場合、Binder オブジェクトに対応するスレッドは 2 つだけです。つまり、Binder-Thread1 と Binder-Thread2 です。

これら 2 つのプロセスはどこから来たのでしょうか?

クライアントのデザイン

サーバーを使用するには、まず、Binder ドライバーでサーバーに対応する mRemote 変数への参照を取得する必要があります。変数への参照を取得したら、変数の transact() メソッドを呼び出すことができます。このメソッドの関数プロトタイプは次のとおりです。

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

このうちデータはリモートのBinderサービスに渡すパッケージ(Parcel)を表しており、リモートサービス機能に必要なパラメータはこのパッケージに入れる必要があります。特定の型の変数のみをパッケージに配置できます。これらの型には、String、int、long などの一般的に使用されるアトミック型が含まれます。一般的なアトミック変数に加えて、Parcel は、パーセルに小さなパーセルを含めることができる writeParcel() メソッドも提供します。したがって、Binder リモート サービスを呼び出すときは、サービス関数のパラメータはアトミック クラスであるか、Parcel クラスから継承する必要があります。そうでない場合、パラメータを渡すことはできません。

したがって、MusicPlayerService のクライアントの場合、transact() メソッドを次のように呼び出すことができます。

        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();

まず、パッケージはクライアント自身によって作成されるのではなく、Parcel.obtain() を呼び出すことによって適用されます。
データ変数と応答変数は両方ともクライアントによって提供され、応答変数はサーバーによって返された結果に組み込まれます。

writeInterfaceToken() メソッドはリモート サービスの名前を示します。クライアントは指定されたリモート サービスの Binder 参照を取得しているため、他のリモート サービスを呼び出さないため、理論的にはこの名前は必要ありません。この名前は、クライアントが実際に指定されたサーバーを呼び出す必要があることを確認するために、バインダー ドライバーによって使用されます。

writeString() メソッドは、文字列変数をパッケージに追加するために使用されます。なお、パッケージに追加する内容は順番になっており、この順番はクライアントとサーバーで事前に合意しておく必要があり、変数はサーバーのonTransact()メソッドで合意された順序で取り出されます。

次に、transact() メソッドを呼び出します。このメソッドを呼び出した後、

クライアント スレッドがバインダー ドライバーに入ると、バインダー ドライバーは現在のスレッドを一時停止し、クライアントから渡されたパッケージを含むメッセージをリモート サービスに送信します。サーバーはパッケージを取得すると、パッケージを逆アセンブルして、指定されたサービス関数を実行し、実行完了後にクライアントが提供する応答パッケージに実行結果を格納します。次に、サーバーは通知メッセージをバインダー ドライバーに送信し、クライアント スレッドをバインダー ドライバーのコード領域からクライアント コード領域に戻します。

transact() の最後のパラメータの意味は、IPC 呼び出しの実行モードであり、2 つのタイプに分けられます。1 つは双方向で、定数 0 で表され、指定されたサービスの実行後にサーバーが特定のデータを返すことを意味します。もう一方は一方向であり、定数 1 で表され、データが返されないことを意味します。

最後に、クライアントは応答から返されたデータを解析できます。同様に、返信パッケージに含まれるデータも順序どおりである必要があり、この順序についてはサーバーとクライアントが事前に合意する必要があります。

バインダーとサービス

Binder サーバーとクライアントを手動で作成する上記のプロセスには、2 つの重要な問題があります。
まず、クライアントがサーバーの Binder オブジェクト参照をどのように取得するかです。

次に、クライアントとサーバーは、次の 2 つのことに事前に同意する必要があります。

  • パッケージ内のサーバー関数のパラメーターの順序。

  • サーバー側のさまざまな関数の int 型識別子。これは、transact メソッドの code パラメーターの値です。

サービス

では、Service クラスはこのセクションの冒頭で挙げた 2 つの重要な問題をどのように解決するのでしょうか?

まず、AmS には顧客サービスを開始するための startService() 関数が用意されており、クライアント側では次の 2 つの関数を使用してサービスとの接続を確立でき、そのプロトタイプは 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;
        }
    }

intentこの関数は、指定されたサービスを開始するために使用されます。起動後、クライアントはまだサーバーからの Binder 参照を持っていないため、まだサービス関数を呼び出すことができません。

    @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;
        }
    }

この関数はサービスをバインドするために使用されます。これが最初の重要な質問の鍵となります。2 番目のパラメーターはインターフェイス クラスであり、インターフェイスの定義は次のコードに示すとおりです。

/**
 * 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);
}

このインターフェースの onServiceConnected() メソッドの 2 番目の変数 Service に注意してください。クライアントが AmS にサービスの開始を要求すると、サービスが正常に開始すると、AmS はリモートで ActivityThread クラスの ApplicationThread オブジェクトを呼び出します。呼び出しのパラメータにはサービスの Binder 参照が含まれ、ApplicationThread はサービスをコールバックします。 bindService.conn インターフェイス。したがって、クライアントでは、パラメータ Service を onServiceConnected() メソッドのグローバル変数として保存できるため、クライアントでいつでもどこからでもリモート サービスを呼び出すことができます。これにより、クライアントがリモート サービスのバインダー参照をどのように取得するかという最初の重要な問題が解決されます。

ここに画像の説明を挿入します

AIDL はパッケージ内のパラメータの順序を保証します

2番目の質問については、Android SDKにはaidlファイルをJavaクラスファイルに変換できるaidlツールが用意されており、JavaクラスファイルではtransactメソッドとonTransact()メソッドが同時にオーバーロードされ、ストレージが統一されています。また、パッケージパラメータを読み取ることで、設計者はサービスコード自体に集中できるようになります。

次に、aidl ツールが何を行うかを見てみましょう。この章の最初のセクションの例で示したように、ここでも MusicPlayerService サービスを記述することを前提としています。このサービスには、start() と stop() という 2 つのサービス関数が含まれています。次に、まず IMusicPlayerService.aidl ファイルを作成します。次のコードに示すように:

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

ファイル名は一定の仕様に従う必要があり、最初の文字「I」は必須ではありませんが、プログラム形式の統一のため、「I」の意味はIInterfaceクラス、つまり、リモート サービスへのアクセスを提供できます。後続の命名 – MusicPlayerService はサービスのクラス名に対応します。これは任意ですが、aidl ツールは出力 Java クラスにこの名前を付けます。

Aidl ファイルの構文は基本的に Java と似ており、package には出力 Java ファイルに対応するパッケージ名を指定しますファイルが他の Java クラスを参照する必要がある場合は、import キーワードを使用できますが、パッケージに記述できるのは次の 3 種類のコンテンツのみであることに注意してください。

  • int、long、String、その他の変数などの Java アトミック タイプ。
  • バインダーのリファレンス。
  • Parcelable を実装するオブジェクト。

したがって、importで参照できるJavaクラスは基本的に上記の3種類のみとなります。

Interface がキーワードですが、interface の前に oneway が付加される場合がありますが、これはサービスが提供するメソッドが戻り値を持たない、つまりすべて void 型であることを意味します。

Aidl によって生成された IMusicPlayerService.java ファイルのコードを見てみましょう。次のように:

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;
}  

これらのコードは主に次の 3 つのタスクを実行します。

IMusicPlayerService

aidl ファイルで宣言されたサービス関数を含む1 つを定義しますJava interface。クラス名は でIMusicPlayerService、クラスはIInterfaceインターフェイスに基づいています。つまり、asBinder() 関数が提供される必要があります。

プロキシ

Proxy クラスを定義します。これは、クライアント プログラムがサーバーにアクセスするためのプロキシとして機能します。いわゆるプロキシは主に、前述の 2 番目の重要な問題、つまりパッケージ内でのパラメータの書き込み順序の統一のためです。

スタブ

スタブ クラスを定義します。これは、Binder クラスに基づく抽象クラスであり、IMusicPlayerService主にサーバーによって使用されるインターフェイスを実装します。このクラスが抽象クラスとして定義されているのは、具体的なサービス機能はプログラマが実装する必要があるためであり、IMusicPlayerServiceインターフェースで定義された機能をわざわざスタブクラスに実装する必要はない。同時に、() メソッドはスタブ クラスでオーバーロードされますonTransacttransact() メソッド内のパッケージにパラメーターを書き込む順序は、aidl ツールによって定義されているため、onTransact() メソッドでは、aidl ツールは自然に順序を知っています。パッケージから対応するパラメータを取得します。

TRANSACTION_start などの int 定数もスタブ クラスで定義されており、これらの定数はサービス関数に対応しており、transact() メソッドと onTransact() メソッドの最初のパラメータ コードの値はこれに由来します。

Stub クラスでは、上記のタスクに加えて、Stub は asInterface() 関数も提供します。この関数の機能は次のように提供されます。

この機能を提供する理由は、サーバーによって提供されるサービスは、他のプロセスに加えて、サービス プロセス内の他のクラスからも使用できるためです。後者については、当然 IPC 経由で呼び出す必要はありませんが、IPC 経由で呼び出すことができます。プロセス内で直接呼び出され、Binder 内には、入力文字列に基づいて Binder オブジェクトがローカル Binder 参照であるかどうかを判断する queryLocalInterface (文字列の説明) 関数があります。

ここに画像の説明を挿入します
サービスを作成すると、サーバー プロセス内に Binder オブジェクトが作成され、Binder ドライバー内にも Binder オブジェクトが作成されます。クライアントプロセスからサーバープロセスのBinderを取得した場合は、Binderドライバー内のBinderオブジェクトのみが返されますが、サーバープロセス内からBinderオブジェクトを取得した場合は、サーバー自体のBinderオブジェクトが取得されます。

したがって、asInterface() 関数は、queryLocalInterface() メソッドを使用して統合インターフェイスを提供します。リモート クライアントであっても、サーバーの内部プロセスであっても、Binder オブジェクトを取得した後、取得した Binder オブジェクトを asInterface() のパラメータとして使用して、Proxy クラスを使用するか、Proxy クラスを直接使用する IMusicPlayerService インターフェイスを返すことができます。スタブによって実装されるもの、対応するサービス関数。

システムサービスのバインダーオブジェクト

アプリケーションでは、getSystemService(String serviceName)システム サービスを取得するためにメソッドがよく使用されますが、これらのシステム サービスの Binder 参照はどのようにクライアントに渡されるのでしょうか?

システムサービスは によって開始されないことに注意してくださいstartService()getSystemService()この関数は ContextImpl クラスに実装されています。この関数は多くのサービスを返します。詳細についてはソース コードを参照してください。これらのサービスは通常、ServiceManager によって管理されます。

ServiceManager によって管理されるサービス

ServiceManager は独立したプロセスです その機能は名前の通りです さまざまなシステムサービスを管理します 管理ロジックは次のとおりです
ここに画像の説明を挿入します
ServiceManager 自体も Service です Framework は Service に対応する Binder 参照を取得するシステム関数を提供します、 あれはBinderInternal.getContextObject()。

静的関数が ServiceManager を返した後、ServiceManager が提供するメソッドを通じて他のシステム サービスのバインダー参照を取得できます。

他のシステム サービスが開始されると、まず Binder オブジェクトが ServiceManager に渡されます。これがいわゆる登録 (addService) です。

サービス{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;
    }

つまり、対応する Binder オブジェクト bServiceManagerを取得し、それをパラメータとして使用することで、統一されたインターフェイスが返されます。InputMethod ServiceIInputMethodManager.Stub.asInterface()IInputMethodManager

ServiceManager.getService()コードは次のとおりです。

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;
    }

つまり、まず、sCacheキャッシュに対応する Binder オブジェクトがあるかどうかを確認します。存在する場合は、それが返されます。存在しない場合は、 が呼び出されます。この関数は、システム内で唯一対応するgetIServiceManager().getService(name)BinderオブジェクトgetIServiceManager()を返すために使用されます。コードは次のとおりです。ServiceManager次のように:

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

BinderInternal.getContextObject()ServiceManager に対応するグローバル Binder オブジェクトを返すには static 関数を使用しますが、役割が固定されているためパラメータは必要ありません。ServiceManager を通じて取得される他のすべてのシステム サービスのプロセスは基本的に上記と同様ですが、唯一の違いは、ServiceManager がサービス名 (String 型) に応じて異なる Binder オブジェクトを保存するため、ServiceManager に渡されるサービス名が異なることです。

addService() を使用した ServiceManager へのサービスの追加は、通常、SystemService プロセスの開始時に完了します。

飼い葉桶を理解する

ServiceManager によって管理されるすべてのサービスは、対応する Manager とともにクライアントに返されるため、フレームワークにおける Manager のセマンティクスについて簡単に説明します。

Android では、Manager の意味はブローカーとして翻訳される必要があります。通常、特定のサービスごとに複数の API インターフェイスが提供され、Manager が管理するのはこれらの API であるため、Manager によって管理されるオブジェクトはサービス自体です。

クライアントは通常、Binder 参照を通じて特定のサービスに直接アクセスできません。まず ServiceManager を通じてリモート サービスの Binder 参照を取得し、次にこの Binder 参照を使用して、クライアントがローカルにアクセスできるブローカー (前の IInputMethodManager など) を構築する必要があります。このブローカーを介してリモート サービスにアクセスできます。

ローカル Manger を介してリモート サービスにアクセスするためのモデル図は次のとおりです。
ここに画像の説明を挿入します

  1. 新しいインターフェイスのデザインは、新しい書き込み体験をもたらします。
  2. 作成センターでお気に入りのコードの強調表示スタイルを設定すると、Markdown は選択した強調表示スタイルでコード部分を表示します。
  3. 画像のドラッグ アンド ドロップ機能が追加されました。ローカル画像を編集領域に直接ドラッグして直接表示できます。
  4. 新しいKaTeX 数式構文。
  5. ガント チャートをサポートする Mermaid syntax 1関数を追加しました。
  6. Markdown記事を複数画面で編集する機能を追加しました。
  7. フォーカス書き込みモード、プレビューモード、簡潔書き込みモード、左右エリア同期ホイール設定などの機能を追加しました。ファンクションボタンは編集エリアとプレビューエリアの間にあります。
  8. チェックリスト機能が追加されました。

機能ショートカットキー

元に戻す: Ctrl/Command+Z
やり直し: Ctrl/Command+Y
太字: Ctrl/Command+B
斜体: Ctrl/Command+I
タイトル: Ctrl/Command+ Shift+順序H
なしリスト: Ctrl/Command+ Shift+U
順序付きリスト: Ctrl/Command+ Shift+O
チェックリスト: + +コードの挿入Ctrl/Command: Shift+ C
+リンクCtrl/Commandの挿入: + +画像の挿入: + +検索: +置換: +ShiftK
Ctrl/CommandShiftL
Ctrl/CommandShiftG
Ctrl/CommandF
Ctrl/CommandG

目次を作成しやすいようにタイトルを適切に作成する

一度直接入力し#て押すとspaceレベル1の称号が生成されます。
2回入力し#て を押すとspaceレベル2の称号が生成されます。
同様に、レベル 6 タイトルをサポートします。構文を使用してTOC完璧な目次を生成するのに役立ちます。

テキストのスタイルを変更する方法

文字の強調 文字の強調

太字 太字

テキストにマークを付ける

テキストの削除

引用されたテキスト

H 2 O は液体です。

2 10演算の結果は 1024 です。

リンクと画像を挿入する

リンク:リンク

写真:代替

寸法付きの写真:代替

中央に配置された画像:代替

画像の中央揃えとサイズ調整:代替

もちろん、ユーザーの利便性を高めるために、画像のドラッグ&ドロップ機能を追加しました。

美しいコードを挿入する方法

ブログ設定ページに移動し、好みのコード フラグメントの強調表示スタイルを選択します。同じ強調表示スタイルを以下に示します代码片

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

自分に合ったリストを作成する

  • プロジェクト
    • プロジェクト
      • プロジェクト
  1. プロジェクト1
  2. プロジェクト2
  3. プロジェクト3
  • スケジュールされたタスク
  • 任務完了

フォームを作成する

単純なテーブルは次のように作成されます。

プロジェクト 価値
コンピューター 1600ドル
携帯電話 12ドル
カテーテル 1ドル

コンテンツを中央、左、または右に設定します

:---------:中央を使用左を
使用を使用:----------
----------:

最初の行 2列目 3列目
テキストの最初の列が中央に配置されます テキストの 2 列目は右側にあります テキストの 3 列目は左側にあります

スマーティパンツ

SmartyPants は、ASCII 句読点文字を「スマート」な活字句読点 HTML エンティティに変換します。例えば:

タイプ アスキー HTML
単一のバッククォート 'Isn't this fun?' 「これは面白くないですか?」
引用 "Isn't this fun?" 「これは面白くないですか?」
ダッシュ -- is en-dash, --- is em-dash – は en-ダッシュ、- は em-ダッシュ

カスタムリストを作成する

マークダウン
テキストから HTML への変換ツール
著者
ジョン
ルーク

脚注の作成方法

脚注付きのテキスト。2

コメントも必須です

Markdown はテキストをHTMLに変換します。

KaTeX の数式

KaTeXを使用して LaTeX 数式をレンダリングできます

ガンマ インフラストラクチャ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb NC ( n )=( n1 )!∀n _Nはオイラーによる積分です

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

LaTeX の数式の詳細については、ここを参照してください。

記事を充実させる新ガントチャート機能

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
  • ガントチャートの構文については、こちらを参照してください。

UML図

UML 図はレンダリングに使用できます。Mermaid . たとえば、以下のように生成されたシーケンス図:

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

これによりフローチャートが生成されます。:

链接
长方形
圆角长方形
菱形
  • Mermaid の構文については、こちらを参照してください。

フローチャート フローチャート

引き続きフローチャート フローチャートをサポートします。

Created with Raphaël 2.3.0 开始 我的操作 确认? 结束 yes no
  • Flowchart フローチャートの構文についてはここを参照してください。

エクスポートとインポート

輸出

このエディターを使用してみたい場合は、この記事で必要なものを編集できます。記事の作成が完了したら、上部のツールバーで記事のエクスポートを見つけて、ローカルに保存するための .md ファイルまたは .html ファイルを生成します。

輸入

自分で作成した .md ファイルをロードする場合は、上部ツールバーのインポート機能を選択して、対応する拡張子を持つファイルをインポートし、
作成を続行できます。


  1. 人魚の構文 説明↩︎

  2. 脚注の説明↩︎

おすすめ

転載: blog.csdn.net/jxq1994/article/details/132609102