Android4.4 RIL簡訊接收流程分析

来源:https://www.cnblogs.com/hackfun/archive/2019/11/01/11775588.html
-Advertisement-
Play Games

最近有客戶反饋Android接收不到簡訊,於是一頭扎進RIL裡面找原因。最後發現不是RIL的問題,而是BC72上報簡訊的格式不對,AT+CNMA=1無作用等幾個小問題導致的。儘管問題不在RIL,但總算把RIL簡訊接收流程搞清楚了。 接收到新信息的log: D/ATC ( 1269): AT< +CM ...


 

        近有客戶反饋Android接收不到簡訊,於是一頭扎進RIL裡面找原因。最後發現不是RIL的問題,而是BC72上報
簡訊的格式不對,AT+CNMA=1無作用等幾個小問題導致的。儘管問題不在RIL,但總算把RIL簡訊接收流程搞清楚了。

接收到新信息的log:

D/ATC ( 1269): AT< +CMT:,27
D/ATC ( 1268): AT< 0891683108705505F0040d91683117358313f500009101329154922307ea31da2c36a301
D/RILJ ( 1792): [UNSL]< UNSOL_RESPONSE_NEW_SMS
D/SmsMessage( 1792): SMS SC address: +8613800755500
V/SmsMessage( 1792): SMS originating address: +8613715338315
V/SmsMessage( 1792): SMS TP-PID:0 data coding scheme: 0
D/SmsMessage( 1792): SMS SC timestamp: 1571831129000
V/SmsMessage( 1792): SMS message body (raw): 'jchfbfh'
D/GsmInboundSmsHandler( 1776): Idle state processing message type 1
D/GsmInboundSmsHandler( 1776): acquired wakelock, leaving Idle state
D/GsmInboundSmsHandler( 1776): entering Delivering state
D/GsmInboundSmsHandler( 1776): URI of new row -> content://raw/3
D/RILJ ( 1775): [3706]> SMS_ACKNOWLEDGE true 0
D/RILC ( 1254): onRequest: SMS_ACKNOWLEDGE
D/ATC ( 1254): AT> AT+CNMA=1
D/ATC ( 1254): AT< OK
D/RILJ ( 1775): [3706]< SMS_ACKNOWLEDGE
D/GsmInboundSmsHandler( 1775): Delivering SMS to: com.android.mms com.android.mms.transaction.PrivilegedSmsReceiver
E/GsmInboundSmsHandler( 1775): unexpected BroadcastReceiver action: android.provider.Telephony.SMS_RECEIVED
D/GsmInboundSmsHandler( 1775): successful broadcast, deleting from raw table.
D/SmsMessage( 2124): SMS SC address: +8613800755500
D/GsmInboundSmsHandler( 1775): Deleted 1 rows from raw table.
D/GsmInboundSmsHandler( 1775): ordered broadcast completed in: 276 ms
D/GsmInboundSmsHandler( 1775): leaving Delivering state
D/GsmInboundSmsHandler( 1775): entering Delivering state
D/GsmInboundSmsHandler( 1775): leaving Delivering state
D/GsmInboundSmsHandler( 1775): entering Idle state
V/SmsMessage( 2124): SMS originating address: +8613715338315
V/SmsMessage( 2124): SMS TP-PID:0 data coding scheme: 0
D/SmsMessage( 2124): SMS SC timestamp: 1572253549000
V/SmsMessage( 2124): SMS message body (raw): 'jchfbfh'
D/GsmInboundSmsHandler( 1775): Idle state processing message type 5
D/GsmInboundSmsHandler( 1775): mWakeLock released

 

一、簡訊接收

1. vendor ril接收到modem上報的簡訊息

hardware/ril/reference-ril/reference-ril.c

static void onUnsolicited (const char *s, const char *sms_pdu)
{
    ... ...
    if (strStartsWith(s, "+CMT:")) {
        RIL_onUnsolicitedResponse (
            RIL_UNSOL_RESPONSE_NEW_SMS,                             /* 上報UNSOL_RESPONSE_NEW_SMS消息 */
            sms_pdu, strlen(sms_pdu)); 
    }
    ... ...
}

 

2. RILD把簡訊息發送到RILJ

hardware/ril/libril/ril.cpp

