IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Unity接入科大讯飞SDK-安卓篇 -> 正文阅读

[游戏开发]Unity接入科大讯飞SDK-安卓篇

最近需要接入sdk,现把接入科大讯飞SDK安卓端的过程整理如下:

接入SDK分成两个大的部分:安卓端 和 Unity端

安卓端主要负责导入科大讯飞的sdk,并编写相关脚本,最后生成aar包,提供给Unity使用

Unity端则根据导出的aar包调用相关方法实现科大语音的功能

本文使用的工具是:Android Studio, Unity2018.4.1f1

一、安卓端:

1.打开Android Studio,新建项目:

?设置项目名称和sdk:

PS:本项目中使用的Android SDK可以将之前eclipse项目中用到的sdk拷贝一份,作为Android Studio使用的sdk。也可以直接下载文末附上的SDK链接中的内容

原因:Android Studio中的sdk路径最好跟eclipse使用的区分开,因为sdk更新之后有可能会导致eclipse项目无法使用 —— 根据项目具体情况有概率出现

创建完成后界面如下:

切换视图到“Project”页签:

切换后界面如下:

2.新建Module:

选中该项目,右键“New -》Module”:

设置Module name:

创建完成后界面如下:

?以下安卓端所有的操作都在此“voiceLib”文件夹下完成。

3.导入需要的jar包:

***先导入Unity的Classes.jar包:—— 这里使用的Unity2018.4.1f1,选择Mono打包方式

D:\Work\ProgramFiles\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes\Classes.jar

选中该“Classes.jar”,Ctrl + C复制一份

在AndroidStudio中选中“voiceLib ->libs”,鼠标右键“Paste”:

***导入科大讯飞的jar包:

在科大讯飞官网注册一个产品:控制台-讯飞开放平台

注册完成即能得到如下页面:

如何下载科大讯飞SDK:

点击页面上方的“平台首页”:

?点击菜单栏:“SDK下载”

?选择对应的应用,平台以及需要接入的语音功能后下载SDK:

下载完成后解压:

?选择“libs”文件夹,将该文件夹下的三个文件都拷贝到AndroidStudio项目的“voiceLib ->libs”下:

?以上一共导入4个文件到AndroidStudio项目中:

?*** 关联两个jar包:

选中以上两个jar包,鼠标右键选择“Add as library”:

注意:没有关联之前,两个jar包是无法展开的;关联之后则可以

查看关联结果:选中该module,鼠标右键选择“Open Module Settings”

如果以上可以看到这两个jar包则代表关联成功

4.导入“libmsc.so”文件:

在“src-》main”下新建Jnilibs”文件夹:

注意文件夹名字不能写错:

将科大讯飞SDK中“libs”文件夹下的“armeabi-v7a"复制进来:

效果如下:

如此需要导入到AndroidStudio项目中的文件都导入完成了

5.修改Mainfest.xml文件:

将“app ->src ->main”文件夹下的“AndroidMainfest.xml"的内容复制一份:

?

?拷贝到刚才新建的Module:“voiceLib ->src -> main”下的“AndroidMainfest.xml”中:

粘贴之后效果如下:

粘贴之后会报红色的错误,所以还需要修改该文件。将该文件修改成以下效果即可:

注意:

1."voiceManager“:这里需要改成继承了“UnityPlayerActivity"的类,作为入口存在 —— 这个类后面会创建,并编写代码

2.这行代码很重要,一定要有,否则Unity会调用不到

<meta-data android:name="unityplayer.UnityActivity" android:value="true" />

之后在该”AndroidMainfest.xml”中插入以下权限描述:

    <!--连接网络权限,用于执行云端语音能力 -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!--获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <!--读取网络信息状态 -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <!--获取当前wifi状态 -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <!--允许程序改变网络连接状态 -->
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
    <!--读取手机信息权限 -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <!--读取联系人权限,上传联系人需要用到此权限 -->
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <!--外存储写权限,构建语法需要用到此权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <!--外存储读权限,构建语法需要用到此权限 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <!--配置权限,用来记录应用配置信息 -->
    <uses-permission android:name="android.permission.WRITE_SETTINGS"
        tools:ignore="ProtectedPermissions" />
    <!--手机定位信息,用来为语义等功能提供定位,提供更精准的服务-->
    <!--定位信息是敏感信息,可通过Setting.setLocationEnable(false)关闭定位请求 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <!--如需使用人脸识别,还要添加:摄像头权限,拍照需要用到 -->
    <uses-permission android:name="android.permission.CAMERA" />

