Android ConnectivityService框架

Android中提供的数据业务方式有几种:移动数据网络,WIFI,热点,网线等。这些数据业务本身可以独立使用,但是同一时刻,只能使用其中的一种数据业务方式。管理这些数据业务方式的使用由ConnectivityService,NetworkFactory,NetworkAgent,NetworkMonitor等来完成,ConnectivityService处于核心调度位置。

ConnectivityService框架主要有四个方面组成:

一 . 网络有效性检测(NetworkMonitor)
二 . 网络评分机制(NetworkFactory)
三 . 路由配置信息的获取(NetworkAgent)
四 . 网络物理端口的设置(Netd)

其大体框架图如下:
这里写图片描述

这篇博客就针对前两个方面进行逐一分析,Netd这一块暂时还没研究到,待日后有空再整理下。

ConnectivityService的工作总结起来就是:通过wifi,mobile data,Tethering,VPN 等方式来获取路由配置信息。无论通过哪种方式,获取到路由配置信息后,需要交给ConnectivityService来处理,ConnectivityService通过ping网络来检查网络的有效性,进而影响到各个数据业务方式的评分值,ConnectivityService通过这些评分值来决定以哪个数据业务方式连接网络。决定好数据业务方式后,把这些路由配置信息设置到网络物理设备中。这样我们的手机就可以正常上网了。


初始化:

ConnectivityService属于系统服务,在SystemServer中被启动。

SystemServer启动的服务:

NetworkManagementService networkManagement = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
NetworkScoreService networkScore = null;
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

一 . ConnectivityService初始化

1.获取其他服务的接口

private INetworkManagementService mNetd;
private INetworkStatsService mStatsService;
private INetworkPolicyManager mPolicyManager;
mNetd = checkNotNull(netManager, "missing INetworkManagementService");
mStatsService = checkNotNull(statsService, "missing INetworkStatsService");
mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.注册其他必要的监听和广播,以便接收变化信息和通知变化信息。

mNetd.registerObserver(mTethering);
mNetd.registerObserver(mDataActivityObserver);

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_STARTED);
intentFilter.addAction(Intent.ACTION_USER_STOPPED);
intentFilter.addAction(Intent.ACTION_USER_ADDED);
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiverAsUser(mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

二 . NetworkFactory的初始化

NetworkFactory负责了网络评分机制的功能,为了在手机开机后可以及时依靠网络评分机制来选择网络。ConnectivityService服务起来后,在各个模块的初始化过程中,NetworkFactory必须要启动起来。以下的时序图只画了mobile data和wifi模块的NetworkFactory启动流程:
这里写图片描述

NetworkFactory在register()之后通过AsyncChannel与ConnectivityService建立起了连接,这一块的逻辑流程,如果看不太懂,那么需要去看看:AsyncChannel的工作机制

public void register() {
    if (DBG) log("Registering NetworkFactory");
    if (mMessenger == null) {
        mMessenger = new Messenger(this);
        ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, LOG_TAG);
    }
}
public void registerNetworkFactory(Messenger messenger, String name) {
    enforceConnectivityInternalPermission();
    NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel());
    mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
}
private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
    if (DBG) log("Got NetworkFactory Messenger for " + nfi.name);
    mNetworkFactoryInfos.put(nfi.messenger, nfi);
    nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

三 . NetworkAgent的初始化

NetworkAgent是一个网络代理,它里面保存了一些路由的配置信息,比如NetworkInfo,LinkProperties,NetworkCapabilities等。NetworkAgent的初始化都是在路由配置信息获取成功之后。比如打开数据开关,打开wifi开关等操作之后。