extern "C"
void RIL_onUnsolicitedResponse(int unsolResponse, void *data,
                                size_t datalen)
{
    ... ...
    unsolResponseIndex = unsolResponse - RIL_UNSOL_RESPONSE_BASE;    /* 找出消息在s_unsolResponses[]的索引 */
    ... ...
    
    switch (s_unsolResponses[unsolResponseIndex].wakeType) {         /* 禁止進入休眠 */
        case WAKE_PARTIAL:
            grabPartialWakeLock();
            shouldScheduleTimeout = true;
        break;
        ... ...
    }
    ... ...
    ret = s_unsolResponses[unsolResponseIndex]                       /* 調用消息處理函數responseString() */
                .responseFunction(p, data, datalen);
    ... ...
    
    ret = sendResponse(p);                                           /* 發送Parcel中的信息內容到服務端RILJ */
}

static UnsolResponseInfo s_unsolResponses[] = { 
... ...
/* 消息對應的消息處理函數,新信息到來會喚醒系統 */
{RIL_UNSOL_RESPONSE_NEW_SMS, responseString, WAKE_PARTIAL},
... ...
};

static int responseString(Parcel &p, void *response, size_t responselen) {
    /* one string only */
    startResponse;
    appendPrintBuf("%s%s", printBuf, (char*)response);               
    closeResponse;

    writeStringToParcel(p, (const char *)response);                  /* 把字元串格式的信息存到Parcel容器中 */

    return 0;
}

 

二、解析簡訊息

1. RILJ獲取簡訊息

frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java

private void 
processUnsolicited (Parcel p) { 
    ... ...
    case RIL_UNSOL_RESPONSE_NEW_SMS: ret =  responseString(p); break;
    ... ...
    
    switch(response) {
        ... ...
        case RIL_UNSOL_RESPONSE_NEW_SMS: {
            if (RILJ_LOGD) unsljLog(response);                        /* 參考log:[UNSL]< UNSOL_RESPONSE_NEW_SMS */
            // FIXME this should move up a layer
            String a[] = new String[2];

            a[1] = (String)ret;

            SmsMessage sms;

            sms = SmsMessage.newFromCMT(a);                           /* 解析PDU格式的簡訊息 */
            if (mGsmSmsRegistrant != null) {
                mGsmSmsRegistrant
                    .notifyRegistrant(new AsyncResult(null, sms, null));
            }
            break;
        }
        ... ...
    }
    ... ...
}


private Object
responseString(Parcel p) { 
    String response;

    response = p.readString();                                                         /* 信息內容轉換成Object */

    return response;
}

 

2. 解析簡訊息

        SmsMessage.newFromCMT(a);根據import android.telephony.SmsMessage,得知代碼路徑:

frameworks/opt/telephony/src/java/android/telephony/SmsMessage.java

public static SmsMessage newFromCMT(String[] lines) {
    // received SMS in 3GPP format
    SmsMessageBase wrappedMessage =
            com.android.internal.telephony.gsm.SmsMessage.newFromCMT(lines);       /* 是對另一個newFromCMT的封裝,因為有gsm和cdma兩種簡訊,
                                                                                    * 即cdma中也有newFromCMT,根據情況按需選擇 
                                                                                    */

    return new SmsMessage(wrappedMessage);
}

 

        com.android.internal.telephony.gsm.SmsMessage.newFromCMT(lines)的實現在

frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/SmsMessage.java

public class SmsMessage extends SmsMessageBase {
    
    ... ...
    
    public static SmsMessage newFromCMT(String[] lines) {
        try {
            SmsMessage msg = new SmsMessage();
            msg.parsePdu(IccUtils.hexStringToBytes(lines[1]));               /* 解析PDU簡訊 */
            return msg;
        } catch (RuntimeException ex) {
            Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
            return null;
        }
    }

    ... ...
}

 

        IccUtils.hexStringToBytes(lines[1])把十六進位的字元串轉換成位元組數組msg.parsePdu()解析這個數組的內容,最後獲得簡訊內容
frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/SmsMessage.java

private void parsePdu(byte[] pdu) { 
    ... ...
    mScAddress = p.getSCAddress(); 
     
    if (mScAddress != null) {      
        if (VDBG) Rlog.d(LOG_TAG, "SMS SC address: " + mScAddress);          /* 參考log:SMS SC address: +8613800755500 */
    }
    
    ... ...
    mMti = firstByte & 0x3;
    switch (mMti) {
        ... ...
          case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
                  //This should be processed in the same way as MTI == 0 (Deliver)
              parseSmsDeliver(p, firstByte);                                   /* 對簡訊類型為Deliver的簡訊進行解析 */
              break;
           ... ...
      }
    ... ...
}