?粘贴之后可能有报错:

点击以上选项修复报错即可。

如此则修改完成。“voiceLib -> src -> main”下的“AndroidMainfest.xml”文件完整代码如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.frank.voicelib">

    <application
        android:allowBackup="true"
        android:label="voicelib"
        android:supportsRtl="true">
        <activity android:name=".voiceManager">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data android:name="unityplayer.UnityActivity" android:value="true"/>
        </activity>
    </application>

    <!--连接网络权限,用于执行云端语音能力 -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!--获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <!--读取网络信息状态 -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <!--获取当前wifi状态 -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <!--允许程序改变网络连接状态 -->
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
    <!--读取手机信息权限 -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <!--读取联系人权限,上传联系人需要用到此权限 -->
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <!--外存储写权限,构建语法需要用到此权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <!--外存储读权限,构建语法需要用到此权限 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <!--配置权限,用来记录应用配置信息 -->
    <uses-permission android:name="android.permission.WRITE_SETTINGS"
        tools:ignore="ProtectedPermissions" />
    <!--手机定位信息,用来为语义等功能提供定位,提供更精准的服务-->
    <!--定位信息是敏感信息,可通过Setting.setLocationEnable(false)关闭定位请求 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <!--如需使用人脸识别,还要添加:摄像头权限,拍照需要用到 -->
    <uses-permission android:name="android.permission.CAMERA" />
</manifest>

如此项目环境搭建完成。

6.编写Android端代码:

在以下路径创建两个java文件:

“JsonParser.java”脚本如下:

package com.frank.voicelib;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;

/**
 * Json结果解析类
 */
public class JsonParser {