注:
NetworkInfo 描述一个给定类型的网络接口的状态方面的信息,包括网络连接状态、网络类型、网络可连接性、是否漫游等信息
LinkProperties 描述一个网络连接属性信息(包含网络地址、网关、DNS、HTTP代理等属性信息
NetworkCapabilities 描述一个网络连接能力方面的信息,包括带宽、延迟等

四 . NetworkMonitor的初始化

NetworkMonitor主要是检测网络有效性的,通过Http封装类去ping一个网站,根据ping网站的结果来影响评分值。因此,它的初始化是在NetworkAgent初始化之后,必须要获取到路由配置信息NetworkAgent后才会去初始化。


一 . 网络有效性检测(NetworkMonitor)

NetworkMonitor是一个状态机。负责检测网络有效性,也就是ping网络的过程。ping网络过程中产生的几种状态如下:

DefaultState 默认状态
EvaluatingState 验证状态
ValidatedState 验证通过状态
LingeringState 休闲状态,表示网络的验证位是真实的,并且曾经是满足特定NetworkRequest的最高得分网络,但是此时另一个网络满足了NetworkRequest的更高分数,在断开连接前的一段时间前,该网络被“固定”为休闲状态。
CaptivePortalState 强制门户状态
MaybeNotifyState 可能通知状态,表示用户可能已被通知需要登录。 在退出该状态时,应该小心清除通知。

NetworkMonitor中各个状态之间的关系:

这里写图片描述

以正常的ping网站过程为例,DefaultState为默认状态,NetworkMonitor接收到CMD_NETWORK_CONNECTED事件消息后,先由DefaultState状态处理,然后由EvaluatingState处理,最后交给ValidatedState处理。这一块的逻辑流程,如果看不太懂,那么需要去看看StateMachine状态机的使用:StateMachine状态机初识

从NetworkMonitor的初始化,到ping网站的过程,到ping网站的结果影响评分值。这个过程的时序图如下:
这里写图片描述

接下来将按照时序图中的三大步骤去结合代码分析。

1 . NetworkMonitor的初始化(以mobile data为例)

DataConnection从modem中获取到了代理信息,并把此代理信息保存到了NetworkAgent中:

mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
        "DcNetworkAgent", mNetworkInfo, makeNetworkCapabilities(), mLinkProperties,
        50, misc);
 
 
  • 1
  • 2
  • 3

在NetworkAgent的构造函数中,把它自己注册到了ConnectivityService中,

ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
        Context.CONNECTIVITY_SERVICE);
netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
        new LinkProperties(lp), new NetworkCapabilities(nc), score, misc);
 
 
  • 1
  • 2
  • 3
  • 4

接下来的流程就如时序图所示了,意味着每产生一个代理信息NetworkAgent的对象,就会有自己相应的NetworkMonitor状态机来处理ping网站的过程。

2 . ping网站的过程

NetworkMonitor状态机运行起来后,接收到sendMessage的消息就可以做相应的处理。这里比较重要的就是CMD_NETWORK_LINGER和CMD_NETWORK_CONNECTED消息,分别由ConnectivityService的linger()和unlinger()方法封装发送的操作。

linger():

封装了CMD_NETWORK_LINGER消息的发送操作,让NetworkMonitor进入到休闲状态:
CMD_NETWORK_LINGER消息首先进入到DefaultState.processMessage()处理:

case CMD_NETWORK_LINGER:
    transitionTo(mLingeringState);
 
 
  • 1
  • 2

CMD_NETWORK_LINGER消息切换到LingeringState处理,enter()做一个CMD_LINGER_EXPIRED消息延迟发送:

public void enter() {
    mEvaluationTimer.reset();
    final String cmdName = ACTION_LINGER_EXPIRED + "." + mNetId;
mWakeupMessage = makeWakeupMessage(mContext, getHandler(), cmdName, CMD_LINGER_EXPIRED);
    long wakeupTime = SystemClock.elapsedRealtime() + mLingerDelayMs;
    mWakeupMessage.schedule(wakeupTime);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

LingeringState.processMessage()中对延迟消息CMD_LINGER_EXPIRED做处理:

case CMD_LINGER_EXPIRED:
    mConnectivityServiceHandler.sendMessage(
            obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo));
    return HANDLED;
 
 
  • 1
  • 2
  • 3
  • 4

ConnectivityService中处理EVENT_NETWORK_LINGER_COMPLETE消息:

case NetworkMonitor.EVENT_NETWORK_LINGER_COMPLETE: {
    NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
    if (isLiveNetworkAgent(nai, msg.what)) {
        handleLingerComplete(nai);
    }
    break;
}
private void handleLingerComplete(NetworkAgentInfo oldNetwork) {
    teardownUnneededNetwork(oldNetwork);
}
private void teardownUnneededNetwork(NetworkAgentInfo nai) {
    for (int i = 0; i < nai.networkRequests.size(); i++) {
        NetworkRequest nr = nai.networkRequests.valueAt(i);
if (!isRequest(nr)) continue;
        break;
    }
    nai.asyncChannel.disconnect();
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

综上,CMD_NETWORK_LINGER消息的处理就是让NetworkMonitor进入空闲状态,NetworkMonitor处于空闲状态说明此网络不再需要,可以释放掉ConnectivityService和NetworkAgent的连接。

unlinger():

封装了CMD_NETWORK_CONNECTED消息的发送操作,让NetworkMonitor进入到非休闲状态:
CMD_NETWORK_CONNECTED消息首先进入到DefaultState.processMessage()处理:

case CMD_NETWORK_CONNECTED:
    transitionTo(mEvaluatingState);
    return HANDLED;
 
 
  • 1
  • 2
  • 3

CMD_NETWORK_LINGER消息切换到EvaluatingState处理,enter()方法发送CMD_REEVALUATE消息:

public void enter() {
sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
}
 
 
  • 1
  • 2
  • 3

CMD_REEVALUATE消息由EvaluatingState.processMessage()处理:
Ping网络的关键地方:

CaptivePortalProbeResult probeResult = isCaptivePortal();
 
 
  • 1

isCaptivePortal()通过HttpURLConnection类去ping一个网站,android原生给的网站在中国由于墙的存在,是ping不通的,因此就会出现wifi和信号格旁边有一个感叹号。芯片厂商一般会对这个网站进行客制化:这里写图片描述

private static String getCaptivePortalServerUrl(Context context, boolean isHttps) {
    String server = Settings.Global.getString(context.getContentResolver(),
            Settings.Global.CAPTIVE_PORTAL_SERVER);
if (server == null) server = DEFAULT_SERVER_SECONDARY;
    return (isHttps ? "https" : "http") + "://" + server + "/generate_204";
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在log中可以找到当前ping的网站是哪一个:

这里写图片描述

3 . 根据ping网站的结果影响评分值

if (probeResult.isSuccessful()) {
    transitionTo(mValidatedState);
} else if (probeResult.isPortal()) {
transitionTo(mCaptivePortalState);
} else {
final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
sendMessageDelayed(msg, mReevaluateDelayMs);
mReevaluateDelayMs *= 2;
if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) {
    mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS;
}
}
boolean isSuccessful() { return mHttpResponseCode == 204; }
boolean isPortal() {
    return !isSuccessful() && mHttpResponseCode >= 200 && mHttpResponseCode <= 399;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

根据ping网络的结果来执行不同的操作:
一.如果ping网络成功,网络返回204,切换到ValidatedState状态处理。
二.如果ping网络失败,网络返回200~399,转到CaptivePortalState状态处理。
三.如果ping网络失败,不是204,也不是200~399,则发送CMD_REEVALUATE消息,重新触发ping网络的动作。第一次失败,8s后重新ping网络,第二次失败,16s后重新ping网络,时间依次倍增,最长的时间间隔为10分钟。

重点关注ping网络成功后,ValidatedState状态的处理:ValidatedState.enter()

mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
        NETWORK_TEST_RESULT_VALID, mNetworkAgentInfo.network.netId, null));
 
 
  • 1
  • 2

ConnectivityService中处理EVENT_NETWORK_TESTED消息,把ping成功的状态保存到NetworkAgentInfo中,并通知NetworkFactory评分值已变,需要重新评估。

if (valid != nai.lastValidated) {
    final int oldScore = nai.getCurrentScore();
    nai.lastValidated = valid;
    nai.everValidated |= valid;
    updateCapabilities(nai, nai.networkCapabilities);
    // If score has changed, rebroadcast to NetworkFactories. b/17726566
    if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这里需要说明一下,ping网络的状态会保存到NetworkAgentInfo中,而后续所有的评分值都会调用NetworkAgentInfo的getCurrentScore()方法来获取,getCurrentScore()方法会根据当前ping网络的状态重新计算评分值:

private int getCurrentScore(boolean pretendValidated) {
if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
        return MAXIMUM_NETWORK_SCORE;
    }
    int score = currentScore;
    if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) && !pretendValidated) {
        score -= UNVALIDATED_SCORE_PENALTY;
    }
    if (score < 0) score = 0;
    return score;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果是用户指定的联网方式,评分值设置为100,如果ping网络失败,评分值-40,如果ping网络成功,则评分值不变。

二 . 网络评分机制(NetworkFactory)