private void parseSmsDeliver(PduParser p, int firstByte) {
    ... ...
    mOriginatingAddress = p.getAddress();                                                                                                                
    
    if (mOriginatingAddress != null) {
        if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "                /* 參考log: SMS originating address: +861371533xxxx */                                                                                  
                + mOriginatingAddress.address);                                                                                                          
    }
    
    ... ...
    
    mProtocolIdentifier = p.getByte();

    // TP-Data-Coding-Scheme
    // see TS 23.038
    mDataCodingScheme = p.getByte();

    if (VDBG) {
        Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
                + " data coding scheme: " + mDataCodingScheme);               /* 參考log: SMS TP-PID:0 data coding scheme: 0 */
    }

    mScTimeMillis = p.getSCTimestampMillis();

    if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);          /* 參考log:SMS SC timestamp: 1571831129000 */

    boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;

    parseUserData(p, hasUserDataHeader);                                      /* 解析信息有效內容 */
    
    ... ... 
}     
    
private void parseUserData(PduParser p, boolean hasUserDataHeader) {
    ... ...
    if (VDBG) Rlog.v(LOG_TAG, "SMS message body (raw): '" + mMessageBody + "'");    /* 簡訊內容,參考log: SMS message body (raw): 'jchfbfh' */
    ... ...
} 

 

三、處理簡訊息     

        對用戶有效的簡訊內容,最終保存在類型為String的mMessageBody變數中,該變數屬於SmsMessageBase抽象類,而
SmsMessage繼承於SmsMessageBase。
        回到前面frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java中processUnsolicited(),
sms = SmsMessage.newFromCMT(a);解析完簡訊息後,返回一個SmsMessage並通知上層應用。

frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java

mGsmSmsRegistrant
    .notifyRegistrant(new AsyncResult(null, sms, null));                             /* 把sms轉成Object類型 */

 

frameworks/base/core/java/android/os/AsyncResult.java

public class AsyncResult
{
    ... ...
    /** please note, this sets m.obj to be this */
    public
    AsyncResult (Object uo, Object r, Throwable ex)
    {
        userObj = uo;
        result = r;
        exception = ex;
    }
    ... ...
}

 

        根據mGsmSmsRegistrant.notifyRegistrant(new AsyncResult(null, sms, null));找到mGsmSmsRegistrant註冊的代碼:

frameworks/opt/telephony/src/java/com/android/internal/telephony/BaseCommands.java  

public abstract class BaseCommands implements CommandsInterface {
    ... ...
    
    @Override                                                                                                                                                
    public void setOnNewGsmSms(Handler h, int what, Object obj) {     /* mGsmSmsRegistrant.notifyRegistrant(new AsyncResult(null, sms, null))中的mGsmSmsRegistrant是在這裡創建的 */                                                                                       
        mGsmSmsRegistrant = new Registrant (h, what, obj);                                                                                                   
    }
    
    ... ...
}

 

        封裝消息EVENT_NEW_SMS消息

frameworks/base/core/java/android/os/Registrant.java

public class Registrant                                                                                                                                      
{ 
    public
    Registrant(Handler h, int what, Object obj)                       /* 傳入需要處理消息為what的事件處理Handler h,obj為事件內容,參考phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null); */                                                                                                      
    {   
        refH = new WeakReference(h);                                                                                                                         
        this.what = what;                                                                                                                                    
        userObj = obj;                                                                                                                                       
    } 
    
    ... ...
    
    /**
     * This makes a copy of @param ar
     */
    public void
    notifyRegistrant(AsyncResult ar)                                  /* 參考mGsmSmsRegistrant.notifyRegistrant(new AsyncResult(null, sms, null)) */
    {
        internalNotifyRegistrant (ar.result, ar.exception);           /* ar.result為sms */
    }

    /*package*/ void
    internalNotifyRegistrant (Object result, Throwable exception)     /* internalNotifyRegistrant (sms, Throwable exception) */
    {
        Handler h = getHandler();

        if (h == null) {
            clear();
        } else {
            Message msg = Message.obtain();                           /* 創建一個消息 */

            msg.what = what;                                          /* 消息類型EVENT_NEW_SMS */

            msg.obj = new AsyncResult(userObj, result, exception);    /* 消息內容sms */

            h.sendMessage(msg);                                       /* 發送消息到註冊了這個消息的Handler,參考phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null);的getHandler() */
        }
    }

