安卓Android.nfc读卡

使用安卓手机,通过NFC功能可以读取IC卡信息,。
IC卡有很多种卡标准,协议,等等,这些具体就不细讨论。
主要讨论,2种卡,一种是M1卡,另外是CPU卡。

1、 M1卡

M1卡是市面上比较常见的卡,一般的门禁卡。比较便宜。
M1卡的数据存储比较通用的是分扇区存储数据。默认是16个扇区,每个扇区4个数据块。
每个卡片都有一个ID号码(去买很多的卡的时候,可以要求厂商提供给你的每个卡ID是唯一的。)
0扇区的数据,是厂商写死的数据,包含卡面的ID号。
其他扇区的数据,可以自己去写,可以加密写。(密码为12位)

2、 CPU卡

CPU卡是加密卡,CPU卡还分为双界面卡和单界面卡。比较复杂。此次只说明,CPU卡的数据,通常是通过指令来读取数据。

3、android 的NFC功能

当安卓手机的NFC功能感应到 卡片的时候。会读取出该卡片支持的数据传输方式

android.nfc.tech.NfcA
android.nfc.tech.NfcB
android.nfc.tech.MifareClassic
android.nfc.tech.IsoDep

1、 普通的M1卡,会支持好几种数据传输方式,一般会有NfcA,MifareClassic等。如果支持MifareClassic,那就优先使用这种数据传输方式,获取数据。
2、 CPU卡的数据传输,会支持NfcA,IsoDep等,优先使用IsoDep
3、现在市面上出现了云解身份证,原理是通过NFC读取身份证的加密信息,通过API调用公安接口返回用户的证件信息。身份证通过NFC读取,只能通过NFCB的模式读取数据。

安卓开启NFC功能,一定先开启权限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.Smkpda"tools:targetApi="31"><activityandroid:name=".MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application><!--NFC权限--><uses-permission android:name="android.permission.NFC" /><!-- 要求当前设备必须要有NFC芯片 --><uses-feature android:name="android.hardware.nfc" android:required="true" />
</manifest>
4、定义一个单例的NfcUtils 工具类
package com.sss.ssspda.common.nfc;import android.app.Activity;
import android.content.Context;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.util.Log;
import android.widget.Toast;/*** Created by Administrator on 2023/11/10.*/public class NfcUtils {private static final String TAG = "NfcUtils";private static NfcAdapter mNfcAdapter;//NFC功能,只能定义为单例模式,避免出现系统奔溃private NfcUtils(){}private static NfcUtils nfcUtils = null;private static boolean isOpen = false;/*** 获取NFC的单例* @return NfcUtils*/public static NfcUtils getInstance(){if (nfcUtils == null){synchronized (NfcUtils.class){if (nfcUtils == null){nfcUtils = new NfcUtils();}}}return nfcUtils;}/*** 在onStart中检测是否支持nfc功能* @param context 当前页面上下文*/public void onStartNfcAdapter(Context context){mNfcAdapter = NfcAdapter.getDefaultAdapter(context);//设备的NfcAdapter对象if(mNfcAdapter==null){//判断设备是否支持NFC功能Toast.makeText(context,"设备不支持NFC功能!",Toast.LENGTH_SHORT).show();return;}if (!mNfcAdapter.isEnabled()){//判断设备NFC功能是否打开Toast.makeText(context,"请到系统设置中打开NFC功能!",Toast.LENGTH_SHORT).show();return;}Log.d(TAG,"NFC is start");}/*** 在onResume中开启nfc功能* @param activity*/public void onResumeNfcAdapter(final Activity activity){if (mNfcAdapter == null  || !mNfcAdapter.isEnabled()) {throw new RuntimeException("NFC is not  start");}
//            mNfcAdapter.enableForegroundDispatch(this,mPendingIntent,null,null);//打开前台发布系统,使页面优于其它nfc处理.当检测到一个Tag标签就会执行mPendingItentif (!isOpen) {mNfcAdapter.enableReaderMode(activity, new NfcAdapter.ReaderCallback() {@Overridepublic void onTagDiscovered(final Tag tag) {//ByteArrayToHexString(tag.getId())即cardIdif (nfcListener != null)(activity).runOnUiThread(new Runnable() {@Overridepublic void run() {nfcListener.doing(tag);}});}},(NfcAdapter.FLAG_READER_NFC_A |NfcAdapter.FLAG_READER_NFC_B |NfcAdapter.FLAG_READER_NFC_F |NfcAdapter.FLAG_READER_NFC_V |NfcAdapter.FLAG_READER_NFC_BARCODE ),null);isOpen = true;Log.d(TAG, "Resume");}}/*** 在onPause中关闭nfc功能* @param activity*/public void onPauseNfcAdapter(Activity activity){if(mNfcAdapter!=null && mNfcAdapter.isEnabled()){if (isOpen){mNfcAdapter.disableReaderMode(activity);}isOpen = false;}Log.d("myNFC","onPause");}private  NfcListener nfcListener;public void setNfcListener(NfcListener listener){nfcListener = listener;}public String ByteArrayToHexString(byte[] inarray) {int i, j, in;String[] hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A","B", "C", "D", "E", "F"};String out = "";for (j = 0; j < inarray.length; ++j) {in = (int) inarray[j] & 0xff;i = (in >> 4) & 0x0f;out += hex[i];i = in & 0x0f;out += hex[i];}return out;}
}
定义一个处理读卡事件的接口类