NetworkFactory的存在意义就是为了帮助ConnectivityService进行评分的管理。一般在NetworkFactory在初始化时,设置固定的评分值,作为评判的标准。
NetworkAgent作为一个代理信息的抽象,在其初始化时,也设置了固定的评分值,不过,这个评分值会根据当前的网络情况的不同而变化,其最后的评分值会和NetworkFactory中的固定评分值进行比较,从而筛选出最优网络。

NetworkFactory和NetworkAgent的评分初始化:

TelephonyNetworkFactory:

private final static int TELEPHONY_NETWORK_SCORE = 50;
setCapabilityFilter(makeNetworkFilter(subscriptionController, phoneId));
setScoreFilter(TELEPHONY_NETWORK_SCmNetworkAgent = new 
 
 
  • 1
  • 2
  • 3

DataConnection(NetworkAgent子类):

DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
        "DcNetworkAgent", mNetworkInfo, makeNetworkCapabilities(), mLinkProperties,
        50, misc);
ORE);
 
 
  • 1
  • 2
  • 3
  • 4

以上只列出了TelephonyNetwork的评分初始化情况。以下是各个网络类型的评分初始化和变化情况:

NetworkFactory初始化NetworkAgent初始化NetworkMonitor中ping网络disconnect
TelephonyNetwork5050成功:+0 失败:-40 用户指定:+1000
Wifi6060成功:+0 失败:-40 用户指定:+1000
EthernetNetwork6969成功:+0 失败:-40 用户指定:+1000
PhoneSwitcher101101成功:+0 失败:-40 用户指定:+1000

各种数据业务类型的评分标准,除了其基础评分值不同之外,其他的评判标准都一样。其评分值的变化,主要有以下几种情况:

一.代理信息获取结束后,会参与ping网络的过程,如果ping网络成功,那么NetworkAgent中的评分值不变。如果ping网络失败,那么NetworkAgent中的评分值-40。如果用户指定了某种网络类型作为连接方式,那么NetworkAgent重的评分值+100。

二.如果NetworkAgent和ConnectivityService的AsyncChannel通道断开,需要设置其评分值为0,好让其他的评分高的网络类型连接。

NetworkFactory中的评分标准:

NetworkFactory中维持了基础的评分分值mScore,mScore只有在 NetworkFactory对象创建的时候才会赋值。因网络环境的变化导致需要重新进行网络评估时,使用基础评分分值与传进来的NetworkRequestInfo中的分值进行比较。如果当前的NetworkRequestInfo没有requested过,且当前的分值score比基础分值mScore小,说明当前的NetworkRequestInfo为最优网络,调用needNetworkFor()连接网络。如果当前的NetworkRequestInfo已经requested过,且当前的分值score比基础分值mScore大,说明当前的NetworkRequestInfo已经不是最优网络了,有个更优的网络可用连接,此时应该调用releaseNetworkFor()释放掉此类网络连接。

private void evalRequest(NetworkRequestInfo n) {
    if (VDBG) log("evalRequest request = " + n.request + " with requested = " + n.requested);
    if (n.requested == false && n.score < mScore &&
            n.request.networkCapabilities.satisfiedByNetworkCapabilities(
            mCapabilityFilter) && acceptRequest(n.request, n.score)) {
        if (VDBG) log("  needNetworkFor");
        needNetworkFor(n.request, n.score);
        n.requested = true;
    } else if (n.requested == true &&
            (n.score > mScore || n.request.networkCapabilities.satisfiedByNetworkCapabilities(
            mCapabilityFilter) == false || acceptRequest(n.request, n.score) == false)) {
        if (VDBG) log("  releaseNetworkFor");
        releaseNetworkFor(n.request);
        n.requested = false;
    } else {
        if (VDBG) log("  done");
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

以上是NetworkFactory的评分标准,那么,应该在哪里触发这个评分过程呢?

触发评分的过程:

1.NetworkFactory与ConnectivityService通过AsyncChannel建立连接的时候,初始化评分,并参与了第一次的评分过程。如果此时还没有ping网络的话,其传进来的评分值为基础评分值,以上代码会执行else逻辑。
这里写图片描述

2.调用sendUpdatedScoreToFactories()方法触发了评分过程

在ping网络过程中,会触发多次评分过程。在NetworkMonitor的多个状态中,都有向ConnectivityService发起EVENT_NETWORK_TESTED事件消息更新评分:

                mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
                    NETWORK_TEST_RESULT_VALID, mNetworkAgentInfo.network.netId, null));
 
 
  • 1
  • 2