    public static String parseIatResult(String json) {
        StringBuffer ret = new StringBuffer();
        try {
            JSONTokener tokener = new JSONTokener(json);
            JSONObject joResult = new JSONObject(tokener);

            JSONArray words = joResult.getJSONArray("ws");
            for (int i = 0; i < words.length(); i++) {
                // 转写结果词,默认使用第一个结果
                JSONArray items = words.getJSONObject(i).getJSONArray("cw");
                JSONObject obj = items.getJSONObject(0);
                ret.append(obj.getString("w"));
//				如果需要多候选结果,解析数组其他字段
//				for(int j = 0; j < items.length(); j++)
//				{
//					JSONObject obj = items.getJSONObject(j);
//					ret.append(obj.getString("w"));
//				}
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ret.toString();
    }

    public static String parseGrammarResult(String json) {
        StringBuffer ret = new StringBuffer();
        try {
            JSONTokener tokener = new JSONTokener(json);
            JSONObject joResult = new JSONObject(tokener);

            JSONArray words = joResult.getJSONArray("ws");
            for (int i = 0; i < words.length(); i++) {
                JSONArray items = words.getJSONObject(i).getJSONArray("cw");
                for(int j = 0; j < items.length(); j++)
                {
                    JSONObject obj = items.getJSONObject(j);
                    if(obj.getString("w").contains("nomatch"))
                    {
                        ret.append("没有匹配结果.");
                        return ret.toString();
                    }
                    ret.append("【结果】" + obj.getString("w"));
                    ret.append("【置信度】" + obj.getInt("sc"));
                    ret.append("\n");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            ret.append("没有匹配结果.");
        }
        return ret.toString();
    }

    public static String parseLocalGrammarResult(String json) {
        StringBuffer ret = new StringBuffer();
        try {
            JSONTokener tokener = new JSONTokener(json);
            JSONObject joResult = new JSONObject(tokener);

            JSONArray words = joResult.getJSONArray("ws");
            for (int i = 0; i < words.length(); i++) {
                JSONArray items = words.getJSONObject(i).getJSONArray("cw");
                for(int j = 0; j < items.length(); j++)
                {
                    JSONObject obj = items.getJSONObject(j);
                    if(obj.getString("w").contains("nomatch"))
                    {
                        ret.append("没有匹配结果.");
                        return ret.toString();
                    }
                    ret.append("【结果】" + obj.getString("w"));
                    ret.append("\n");
                }
            }
            ret.append("【置信度】" + joResult.optInt("sc"));

        } catch (Exception e) {
            e.printStackTrace();
            ret.append("没有匹配结果.");
        }
        return ret.toString();
    }

    public static String parseTransResult(String json,String key) {
        StringBuffer ret = new StringBuffer();
        try {
            JSONTokener tokener = new JSONTokener(json);
            JSONObject joResult = new JSONObject(tokener);
            String errorCode = joResult.optString("ret");
            if(!errorCode.equals("0")) {
                return joResult.optString("errmsg");
            }
            JSONObject transResult = joResult.optJSONObject("trans_result");
            ret.append(transResult.optString(key));
			/*JSONArray words = joResult.getJSONArray("results");
			for (int i = 0; i < words.length(); i++) {
				JSONObject obj = words.getJSONObject(i);
				ret.append(obj.getString(key));
			}*/
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ret.toString();
    }
}

“voiceManager.java"脚本如下:

package com.frank.voicelib;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;

import com.iflytek.cloud.RecognizerListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.SpeechUtility;
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.LinkedHashMap;

public class voiceManager extends  UnityPlayerActivity{
    private SpeechRecognizer mIat;       //语音识别器
    private RecognizerListener mRecognizerLis;    //语音识别监听接口
    private HashMap<String, String> mIatResults = new LinkedHashMap<String, String>();   //语音识别结果

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getPermission();   //获取权限
        this.InitializeSpeechRecognizer();   //初始化语音识别
    }

    //开始语音识别,提供给Unity调用
    public void beginListen(){
        Log.d("@@@", "开始判断权限是否获取 ");
        //使用录音前首先需要获取权限
        int permissionCheck = this.checkSelfPermission(android.Manifest.permission.RECORD_AUDIO);
        //如果有权限则返回PackageManager.PERMISSION_GRANTED,否则返回PackageManager.PERMISSION_DENIED。
        if(permissionCheck!= PackageManager.PERMISSION_GRANTED) {//未获取权限时
            //请求获取权限
            Log.d("@@@", "正在请求权限 ");
            this.requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 0x01);
            //用new String[]的原因是可以在String[]中存储多个需要的权限,一次过请求
            //将回调onRequestPermissionsResult()方法
        }else {
            //开始识别
            mIat.startListening(mRecognizerLis);
        }
    }

    //region 由于语音识别功能一定要获取设备的“RECORD_AUDIO"权限,否则该功能无法正常运行
    //获取权限
    public void getPermission() {
        Log.d("@@@", "开始获取各类权限 ");
        this.requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 0x01);
        Log.d("@@@", "各类权限获取成功 ");
    }

    //权限获取的回调结果
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        switch (requestCode) {
            case 0x01: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0&&  grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.d("@@@", "进入获取权限成功的回调了 ");
                    // permission was granted, yay! Do the
                    // contacts-related task you need to do.
                    UnityPlayer.UnitySendMessage("VoiceController", "GetPermissionMsgFromAndroid", "获取到录音的权限");
                } else if(grantResults[0] != PackageManager.PERMISSION_GRANTED)
                {
                    // permission denied, boo! Disable the
                    // functionality that depends on this permission.
                    Log.d("@@@", "进入获取权限失败的回调了 ");
                    UnityPlayer.UnitySendMessage("VoiceController", "GetPermissionMsgFromAndroid", "没有获取到权限");
                }
                return;
            }
        }
    }
    //endregion

    //region 语音识别功能初始化,监听接口以及识别结果输出
    //初始化语音识别功能
    public void InitializeSpeechRecognizer(){
        //初始化
        SpeechUtility.createUtility(this, SpeechConstant.APPID + "=5bea308f");
        mIat = SpeechRecognizer.createRecognizer(this, null);
        //设置mIat的参数
        //表示是什么服务
        mIat.setParameter(SpeechConstant.DOMAIN, "iat");
        //设置语言
        mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
        //接受语言的类型
        mIat.setParameter(SpeechConstant.ACCENT, "mandarin");
        //使用什么样引擎
        mIat.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);

        //语音识别接口监听:
        mRecognizerLis=new RecognizerListener() {
            @Override
            public void onVolumeChanged(int i, byte[] bytes) {
            }
            @Override
            public void onBeginOfSpeech() {
            }
            @Override
            public void onEndOfSpeech() {
            }
            @Override       //语音识别得到结果
            public void onResult(RecognizerResult recognizerResult, boolean b) {
                printResult(recognizerResult);
            }
            @Override
            public void onError(SpeechError speechError) {
            }
            @Override
            public void onEvent(int i, int i1, int i2, Bundle bundle) {
            }
        };
    }

    //解析语音识别结果成string
    private void printResult(RecognizerResult results) {
        String text = JsonParser.parseIatResult(results.getResultString());

        String sn = null;
        // 读取json结果中的sn字段
        try {
            JSONObject resultJson = new JSONObject(results.getResultString());
            sn = resultJson.optString("sn");
        } catch (JSONException e) {
            e.printStackTrace();
        }

        mIatResults.put(sn, text);

        StringBuffer resultBuffer = new StringBuffer();
        for (String key : mIatResults.keySet()) {
            resultBuffer.append(mIatResults.get(key));
        }
        //把消息发送给Unity场景中iFlytekASRController物体上的OnResult方法
        UnityPlayer.UnitySendMessage("VoiceController", "GetVoiceResultFromAndroid", resultBuffer.toString());
    }
    // endregion

    //#region 检测Unity和Android交互是否联通
    public int beginTest(int a, int b){
        //交互测试
        return a+b;
    }

    public void connected(String str){
        StringBuffer buffer = new StringBuffer(str);
        buffer.append("    连通成功了");
        UnityPlayer.UnitySendMessage("VoiceController","TestConnectResult", buffer.toString());
    }
    //#endregion
}

