当前位置:   article > 正文

Android基于网络的VoIP电话的实现linphone_android linphone

android linphone

Linphone是一个网络电话或者IP语音电话,可以直接通过网络设备进行电话的拨打。


简单的使用,直接上代码!



1.自定义的Service用于监听linphone的相关操作的状态,初始化LinphoneManager。

public class LinphoneService extends Service implements LinphoneCoreListener {
    private static final String TAG = "LinphoneService";
    private PendingIntent mKeepAlivePendingIntent;
    private static LinphoneService instance;
    private static PhoneServiceCallback sPhoneServiceCallback;
    private LinphoneCore mLinphoneCore;
    private LinphoneCall mLinphoneCall;

    public static void addCallback(PhoneServiceCallback phoneServiceCallback) {
        sPhoneServiceCallback = phoneServiceCallback;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        EventBus.getDefault().register(this);
        LinphoneCoreFactoryImpl.instance();
        LinphoneManager.createAndStart(LinphoneService.this);
        instance = this;
        Intent intent = new Intent(this, KeepAliveHandler.class);
        mKeepAlivePendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
        ((AlarmManager)this.getSystemService(Context.ALARM_SERVICE)).setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                SystemClock.elapsedRealtime() + 600000,
                600000,
                mKeepAlivePendingIntent);
    }

    public static boolean isReady() {
        return instance != null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
        LinphoneManager.destroy();
        ((AlarmManager)this.getSystemService(Context.ALARM_SERVICE)).cancel(mKeepAlivePendingIntent);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    /*
    *拒接操作
    */
    @Subscribe
    public void refuseCall(RefuseCallEvent event) {
        mLinphoneCore.declineCall(mLinphoneCall, Reason.None);
    }
    /*
    *通话状态监听
    */
    @Override
    public void callState(final LinphoneCore linphoneCore, final LinphoneCall linphoneCall, LinphoneCall.State state, String s) {
        Log.e(TAG, "callState: " + state.toString());
        if(state == LinphoneCall.State.OutgoingEarlyMedia){//建立连接
            if(sPhoneServiceCallback != null){
                sPhoneServiceCallback.callStart();
            }
        }
        if (state == LinphoneCall.State.Connected) {//接听
            if (sPhoneServiceCallback != null) {
                sPhoneServiceCallback.callConnected();
            }
        }
        if (state == LinphoneCall.State.CallEnd || state == LinphoneCall.State.CallReleased || state == LinphoneCall.State.Error) {
        //CallEnd通话结束  CallReleased通话释放  Error通话错误
        //将三个状态都统一成通话结束
            if (sPhoneServiceCallback != null) {
                sPhoneServiceCallback.callReleased();
            }
        }
    }
    /*
    *注册状态监听
    */
    @Override
    public void registrationState(LinphoneCore linphoneCore, LinphoneProxyConfig linphoneProxyConfig, LinphoneCore.RegistrationState registrationState, String s) {
        Log.e(TAG, "registrationState: = " + registrationState.toString());
        if(registrationState == LinphoneCore.RegistrationState.RegistrationOk){
            //注册成功
            EventBus.getDefault().post(new LoginOk());
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

自定义的管理类LinphoneManager,初始化 linphone

public class LinphoneManager implements LinphoneCoreListener {
    private static final String TAG = "LinphoneManager";
    private static LinphoneManager instance;
    private Context mServiceContext;
    private LinphoneCore mLc;
    private Timer mTimer;
    private LinphoneCall mCall;
    private static boolean sExited;
    private Resources mR;

    private String mLPConfigXsd = null;
    private String mLinphoneFactoryConfigFile = null;
    public String mLinphoneConfigFile = null;
    private String mLinphoneRootCaFile = null;
    private String mRingSoundFile = null;
    private String mRingBackSoundFile = null;
    private String mPauseSoundFile = null;
    private String mChatDatabaseFile = null;

    private BroadcastReceiver mKeepAliveReceiver = new KeepAliveReceiver();

    public LinphoneManager(Context serviceContext) {
        mServiceContext = serviceContext;
        LinphoneCoreFactory.instance().setDebugMode(true, "huanyutong");
        sExited = false;

        String basePath = mServiceContext.getFilesDir().getAbsolutePath();
        mLPConfigXsd = basePath + "/lpconfig.xsd";
        mLinphoneFactoryConfigFile = basePath + "/linphonerc";
        mLinphoneConfigFile = basePath + "/.linphonerc";
        mLinphoneRootCaFile = basePath + "/rootca.pem";
        mRingSoundFile = basePath + "/oldphone_mono.wav";
        mRingBackSoundFile = basePath + "/ringback.wav";
        mPauseSoundFile = basePath + "/toy_mono.wav";
        mChatDatabaseFile = basePath + "/linphone-history.db";
//        mErrorToneFile = basePath + "/error.wav";

        mR = serviceContext.getResources();
    }

    public synchronized static final LinphoneManager createAndStart(Context context) {
        if (instance != null) {
            throw new RuntimeException("Linphone Manager is already initialized");
        }
        instance = new LinphoneManager(context);
        instance.startLibLinphone(context);
        return instance;
    }

    public static synchronized LinphoneCore getLcIfManagerNotDestroyOrNull() {
        if (sExited || instance == null) {
            Log.e("Trying to get linphone core while LinphoneManager already destroyed or not created");
            return null;
        }
        return getLc();
    }

    public static final boolean isInstanceiated() {
        return instance != null;
    }

    public static synchronized final LinphoneCore getLc() {
        return getInstance().mLc;
    }

    public static synchronized final LinphoneManager getInstance() {
        if (instance != null) {
            return instance;
        }
        if (sExited) {
            throw new RuntimeException("Linphone Manager was already destroyed. "
                    + "Better use getLcIfManagerNotDestroyed and check returned value");
        }
        throw new RuntimeException("Linphone Manager should be created before accessed");
    }

    private synchronized void startLibLinphone(Context context) {
        try {
            copyAssetsFromPackage();
            mLc = LinphoneCoreFactory.instance().createLinphoneCore(this, mLinphoneConfigFile,
                    mLinphoneFactoryConfigFile, null, context);
            mLc.addListener((LinphoneCoreListener)context);

            try {
                initLibLinphone();
            } catch (LinphoneCoreException e) {
                Log.e(e);
            }

            TimerTask task = new TimerTask() {
                @Override
                public void run() {
                    UIThreadDispatcher.dispatch(new Runnable() {
                        @Override
                        public void run() {
                            if (mLc != null) {
                                mLc.iterate();
                            }
                        }
                    });
                }
            };
            mTimer = new Timer("Linphone Scheduler");
            mTimer.schedule(task, 0, 20);
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "startLibLinphone: cannot start linphone");
        }
    }

    private synchronized void initLibLinphone() throws LinphoneCoreException {
        mLc.setContext(mServiceContext);
        setUserAgent();
        mLc.setRemoteRingbackTone(mRingSoundFile);
        mLc.setTone(ToneID.CallWaiting, mRingSoundFile);
        mLc.setRing(mRingSoundFile);
        mLc.setRootCA(mLinphoneRootCaFile);
        mLc.setPlayFile(mPauseSoundFile);
        mLc.setChatDatabasePath(mChatDatabaseFile);
//        mLc.setCallErrorTone(Reason.NotFound, mErrorToneFile);//设置呼叫错误播放的铃声

        int availableCores = Runtime.getRuntime().availableProcessors();
        Log.w(TAG, "MediaStreamer : " + availableCores + " cores detected and configured");
        mLc.setCpuCount(availableCores);

        int migrationResult = getLc().migrateToMultiTransport();
        Log.d(TAG, "Migration to multi transport result = " + migrationResult);

        mLc.setNetworkReachable(true);

        //回声消除
        boolean isEchoCancellation =  true;
        mLc.enableEchoCancellation(isEchoCancellation);

        //自适应码率控制
        boolean isAdaptiveRateControl = true;
        mLc.enableAdaptiveRateControl(isAdaptiveRateControl);

        //audio 码率设置
        LinphoneUtils.getConfig(mServiceContext).setInt("audio", "codec_bitrate_limit", 128);

        mLc.setPreferredVideoSizeByName("720p");
        mLc.setUploadBandwidth(1536);
        mLc.setDownloadBandwidth(1536);

        mLc.setVideoPolicy(mLc.getVideoAutoInitiatePolicy(), true);
        mLc.setVideoPolicy(true, mLc.getVideoAutoAcceptPolicy());
        mLc.enableVideo(true, true);

        setCodecMime();

        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        mServiceContext.registerReceiver(mKeepAliveReceiver, filter);
    }


    private void setCodecMime()
    {
        for (final PayloadType pt : mLc.getVideoCodecs())
        {
            android.util.Log.i(TAG, "setCodecMime = " + pt.getMime());
            if (!pt.getMime().equals("VP8"))
            {
                try
                {
                    android.util.Log.i(TAG, "disable codec " + pt.getMime());
                    mLc.enablePayloadType(pt, false);
                }
                catch (LinphoneCoreException e)
                {
                    Log.e(e);
                }
            }
        }
    }

    private void copyAssetsFromPackage() throws IOException {
        LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.oldphone_mono, mRingSoundFile);
        LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.ringback, mRingBackSoundFile);
        LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.toy_mono, mPauseSoundFile);
        LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.linphonerc_default, mLinphoneConfigFile);
        LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.linphonerc_factory, new File(mLinphoneFactoryConfigFile).getName());
        LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.lpconfig, mLPConfigXsd);
        LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.rootca, mLinphoneRootCaFile);
    }

    private void setUserAgent() {
        try {
            String versionName = mServiceContext.getPackageManager().getPackageInfo(mServiceContext.getPackageName(),
                    0).versionName;
            if (versionName == null) {
                versionName = String.valueOf(mServiceContext.getPackageManager().getPackageInfo(mServiceContext.getPackageName(), 0).versionCode);
            }
            mLc.setUserAgent("Hunayutong", versionName);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void destroy() {
        if (instance == null) {
            return;
        }
        sExited = true;
        instance.doDestroy();
    }

    private void doDestroy() {
        try {
            mTimer.cancel();
            mLc.destroy();
        } catch (RuntimeException e) {
            e.printStackTrace();
        } finally {
            mLc = null;
            instance = null;
        }
    }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220

自定义的用于状态监听的抽象类

public abstract class PhoneServiceCallback {
    /**
     * 注册状态
     * @param registrationState
     */
    public void registrationState(LinphoneCore.RegistrationState registrationState) {}
    /**
     * 注册状态
     * @param registrationState
     */
    public void unRegistrationState(LinphoneCore.RegistrationState registrationState) {}

    /**
     * 来电状态
     * @param linphoneCall
     */
    public void incomingCall(LinphoneCall linphoneCall) {}

    /**
     * 电话接通
     */
    public void callConnected() {}

    /**
     * 电话被挂断
     */
    public void callReleased() {}
    /**
     * 电话接通
     */
    public void callStart() {}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

封装linphone的工具类PhoneVoiceUtils,包括了登录、登出、拨打电话、静音、外放功能的集成

public class PhoneVoiceUtils {
    private static final String TAG = "PhoneVoiceUtils";
    private static volatile PhoneVoiceUtils sPhoneVoiceUtils;
    private LinphoneCore mLinphoneCore = null;

    public static PhoneVoiceUtils getInstance() {
        if (sPhoneVoiceUtils == null) {
            synchronized (PhoneVoiceUtils.class) {
                if (sPhoneVoiceUtils == null) {
                    sPhoneVoiceUtils = new PhoneVoiceUtils();
                }
            }
        }
        return sPhoneVoiceUtils;
    }

    private PhoneVoiceUtils() {
        mLinphoneCore = LinphoneManager.getLc();
        mLinphoneCore.enableEchoCancellation(true);
        mLinphoneCore.enableEchoLimiter(true);
    }

    /**
     * 注册到服务器
     *
     * @param name  账号名
     * @param password  密码
     * @param host IP地址:端口号
     * @throws LinphoneCoreException
     */
    public void registerUserAuth(String name, String password, String host) throws LinphoneCoreException {
        Log.e(TAG, "registerUserAuth name = " + name);
        Log.e(TAG, "registerUserAuth pw = " + password);
        Log.e(TAG, "registerUserAuth host = " + host);
        String identify = "sip:" + name + "@" + host;
        String proxy = "sip:" + host;
        LinphoneAddress proxyAddr = LinphoneCoreFactory.instance().createLinphoneAddress(proxy);
        LinphoneAddress identifyAddr = LinphoneCoreFactory.instance().createLinphoneAddress(identify);
        LinphoneAuthInfo authInfo = LinphoneCoreFactory.instance().createAuthInfo(name, null, password,
                null, null, host);
        LinphoneProxyConfig prxCfg = mLinphoneCore.createProxyConfig(identifyAddr.asString(),
                proxyAddr.asStringUriOnly(), proxyAddr.asStringUriOnly(), true);
        prxCfg.enableAvpf(false);
        prxCfg.setAvpfRRInterval(0);
        prxCfg.enableQualityReporting(false);
        prxCfg.setQualityReportingCollector(null);
        prxCfg.setQualityReportingInterval(0);
        prxCfg.enableRegister(true);
        mLinphoneCore.addProxyConfig(prxCfg);
        mLinphoneCore.addAuthInfo(authInfo);
        mLinphoneCore.setDefaultProxyConfig(prxCfg);
        LinphoneService.addCallback(new PhoneServiceCallback() {
            @Override
            public void registrationState(LinphoneCore.RegistrationState registrationState) {
                super.registrationState(registrationState);
            }
        });
    }
    //取消注册
    public void unRegisterUserAuth() {
        mLinphoneCore = LinphoneManager.getLc();
        mLinphoneCore.clearAuthInfos();
    }
    /**
     * 拨打电话
     * @param phone  手机号
     */
    public LinphoneCall startSingleCallingTo(String phone) {
        LinphoneAddress address;
        LinphoneCall call = null;
        try {
            address = mLinphoneCore.interpretUrl(phone);
        } catch (LinphoneCoreException e) {
            e.printStackTrace();
            Log.e("startSingleCallingTo", " LinphoneCoreException0:" + e.toString());
            return null;
        }
        LinphoneCallParams params = mLinphoneCore.createCallParams(null);
        params.setVideoEnabled(false);
        try {
            call = mLinphoneCore.inviteAddressWithParams(address, params);
        } catch (LinphoneCoreException e) {
            e.printStackTrace();
            Log.e("startSingleCallingTo", " LinphoneCoreException1:" + e.toString());
        }
        return call;
    }


    /**
     * 挂断电话
     */
    public void hangUp() {
        if(mLinphoneCore == null)
            mLinphoneCore = LinphoneManager.getLc();
        LinphoneCall currentCall = mLinphoneCore.getCurrentCall();
        if (currentCall != null) {
            mLinphoneCore.terminateCall(currentCall);
        } else if (mLinphoneCore.isInConference()) {
            mLinphoneCore.terminateConference();
        } else {
            mLinphoneCore.terminateAllCalls();
        }
    }

    /**
     * 是否静音
     *
     * @param isMicMuted
     */
    public void toggleMicro(boolean isMicMuted) {
        if(mLinphoneCore == null)
            mLinphoneCore = LinphoneManager.getLc();
        mLinphoneCore.muteMic(isMicMuted);
    }

    /**
     * 是否外放
     *
     * @param isSpeakerEnabled
     */
    public void toggleSpeaker(boolean isSpeakerEnabled) {
        if(mLinphoneCore == null)
            mLinphoneCore = LinphoneManager.getLc();
        mLinphoneCore.enableSpeaker(isSpeakerEnabled);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127





使用

首先要开启service,在开启完成之后,进行注册操作,注册成功即可进行电话的拨打。

//注册VoIP
    public void registerVoip(String sipAccount) {
        if (LinphoneService.isReady()) {//判断service是否开启
            registerCall(sipAccount);
        } else {
            mServiceWaitThread = new ServiceWaitThread();
            mServiceWaitThread.start();
        }
    }
    private ServiceWaitThread mServiceWaitThread;
    private class ServiceWaitThread extends Thread {
        @Override
        public void run() {
            super.run();
            while (!LinphoneService.isReady()) {
                try {
                    startService(new Intent(Intent.ACTION_MAIN).setClass(getApplicationContext(), LinphoneService.class));
                    sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException("waiting thread sleep() has been interrupted");
                }
            }
            EventBus.getDefault().postSticky(new LinphoneServiceStartEvent());
            mServiceWaitThread = null;
        }
    }
    //注册VoIP
    public void registerCall(String sipAccount) {
        try {
            PhoneVoiceUtils.getInstance().registerUserAuth(sipAccount, AppString.SIPPASSWORD, AppString.SIPSERVER);
        } catch (LinphoneCoreException e) {
            e.printStackTrace();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

注册成功之后,就可以直接调用拨打电话了。直接调用PhoneVoiceUtils的拨打电话的方法

PhoneVoiceUtils.getInstance().startSingleCallingTo(number);
  • 1

添加通话监听,在不同的状态进行相应的操作

LinphoneService.addCallback(new PhoneServiceCallback() {
            @Override
            public void callConnected() {
                //电话接通
            }

            @Override
            public void callStart() {
                //建立连接
            }

            @Override
            public void callReleased() {
                //电话挂断
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

挂断电话

PhoneVoiceUtils.getInstance().hangUp();
  • 1

至此,通话的一个简单流程完成。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/128747
推荐阅读
相关标签
  

闽ICP备14008679号