    ... ...
}  

 

        然而BaseCommands是一個抽象類,實現了CommandsInterface中的setOnNewGsmSms介面,這個介面由GsmInboundSmsHandler調用
(phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null)),也就是說GsmInboundSmsHandler的getHandler()是EVENT_NEW_SMS
的監聽者,也就是說frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java中mGsmSmsRegistrant.notifyRegistrant(new AsyncResult(null, sms, null))
調用之後,會觸發GsmInboundSmsHandler中getHandler()的Handler對EVENT_NEW_SMS消息進行解析。這個Handler肯定是GsmInboundSmsHandler
實例化的對象中的,這個對象在什麼時候,在哪裡創建的,暫且不管。我們只管EVENT_NEW_SMS這個消息從哪裡來,然後到哪裡去
就行了。

./frameworks/opt/telephony/src/java/com/android/internal/telephony/ImsSMSDispatcher.java

public final class ImsSMSDispatcher extends SMSDispatcher {
    ... ...
    mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),         /* 獲取mGsmInboundSmsHandler,並啟動狀態機 */
            storageMonitor, phone);  
    ... ...
}

 

./frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java

public class GsmInboundSmsHandler extends InboundSmsHandler {
    ... ...
    /**
     * Create a new GSM inbound SMS handler.
     */
    private GsmInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
            PhoneBase phone) {
        super("GsmInboundSmsHandler", context, storageMonitor, phone,                              /* 構造GsmInboundSmsHandler時,通過super()調用InboundSmsHandler的構造函數 */
                GsmCellBroadcastHandler.makeGsmCellBroadcastHandler(context, phone));
        phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null);                               /* 註冊EVENT_NEW_SMS消息 */
        mDataDownloadHandler = new UsimDataDownloadHandler(phone.mCi);
    }
    
    ... ...
    
    /** 
     * Wait for state machine to enter startup state. We can't send any messages until then.
     */
    public static GsmInboundSmsHandler makeInboundSmsHandler(Context context,
            SmsStorageMonitor storageMonitor, PhoneBase phone) {
        GsmInboundSmsHandler handler = new GsmInboundSmsHandler(context, storageMonitor, phone);   /* 實例化GsmInboundSmsHandler */
        handler.start();                                                                           /* 抽象類InboundSmsHandler繼承與StateMachine,而GsmInboundSmsHandler繼承於InboundSmsHandler,
                                                                                                    * GsmInboundSmsHandler調用啟動狀態機方法start()
                                                                                                    */
        return handler;
    }

    ... ...
}

 

./frameworks/opt/telephony/src/java/com/android/internal/telephony/InboundSmsHandler.java

public abstract class InboundSmsHandler extends StateMachine {
    ... ...
    protected InboundSmsHandler(String name, Context context, SmsStorageMonitor storageMonitor,
            PhoneBase phone, CellBroadcastHandler cellBroadcastHandler) {
        ... ...
        
        addState(mDefaultState);                                                                   /* 構造InboundSmsHandler時,添加狀態機的狀態 */
        addState(mStartupState, mDefaultState);
        addState(mIdleState, mDefaultState);
        addState(mDeliveringState, mDefaultState);
        addState(mWaitingState, mDeliveringState);

        setInitialState(mStartupState);                                                            /* 初始化狀態機 */
        if (DBG) log("created InboundSmsHandler");
    }
    
    ... ...    
    
    class IdleState extends State {
        @Override
        public void enter() {
            if (DBG) log("entering Idle state");
            sendMessageDelayed(EVENT_RELEASE_WAKELOCK, WAKELOCK_TIMEOUT);
        }

        @Override
        public void exit() {
            mWakeLock.acquire();
            if (DBG) log("acquired wakelock, leaving Idle state");
        }

        @Override
        public boolean processMessage(Message msg) {
            if (DBG) log("Idle state processing message type " + msg.what);
            switch (msg.what) {
                case EVENT_NEW_SMS:                                                                /* 空閑時,接收到簡訊 */
                case EVENT_BROADCAST_SMS:
                    deferMessage(msg);
                    transitionTo(mDeliveringState);                                                /* 轉到mDeliveringState */
                    return HANDLED;
                    
                ... ...
            }
        }
    }    
        ... ...
        