ConnectivityService接收到EVENT_NETWORK_TESTED事件消息,更新评分值和触发评分机制:

private boolean maybeHandleNetworkMonitorMessage(Message msg) {
    switch (msg.what) {
        default:
            return false;
        case NetworkMonitor.EVENT_NETWORK_TESTED: {
                if (valid != nai.lastValidated) {
                    final int oldScore = nai.getCurrentScore();
                    nai.lastValidated = valid;
                    nai.everValidated |= valid;
                    updateCapabilities(nai, nai.networkCapabilities);
                    // If score has changed, rebroadcast to NetworkFactories. b/17726566
                    if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
                }
        }
    }
}               
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

从log中看评分过程:

例子:打开wifi开关连接wifi —> 打开数据开关 —-> 关闭wifi开关

radio.log
Telephony 角度看评分:
这里写图片描述

wifi角度看评分:
这里写图片描述

从wifi的log来看,此次wifi的评分过程没有满足条件,因此都没有执行releaseNetworkFor()或者needNetworkFor()方法。即使wifi执行了needNetworkFor()和releaseNetworkFor()方法,对于wifi也没有影响,因为wifi并没有实现对这两个方法的具体实现。如果存在比wifi优先级更高的数据业务方式,此处应该是要自己实现的吧??

WifiStateMachine.java

private class WifiNetworkFactory extends NetworkFactory {
    public WifiNetworkFactory(Looper l, Context c, String TAG, NetworkCapabilities f) {
        super(l, c, TAG, f);
    }

    @Override
    protected void needNetworkFor(NetworkRequest networkRequest, int score) {
        ++mConnectionRequests;
    }

    @Override
    protected void releaseNetworkFor(NetworkRequest networkRequest) {
        --mConnectionRequests;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

三 . 路由配置信息的获取(NetworkAgent)

路由配置信息的获取方式有多种,wifi,mobile data,Tethering,VPN都可,此处主要研究的是mobile data的路由配置信息的获取,由于篇幅较长,因此另起一篇博客。

四 . 网络物理端口的设置(Netd)

此篇章暂时留空白,待日后补充。可参考:http://www.360doc.com/content/13/0817/17/9171956_307859123.shtml

借用一张图表示Netd层的框架:
这里写图片描述

  •                     <li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#csdnc-thumbsup"></use>
                        </svg><span class="name">点赞</span>
                        <span class="count">4</span>
                        </a></li>
                        <li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;popu_824&quot;}"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#icon-csdnc-Collection-G"></use>
                        </svg><span class="name">收藏</span></a></li>
                        <li class="tool-item tool-active is-share"><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;1582594662_002&quot;}"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#icon-csdnc-fenxiang"></use>
                        </svg>分享</a></li>
                        <!--打赏开始-->
                                                <!--打赏结束-->
                                                <li class="tool-item tool-more">
                            <a>
                            <svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg>
                            </a>
                            <ul class="more-box">
                                <li class="item"><a class="article-report">文章举报</a></li>
                            </ul>
                        </li>
                                            </ul>
                </div>
                            </div>
            <div class="person-messagebox">
                <div class="left-message"><a href="https://blog.csdn.net/sjz4860402">
                    <img src="https://profile.csdnimg.cn/F/A/4/3_sjz4860402" class="avatar_pic" username="sjz4860402">
                                            <img src="https://g.csdnimg.cn/static/user-reg-year/2x/8.png" class="user-years">
                                    </a></div>
                <div class="middle-message">
                                        <div class="title"><span class="tit"><a href="https://blog.csdn.net/sjz4860402" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}" target="_blank">谁的影子</a></span>
                                            </div>
                    <div class="text"><span>发布了56 篇原创文章</span> · <span>获赞 40</span> · <span>访问量 16万+</span></div>
                </div>
                                <div class="right-message">
                                            <a href="https://im.csdn.net/im/main.html?userName=sjz4860402" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-letter">私信
                        </a>
                                                            <a class="btn btn-sm  bt-button personal-watch" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}">关注</a>
                                    </div>
                            </div>
                    </div>
    
  • 8
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值