NfcListener

package com.sss.ssspda.common.nfc;import android.nfc.Tag;/*** 自定义的NFC接口*/public interface NfcListener{/*** 用于扫到nfc后的后续操作*/void doing(Tag tag);}
实现获取卡片数据交互
package com.sss.ssspda.common.nfc;import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.NfcB;
import android.widget.Toast;import java.io.IOException;public class NfcReadHander {/*** 读M1卡的方法扇区,MifareClassic 类型* @return**/public static String readMifareTag(Tag tag, MifareClassic mfc){//读取TAGtry {String metaInfo = "";//操作之前,一点要先连接卡片通讯mfc.connect();int type = mfc.getType();//获取TAG的类型int sectorCount = mfc.getSectorCount();//获取TAG中包含的扇区数String typeS = "";switch (type) {case MifareClassic.TYPE_CLASSIC:typeS = "TYPE_CLASSIC";break;case MifareClassic.TYPE_PLUS:typeS = "TYPE_PLUS";break;case MifareClassic.TYPE_PRO:typeS = "TYPE_PRO";break;case MifareClassic.TYPE_UNKNOWN:typeS = "TYPE_UNKNOWN";break;}metaInfo += "卡片类型:" + typeS + "\n共" + sectorCount + "个扇区\n共" 	+ mfc.getBlockCount() + "个块\n存储空间: " + mfc.getSize() + "B\n";System.out.println(metaInfo);int blockIndex;for(int i =0;i<16;i++){try{System.out.println(i);metaInfo += "扇区:"+i+",key:";boolean f = false;byte[] keybyte = new byte[]{0x01, (byte) 0x11, (byte) 0x18,0x3F,0x12, (byte) 0x11};//样例的一个密码,要自己去获取,卡片商获取//每个扇区的数据读取都可能不一样,每个扇区有的有数据,有的是加密数据,有的是空数据//没加密扇区都会配置一个默认密码,开发包里面默认带有3种默认密码,//这里代码只列出来使用A加密的方法,也有B加密方法authenticateSectorWithKeyB,要根据实际卡片加密方法来设置if(mfc.authenticateSectorWithKeyA(i,MifareClassic.KEY_DEFAULT)){metaInfo += "KEY_DEFAULT";f = true;}else if(mfc.authenticateSectorWithKeyA(i,MifareClassic.KEY_NFC_FORUM)){metaInfo += ".KEY_NFC_FORUM";f = true;}else if(mfc.authenticateSectorWithKeyA(i,MifareClassic.KEY_MIFARE_APPLICATION_DIRECTORY)){metaInfo += ".KEY_MIFARE_APPLICATION_DIRECTORY";f = true;}else if(mfc.authenticateSectorWithKeyA(i,keybyte)){//这里的密码 keybyte,要自己和卡片提供商来获取,有可能是一个批次的卡片的密码都一样,也可能每个卡片的密码都不一样,这里的密码需要自己想办法获取。metaInfo += ByteArrayToHexString(keybyte);f = true;}else{}metaInfo += "\n";if(f){//前面通过扇区界面进入了扇区里面,这里就是计算当前扇区的第一个块的块号码int bnum = mfc.sectorToBlock(i);//通过块号码读取块里面的数据metaInfo += ByteArrayToHexString(mfc.readBlock(bnum))+"\n";//每个扇区有4个块,依次读取metaInfo +=ByteArrayToHexString(mfc.readBlock(bnum+1))+"\n";metaInfo +=ByteArrayToHexString(mfc.readBlock(bnum+2))+"\n";metaInfo +=ByteArrayToHexString(mfc.readBlock(bnum+3))+"\n";}}catch (Exception e){e.printStackTrace();}}return metaInfo;} catch (Exception e) {e.printStackTrace();} finally {if (mfc != null) {try {mfc.close();} catch (IOException e) {e.printStackTrace();}}}return null;}public static String ByteArrayToHexString(byte[] inarray) {int i, j, in;String[] hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A","B", "C", "D", "E", "F"};String out = "";for (j = 0; j < inarray.length; ++j) {in = (int) inarray[j] & 0xff;i = (in >> 4) & 0x0f;out += hex[i];i = in & 0x0f;out += hex[i];}return out;}//找卡片提供商获取的读卡指令private static byte[] sheb = new  byte[]{0x00, (byte) 0x14,0x04,0x00,0x0F,0x13,0x18,0x11,0x1E,0x13,0x18,0x2E,(byte) 0x19,(byte) 0xE1,(byte) 0xB1,(byte) 0xE1,(byte) 0x11,(byte) 0x13,(byte) 0x15,(byte) 0xCF };//找卡片提供商获取的读卡指令private static byte[] JIAOTONG = new  byte[]{0x00, (byte) 0x14, (byte) 0x04, (byte) 0x00, (byte) 0x18, (byte) 0x10, (byte) 0x10, (byte) 0x00, (byte) 0x06, (byte) 0x12, (byte) 0x11, (byte) 0x11, (byte) 0x15};//通过ISODEP读取数据 public static String readIsoDepTag(Tag tag, IsoDep isodep) {String msg = "";try{//先连接isodep.connect();if(isodep.isConnected()){//判断是否链接成功}msg += "sheb指令读取的数据:\n";msg += transcMsg(isodep,sheb)+"\n";msg += "JIAOTONG指令读取的数据:\n";msg += transcMsg(isodep,JIAOTONG)+"\n";}catch (Exception e){}return msg;}public static String transcMsg(IsoDep isodep,byte[] b) throws  Exception{return ByteArrayToHexString(isodep.transceive(b));}//nfcB类型,身份证就是此种传输方式public static String readNfcBTag(Tag tag, NfcB nfcb) {String msg = "";try{nfcb.connect();msg += ByteArrayToHexString(nfcb.getApplicationData()) +"\n";byte[]  sd = new byte[]{0x05,0x00,0x00};msg += ByteArrayToHexString(nfcb.transceive(sd)) +"\n";msg += ByteArrayToHexString(nfcb.getApplicationData()) +"\n";}catch (Exception e){e.printStackTrace();}return msg;}
}
调用主类
package com.sss.ssspda;import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.NfcA;
import android.nfc.tech.NfcB;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;import com.sss.ssspda.common.nfc.NfcListener;
import com.sss.ssspda.common.nfc.NfcReadHander;
import com.sss.ssspda.common.nfc.NfcUtils;public class MainActivity extends AppCompatActivity  implements NfcListener {private   TextView typetext;private   TextView datatext;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);return insets;});typetext = findViewById(R.id.typenfc);datatext = findViewById(R.id.data);nfcUtils.setNfcListener(this);Log.d("myNFC","onPause");}public void Welcome1(View view) {Toast.makeText(this, "按钮点击一下", Toast.LENGTH_SHORT).show();}public void Welcome2(View view) {Toast.makeText(this, "撮了一下", Toast.LENGTH_SHORT).show();}private NfcUtils nfcUtils = NfcUtils.getInstance();private TextView textView;@Overrideprotected void onStart() {super.onStart();System.out.println("onStart................................");nfcUtils.onStartNfcAdapter(this);       //初始化Nfc对象}@Overrideprotected void onResume() {super.onResume();System.out.println("onResume................................");nfcUtils.onResumeNfcAdapter(this);      //activity激活的时候开始扫描}@Overrideprotected void onPause() {super.onPause();System.out.println("onPause................................");nfcUtils.onPauseNfcAdapter(this);       //activity切换到后台的时候停止扫描}@Overridepublic void doing(Tag tag) {String tl[] = tag.getTechList();for (String s:tl) {System.out.println(s);}for (String s:tl) {System.out.println("type:"+s);if(s.equals("android.nfc.tech.MifareClassic")){typetext.setText("MifareClassic");String data = NfcReadHander.readMifareTag(tag,MifareClassic.get(tag));datatext.setText(data);break;}else  if(s.equals("android.nfc.tech.IsoDep")){typetext.setText("IsoDep");String data = NfcReadHander.readIsoDepTag(tag, IsoDep.get(tag));datatext.setText(data);break;}else  if(s.equals("android.nfc.tech.NfcB")){typetext.setText("NfcB");String data = NfcReadHander.readNfcBTag(tag, NfcB.get(tag));datatext.setText(data);break;}}System.out.println("onPause................................"+NfcReadHander.ByteArrayToHexString(tag.getId()));}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="900sp"android:onClick="Welcome1"tools:context=".MainActivity"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="1054dp"android:orientation="vertical"app:layout_constraintTop_toTopOf="parent"><TextViewandroid:id="@+id/textView1"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="15dp"android:layout_marginTop="15dp"android:layout_marginRight="15dp"android:layout_marginBottom="15dp"android:background="#77CCB3"android:text="读取类型"android:textAlignment="center"android:textSize="35sp" /><TextViewandroid:id="@+id/typenfc"android:layout_width="match_parent"android:layout_height="77dp"android:text="type"android:textSize="25sp" /><TextViewandroid:id="@+id/textView2"android:layout_width="match_parent"android:layout_height="59dp"android:background="#6DCFC6"android:text="数据"android:textAlignment="center"android:textSize="35sp" /><ScrollViewandroid:layout_width="match_parent"android:layout_height="613dp"><TextViewandroid:id="@+id/data"android:layout_width="match_parent"android:layout_height="match_parent"android:text="TextView"android:textSize="15sp" /></ScrollView></LinearLayout></androidx.constraintlayout.widget.ConstraintLayout>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/813730.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用Tess4J实现图片文字识别

利用Tess4J实现图片文字识别 前言 光学字符识别&#xff08;OCR&#xff09;技术允许计算机通过扫描仪、摄像头等设备来识别并转换印刷或手写文本的图像数据为可编辑的文本格式。Tess4J是一个优秀的Java库&#xff0c;提供了与Tesseract OCR引擎的集成&#xff0c;方便进行图片…

为什么在cmd中输入jupyter notebook会出现问题

C:\Users\REBECCA329>jupyter notebook jupyter 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 这是什么意思&#xff1f;这个说明在cmd中找不到jupyter notebook&#xff0c;说明没有添加到环境变量里去&#xff0c;在配置R内核的时候找不到&#xff…

cocos creator开发中遇到的问题和解决方案

前言 总结一下使用cocos开发遇到的坑&#xff0c;不定期更新。 问题汇总 代码修改Position坐标不生效 首先要通过打log或者断点排除下是不是逻辑上的问题&#xff0c;还有是不是有动画相关把位置修改了。我遇到的问题是坐标修改被widget组件覆盖了。 纹理压缩包体变大 co…

TSINGSEE青犀AI智能分析网关V4叉车载货出入库检测算法介绍及应用

随着物流行业的快速发展&#xff0c;叉车作为物流运输的重要设备&#xff0c;其安全性和效率性越来越受到人们的关注。然而&#xff0c;在实际操作中&#xff0c;由于人为因素和操作环境的复杂性&#xff0c;叉车事故时有发生&#xff0c;给企业和个人带来了巨大的损失。为了提…

一些错误的记录

Linux使用rz命令出现乱码&#xff1f; 使用下面的命令上传&#xff1a; rz -besz filename //从linux操作系统上下载文件&#xff08;夹&#xff09;到本地云服务器上的redis连接不上&#xff0c;本地的图形化界面 1.在云服务器控制台-防火墙开放6379端口(使用命令开放端口不…

MySQL-进阶篇-一条sql更新语句是如何执行的(redo log和binlog)

上一篇&#xff1a;一条sql查询语句是如何执行的 http://t.csdnimg.cn/nV3EY 摘自&#xff1a;林晓斌MySQL实战45讲——第二篇 更新语句的执行过程与上一篇查询流程相同&#xff0c;本篇简写。 但多了两个重要的日志模块&#xff1a;redo log&#xff08;重做日志&#xff0…

Java 中什么是同步集合什么是并发集合,同步集合与并发集合有什么区别

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Java 中的同步集合与并发集合有什么区别 同步集合适用于简单的并发场景,其中线程对集合的访问和修改不频繁。而并发集合则更适用于高并发场景,其…

CorelDRAW2024破解注册机授权码分享

CorelDRAW是一款由加拿大Corel公司开发的平面设计软件&#xff0c;主要用于矢量图形制作、排版和编辑。它以其强大的功能和用户友好的界面而广受欢迎&#xff0c;被广泛应用于各个领域&#xff0c;包括设计、广告、出版和印刷等。 CDR2017-2024全版本下载网盘汉化版链接: http…

js可视化爬取数据生成当前热点词汇图

功能 可以爬取到很多数据&#xff0c;并且生成当前的热点词汇图&#xff0c;词越大越热门&#xff08;词云图&#xff09; 这里以b站某个评论区的数据为例&#xff0c;爬取63448条数据生成这样的图片 让我们能够更加直观的看到当前的热点 git地址 可以直接使用&#xff0c;中文…

pyqt 标题栏设置

在PyQt中&#xff0c;可以通过QWidget或其子类&#xff08;如QMainWindow或QDialog&#xff09;的setWindowTitle()方法来设置窗口的标题栏。以下是一个简单的例子&#xff0c;展示了如何为应用程序的主窗口设置标题&#xff1a; import sys from PyQt5.QtWidgets import QApp…

如何通过多媒体设计迅速为科普展厅制作提供清晰的思路?

随着数字化信息时代的迅速发展&#xff0c;各类领域的知识内容变得愈发繁杂&#xff0c;于是为了针对性地向大众普及各方面知识&#xff0c;各地域都会选择建设科普展厅的方式来实现这一目的&#xff0c;并在当中运用先进的多媒体技术&#xff0c;用以加强公众对相关知识的理解…

使用 HBuilderX自动上传Uniapp 微信小程序代码

HBuilderX内置相关环境&#xff0c;开箱即用&#xff0c;无需配置nodejs。本文只介绍发布微信小程序的步骤。 1.下载和安装 HBuilderX hbuilder首页&#xff1a;https://www.dcloud.io/hbuilderx.html 下载hbuilder编辑器,选择对应的系统,Windows和mac正式版即可,下载后免安…

java实现简易计算器

题目描述&#xff1a; 给定一个包含正整数、加()、减(-)、乘(*)、除(/)的算数表达式(括号除外)&#xff0c;计算其结果。 表达式仅包含非负整数&#xff0c;&#xff0c; - &#xff0c;*&#xff0c;/ 四种运算符和空格 。 整数除法仅保留整数部分。 示例 1: 输入: "…

FNP preptool has not been run on this executable

pycharm导入arcgis pro的python运行程序后提示 我也看了很多解决方法&#xff0c;也重新安装过一遍&#xff0c;终于看到一个说法是你的arcgis pro是学习版所以才会有这个提示&#xff0c;不会影响输入 测试代码 import arcpy print(arcpy.GetInstallInfo()[Version])输出结果…

海康4G摄像机国标注册失败,接入国标28181视频监控平台失败的问题分析、排查、解决

目录 一、问题现象 &#xff08;一&#xff09;背景 &#xff08;二&#xff09;在平台端&#xff0c;设备显示在线&#xff0c;通道不能传到平台端 &#xff08;二&#xff09;直接把通道接入进来&#xff0c;查看是否&#xff0c;显示请求超时 二、问题分析 &#xff0…

【b站李同学的Lee】NodeJS+Gulp基础入门+实战

课程地址&#xff1a;【NodeJSGulp基础入门实战】 https://www.bilibili.com/video/BV1aE411n737/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 目录 1 Node 开发概述 1.1 为什么要学习服务器端开发基础 1.2 服务器端开发要做的事情 1.3 为什么…

【计算机毕业设计】企业销售人员培训——后附源码

&#x1f389;**欢迎来到琛哥的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 琛哥&#xff0c;一名来自世界500强的资深程序猿&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 琛哥在深度学习任务中展现出卓越的能力&a…

vue3中axios添加请求和响应的拦截器

本章主要是以记录为主。 在src创建一个utils文件夹&#xff0c;并在utils中创建一个request.js文件。 // 引入axios import axios from "axios"; // import qs from "qs"; // 创建axios实例 const instance axios.create(); // 请求拦截器 instance.int…

Windows下使用PanguVip实现浮动IP

在某些高可用场景下&#xff0c;我们往往需要使用浮动IP来进行实际访问的切换&#xff0c;比如为了保证Web应用的高可用&#xff0c;当主节点宕机后&#xff0c;我们将浮动IP切换到备节点&#xff0c;那么备节点就继续可以提供服务&#xff0c;在linux下我们可以使用keepalived…

opengl介绍和使用实例

OpenGL是一个开放的图形库&#xff0c;用于开发二维和三维图形应用程序。它提供了一组用于渲染图形的函数&#xff0c;使开发者能够在不同的平台上创建高性能的图形应用。 使用OpenGL需要以下步骤&#xff1a; 初始化OpenGL上下文&#xff1a;在应用程序中&#xff0c;首先需要…