注意:

1.初始化语音识别功能时使用的"APPID"需要科大讯飞官网上的产品id:

?所以在科大讯飞官网找到已经注册完成的应用,获取其id即可:

?2.语音识别功能需要获取设备的“RECORD_AUDIO"权限,否则无法正常识别

7.打包aar:

选中"voiceLib”,菜单栏“Build -》Make Module”

?

打包aar时遇到问题:

?解决办法:

不需要卸载"Build Tools 31.0.0"后重装。

之所以出现这个问题是由于缺失两个文件:dx.bat,? dx.jar

将文件夹内的“d8.bat”重命名为“dx.bat"

将子文件夹“lib”下的“d8.jar”修改为“dx.jar”即可

参考大佬详细解释链接:Android Studio error "Installed Build Tools revision 31.0.0 is corrupted" - Stack Overflow

两个文件重命名后打包完成:

?注意:在AndroidStudio中开发项目时需要设置其SDK,以及NDK路径。文末会附上需要用到的SDK, NDK下载链接,大家可以按需求取用

8.修改打包出来的aar文件:

打包完成后,在如下路径找到这两个文件:AndroidMainfest.xml,voiceLib-debug.aar

将这两个文件复制出来,新建一个文件夹用来存放这两个文件:

*** 修改aar包:

注意:这里不要把aar包解压,然后去修改里面的内容,最后重新压缩成aar。这样操作之后新生成的aar包是无法被Unity正常使用的:

选中该aar文件后,使用“360压缩”打开:

下面要执行的操作是将子文件夹libs中的“classes.jar”删掉,将aar包根目录中的“classes.jar”放到libs下

两个“classes.jar”只能留下一个,两个都存在时,Unity打包会报错

首先进入libs文件夹:选中“classes.jar"后直接删掉

然后选中根目录中的"classes.jar”,注意这里是无法直接拖动该文件到libs文件夹的。所以选中之后先复制,然后进入libs文件夹,粘贴即可

然后退回到aar根目录,选中根目录中的“classes.jar”,删除掉即可,两个classes.jar不可同时存在