    class DeliveringState extends State {                                                          /* 轉到mDeliveringState狀態 */
        @Override
        public void enter() {
            if (DBG) log("entering Delivering state");
        }

        @Override
        public void exit() {
            if (DBG) log("leaving Delivering state");
        }

        @Override
        public boolean processMessage(Message msg) {
            switch (msg.what) {
                case EVENT_NEW_SMS:
                    // handle new SMS from RIL
                    handleNewSms((AsyncResult) msg.obj);                                           /* 處理新SMS */
                    sendMessage(EVENT_RETURN_TO_IDLE);                                             /* 處理完回到空閑狀態 */
                    return HANDLED;
                
                ... ...
            }
        }    
            ... ...
    }
}


void handleNewSms(AsyncResult ar) {
    ... ...
    SmsMessage sms = (SmsMessage) ar.result;
    result = dispatchMessage(sms.mWrappedSmsMessage);
    ... ...
}


public int dispatchMessage(SmsMessageBase smsb) {
    ... ...
    return dispatchMessageRadioSpecific(smsb);
    ... ...
}

 

        通過以上流程可以瞭解到,當狀態機接收到SMS後,對消息進行分發,針對type zero, SMS-PP data download,
和3GPP/CPHS MWI type SMS判斷,如果是Normal SMS messages,則調用dispatchNormalMessage(smsb),然後創建
一個InboundSmsTracker對象,把信息保存到raw table,然後在通過sendMessage(EVENT_BROADCAST_SMS, tracker)
把消息廣播出去。

./frameworks/opt/telephony/src/java/com/android/internal/telephony/InboundSmsHandler.java  

class DeliveringState extends State {
    ... ...
    public boolean processMessage(Message msg) {
        switch (msg.what) {
            ... ...
            case EVENT_BROADCAST_SMS:                                                         /* 接收到EVENT_BROADCAST_SMS消息並處理 */
                // if any broadcasts were sent, transition to waiting state
                if (processMessagePart((InboundSmsTracker) msg.obj)) {
                    transitionTo(mWaitingState);
                }
                return HANDLED;
            ... ...
        }
    }
    ... ...

}
    
boolean processMessagePart(InboundSmsTracker tracker) {
    ... ...
    BroadcastReceiver resultReceiver = new SmsBroadcastReceiver(tracker);                    /* 創建一個廣播接收者,用來處理簡訊廣播的結果 */
    ... ...
    intent = new Intent(Intents.SMS_DELIVER_ACTION);                                         /* 設置當前intent的action為SMS_DELIVER_ACTION */

    // Direct the intent to only the default SMS app. If we can't find a default SMS app
    // then sent it to all broadcast receivers.
    ComponentName componentName = SmsApplication.getDefaultSmsApplication(mContext, true);   /* 這個action只會發送給carrier app,而且carrier app可以通過set result為RESULT_CANCELED來終止這個廣播 */
    if (componentName != null) {
        // Deliver SMS message only to this receiver
        intent.setComponent(componentName);
        log("Delivering SMS to: " + componentName.getPackageName() +
                " " + componentName.getClassName());
    }
    ... ...
    dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,                          /* 廣播intent */
                AppOpsManager.OP_RECEIVE_SMS, resultReceiver);
    ... ...
}

    
private final class SmsBroadcastReceiver extends BroadcastReceiver {
    ... ... 
    public void onReceive(Context context, Intent intent) {
        ... ...
        // Now that the intents have been deleted we can clean up the PDU data.
        if (!Intents.DATA_SMS_RECEIVED_ACTION.equals(action)
                && !Intents.DATA_SMS_RECEIVED_ACTION.equals(action)
                && !Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) {
            loge("unexpected BroadcastReceiver action: " + action);
        }

        int rc = getResultCode();
        if ((rc != Activity.RESULT_OK) && (rc != Intents.RESULT_SMS_HANDLED)) {
            loge("a broadcast receiver set the result code to " + rc
                    + ", deleting from raw table anyway!");
        } else if (DBG) {
            log("successful broadcast, deleting from raw table.");
        }

        deleteFromRawTable(mDeleteWhere, mDeleteWhereArgs);
        sendMessage(EVENT_BROADCAST_COMPLETE);                                               /* 成功廣播 */

        ... ...
    }
    ... ...
}

 

        到這裡,在應用層註冊具有Intents.SMS_RECEIVED_ACTION這樣action的廣播,就可以獲取到簡訊了。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...