删除后效果如下:

***修改aar包中的AndroidMainfest文件:

选中aar包中的AndroidMainfest.xml文件,打开:

将 android:label="voicelib" 这行代码删掉:这行代码用于显示Unity中打包出来的apk安装在真机上后显示的name。据说不删的话会与外部的“AndroidMainfest.xml"冲突,所以最好是删掉

?

?删掉之后的效果:

注意:这里不要把aar包解压,然后修改aar包内的AndroidMainfest.xml文件,直接在aar包内修改即可

如此“aar”包即修改完成

*** 修改包外的AndroidMainfest.xml文件:

这里修改package name为:不与aar内部的AndroidMainfest.xml一致即可。

如aar内部AndroidMainfest.xml的package name为“com.frank.voicelib”,因此这里修改外部的AndroidMainfest.xml为“com.frank.voicelib1”即可

PS:

1.外部AndroidMainfest.xml设置的package name需要与Unity中最后打包时PlayerSetting设置的package 一致,否则Unity会调用不到AndroidStudio中的方法

2.外部的 android:label="voicelib” 可以用于设置Unity中最后打包出来的apk安装在真机上显示的name。因此如果需要修改apk name可以直接在这里修改

PS: “label”属性控制的是Unity打包出来的apk在真机上安装完成后显示在手机上的apk名字,并不是Unity打包出来的apk名字或者PlayerSetting中的“Product Name”

默认情况下,如果没有外部导入的“AndroidMainfest.xml”文件,则使用Unity打包apk后安装在真机上显示出来的则是PlayerSetting中设置的“Product Name”

如此安卓端的所有操作都完成了

?二、Unity端:

在Unity中新建项目,创建“Plugins/Android”文件夹,将安卓端最后得到的两个文件导入进来:

在场景中新建GameObject “VoiceController",然后创建脚本“GameMgr.cs”,挂载在该“VoiceController”物体上:

编写脚本?GameMgr.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class GameMgr : MonoBehaviour
{
    private AndroidJavaObject jo;

    //用于测试连通性
    public Text testConnectText1, testConnectText2;
    public Button testConnectedBtn;

    public Button voiceBtn;
    public Text permissionText, voiceResultText;

    // Start is called before the first frame update
    void Start()
    {
        AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        jo = jc.GetStatic<AndroidJavaObject>("currentActivity");

        testConnectedBtn.onClick.AddListener(UnityAndAndroidTestConnect);   //测试连通性

        voiceBtn.onClick.AddListener(StarVoiceDetect);
    }

    public void StarVoiceDetect()
    {
        jo.Call("beginListen");
        
    }
    
    //权限获取结果
    public void GetPermissionMsgFromAndroid(string msg)
    {
        permissionText.text = msg;
    }

    public void GetVoiceResultFromAndroid(string msg)
    {
        voiceResultText.text = msg;
    }


    #region 测试Unity和Android是否连接成功
    public void UnityAndAndroidTestConnect()
    {
        testConnectText1.text = jo.Call<int>("beginTest", 222, 444).ToString();
        jo.Call("connected", "Hello, today is a good day.");
    }

    public void TestConnectResult(string msg)
    {
        testConnectText2.text = msg;
    }
    #endregion
}

设置包名与aar外部的AndroidMainfest.xml中的包名一致,并根据aar项目的最低sdk版本设置“Minimum API Level”:

由于AS中的项目最低API版本为API-23,因此这里同样设置为“API level 23”。然后开始打包

打包完成后的apk在真机上运行效果如下:

PS:

打包时使用的SDK,NDK等文件下载链接:

SDK:?Unity_android_sdk.zip-Unity3D文档类资源-CSDN下载

NDK:?https://download.csdn.net/download/m0_47975736/33254055

源码工程地址:

包含AndroidStudio和Unity端的项目源码以及可以直接运行的APK文件:

https://download.csdn.net/download/m0_47975736/33253823

如此,在Unity接入科大讯飞语音SDK 安卓端就完成了。

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-10-20 12:48:49  更:2021-10-20 12:49:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/28 0:34:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码