【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:

     上一篇跟各位童鞋介绍了SharedPreference 和 File流如何存储数据,并且推荐使用FileOutputStream/FileInputStream来存储咱们游戏数据,那么这一篇则是像大家介绍另外一种适合游戏数据存储的方式:SQLite 轻量级数据库!

     先介绍几个基本概念知识: 

什么是SQLite:

    SQLite是一款轻量级数据库,它的设计目的是嵌入式,而且它占用的资源非常少,在嵌入式设备中,只需要几百KB!!!!! 

SQLite的特性: 

  • 轻量级
    使用 SQLite 只需要带一个动态库,就可以享受它的全部功能,而且那个动态库的尺寸想当小。
  • 独立性
    SQLite 数据库的核心引擎不需要依赖第三方软件,也不需要所谓的“安装”。
  • 隔离性
    SQLite 数据库中所有的信息(比如表、视图、触发器等)都包含在一个文件夹内,方便管理和维护。
  • 跨平台
    SQLite 目前支持大部分操作系统,不至电脑操作系统更在众多的手机系统也是能够运行,比如:Android。
  • 多语言接口
    SQLite 数据库支持多语言编程接口。
  • 安全性
    SQLite 数据库通过数据库级上的独占性和共享锁来实现独立事务处理。这意味着多个进程可以在同一时间从同一数据库读取数据,但只能有一个可以写入数据.

优点:1.能存储较多的数据。

2.能将数据库文件存放到SD卡中! 

什么是 SQLiteDatabase?

    一个 SQLiteDatabase 的实例代表了一个SQLite 的数据库,通过SQLiteDatabase 实例的一些方法,我们可以执行SQL 语句,对数 据库进行增、删、查、改的操作。需要注意的是,数据库对于一个应用来说是私有的,并且在一个应用当中,数据库的名字也是惟一的。

 什么是 SQLiteOpenHelper ?

    根据这名字,我们可以看出这个类是一个辅助类。这个类主要生成一个数据库,并对数据库的版本进行管理。当在程序当中调用这个类的 方法getWritableDatabase(),或者getReadableDatabase()方法的时候,如果当时没有数据,那么Android 系统就会自动生成一 个数 据库。SQLiteOpenHelper 是一个抽象类,我们通常需要继承它,并且实现里边的3 个函数,

 什么是 ContentValues 类?

    ContentValues 类和Hashmap/Hashtable 比较类似,它也是负责存储一些名值对,但是它存储的名值对当中的名是一个String 类型,而值都是基本类型。

 什么是 Cursor ?

    Cursor 在Android 当中是一个非常有用的接口,通过Cursor 我们可以对从数据库查询出来的结果集进行随 机的读写访问。

     OK,基本知识就介绍到这里,下面开始上代码:还是按照我的一贯风格,代码中该解释的地方都已经在代码中及时注释和讲解了!

顺便来张项目截图: 

 先给出xml: 

 
 
  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  3.     android:orientation="vertical" android:layout_width="fill_parent" 
  4.     android:layout_height="fill_parent"> 
  5.     <TextView android:layout_width="fill_parent" 
  6.         android:layout_height="wrap_content" android:text="SQL 练习!(如果你使用的SD卡存储数据方式,为了保证正常操作,请你先点击创建一张表然后再操作)" 
  7.         android:textSize="20sp" android:textColor="#ff0000" android:id="@+id/tv_title" /> 
  8.     <Button android:id="@+id/sql_addOne" android:layout_width="fill_parent" 
  9.         android:layout_height="wrap_content" android:text="插入一条记录"></Button> 
  10.     <Button android:id="@+id/sql_check" android:layout_width="fill_parent"   
  11.         android:layout_height="wrap_content" android:text="查询数据库"></Button> 
  12.     <Button android:id="@+id/sql_edit" android:layout_width="fill_parent" 
  13.         android:layout_height="wrap_content" android:text="修改一条记录"></Button> 
  14.     <Button android:id="@+id/sql_deleteOne" android:layout_width="fill_parent" 
  15.         android:layout_height="wrap_content" android:text="删除一条记录"></Button> 
  16.     <Button android:id="@+id/sql_deleteTable" android:layout_width="fill_parent" 
  17.         android:layout_height="wrap_content" android:text="删除数据表单"></Button> 
  18.     <Button android:id="@+id/sql_newTable" android:layout_width="fill_parent" 
  19.         android:layout_height="wrap_content" android:text="新建数据表单"></Button> 
  20. </LinearLayout> 

      xml中定义了我们需要练习用到的几个操作按钮,这里不多解释了,下面看java源码:先看我们继承的 SQLiteOpenHelper 类 

 
 
  1. package com.himi;  
  2. import android.content.Context;  
  3. import android.database.sqlite.SQLiteDatabase;  
  4. import android.database.sqlite.SQLiteOpenHelper;  
  5. import android.util.Log;  
  6. /**  
  7.  *   
  8.  * @author Himi  
  9.  * @解释 此类我们只需要传建一个构造函数 以及重写两个方法就OK啦、  
  10.  *   
  11.  */  
  12. public class MySQLiteOpenHelper extends SQLiteOpenHelper {  
  13.     public final static int VERSION = 1;// 版本号  
  14.     public final static String TABLE_NAME = "himi";// 表名  
  15.     public final static String ID = "id";// 后面ContentProvider使用  
  16.     public final static String TEXT = "text";  
  17.     public static final String DATABASE_NAME = "Himi.db";  
  18.     public MySQLiteOpenHelper(Context context) {  
  19.         // 在Android 中创建和打开一个数据库都可以使用openOrCreateDatabase 方法来实现,  
  20.         // 因为它会自动去检测是否存在这个数据库,如果存在则打开,不过不存在则创建一个数据库;  
  21.         // 创建成功则返回一个 SQLiteDatabase对象,否则抛出异常FileNotFoundException。  
  22.         // 下面是来创建一个名为"DATABASE_NAME"的数据库,并返回一个SQLiteDatabase对象   
  23.           
  24.         super(context, DATABASE_NAME, null, VERSION);   
  25.     }   
  26.     @Override  
  27.     // 在数据库第一次生成的时候会调用这个方法,一般我们在这个方法里边生成数据库表;  
  28.     public void onCreate(SQLiteDatabase db) {   
  29.         String str_sql = "CREATE TABLE " + TABLE_NAME + "(" + ID  
  30.                 + " INTEGER PRIMARY KEY AUTOINCREMENT," + TEXT + " text );";  
  31.         // CREATE TABLE 创建一张表 然后后面是我们的表名  
  32.         // 然后表的列,第一个是id 方便操作数据,int类型  
  33.         // PRIMARY KEY 是指主键 这是一个int型,用于唯一的标识一行;  
  34.         // AUTOINCREMENT 表示数据库会为每条记录的key加一,确保记录的唯一性;  
  35.         // 最后我加入一列文本 String类型  
  36.         // ----------注意:这里str_sql是sql语句,类似dos命令,要注意空格!  
  37.         db.execSQL(str_sql);  
  38.         // execSQL()方法是执行一句sql语句  
  39.         // 虽然此句我们生成了一张数据库表和包含该表的sql.himi文件,  
  40.         // 但是要注意 不是方法是创建,是传入的一句str_sql这句sql语句表示创建!!  
  41.     }  
  42.     @Override  
  43.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  44.         // 一般默认情况下,当我们插入 数据库就立即更新  
  45.         // 当数据库需要升级的时候,Android 系统会主动的调用这个方法。  
  46.         // 一般我们在这个方法里边删除数据表,并建立新的数据表,  
  47.         // 当然是否还需要做其他的操作,完全取决于游戏需求。  
  48.         Log.v("Himi", "onUpgrade");  
  49.     }   
  50. }   

      我喜欢代码中立即附上解释,感觉这样代码比较让大家更容易理解和寻找,当然如果童鞋们不喜欢,可以告诉我,我改~嘿嘿~

     下面看最重要的MainActivity中的代码: 

 
 
  1. package com.himi;  
  2. import java.io.File;  
  3. import java.io.IOException;  
  4. import android.app.Activity;  
  5. import android.content.ContentValues;  
  6. import android.database.Cursor;  
  7. import android.database.sqlite.SQLiteDatabase;  
  8. import android.os.Bundle;  
  9. import android.view.View;  
  10. import android.view.Window;  
  11. import android.view.WindowManager;  
  12. import android.view.View.OnClickListener;  
  13. import android.widget.Button;  
  14. import android.widget.TextView;  
  15. // ------------第三种保存方式--------《SQLite》---------  
  16. /**  
  17.  * @author Himi  
  18.  * @保存方式:SQLite 轻量级数据库、  
  19.  * @优点: 可以将自己的数据存储到文件系统或者数据库当中, 也可以将自己的数据存  
  20.  *         储到SQLite数据库当中,还可以存到SD卡中  
  21.  * @注意1:数据库对于一个游戏(一个应用)来说是私有的,并且在一个游戏当中,   
  22.  *         数据库的名字也是唯一的。  
  23.  * @注意2 apk中创建的数据库外部的进程是没有权限去读/写的,   
  24.  *         我们需要把数据库文件创建到sdcard上可以解决类似问题.  
  25.  * @注意3 当你删除id靠前的数据或者全部删除数据的时候,SQLite不会自动排序,  
  26.  *        也就是说再添加数据的时候你不指定id那么SQLite默认还是在原有id最后添加一条新数据  
  27.  * @注意4 android 中 的SQLite 语法大小写不敏感,也就是说不区分大小写;  
  28.  *     
  29.  */  
  30. public class MainActivity extends Activity implements OnClickListener {  
  31.     private Button btn_addOne, btn_deleteone, btn_check, btn_deleteTable,  
  32.             btn_edit, btn_newTable;  
  33.     private TextView tv;  
  34.     private MySQLiteOpenHelper myOpenHelper;// 创建一个继承SQLiteOpenHelper类实例  
  35.     private SQLiteDatabase mysql ;   
  36. //---------------以下两个成员变量是针对在SD卡中存储数据库文件使用  
  37. //  private File path = new File("/sdcard/himi");// 创建目录  
  38. //  private File f = new File("/sdcard/himi/himi.db");// 创建文件  
  39.     @Override  
  40.     public void onCreate(Bundle savedInstanceState) {  
  41.         super.onCreate(savedInstanceState);  
  42.         getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,  
  43.                 WindowManager.LayoutParams.FLAG_FULLSCREEN);  
  44.         this.requestWindowFeature(Window.FEATURE_NO_TITLE);  
  45.         setContentView(R.layout.main);  
  46.         tv = (TextView) findViewById(R.id.tv_title);  
  47.         btn_addOne = (Button) findViewById(R.id.sql_addOne);  
  48.         btn_check = (Button) findViewById(R.id.sql_check);  
  49.         btn_deleteone = (Button) findViewById(R.id.sql_deleteOne);  
  50.         btn_deleteTable = (Button) findViewById(R.id.sql_deleteTable);  
  51.         btn_newTable = (Button) findViewById(R.id.sql_newTable);  
  52.         btn_edit = (Button) findViewById(R.id.sql_edit);  
  53.         btn_edit.setOnClickListener(this);  
  54.         btn_addOne.setOnClickListener(this);  
  55.         btn_check.setOnClickListener(this);  
  56.         btn_deleteone.setOnClickListener(this);  
  57.         btn_deleteTable.setOnClickListener(this);  
  58.         btn_newTable.setOnClickListener(this);  
  59.         myOpenHelper = new MySQLiteOpenHelper(this);// 实例一个数据库辅助器  
  60. //备注1  ----如果你使用的是将数据库的文件创建在SD卡中,那么创建数据库mysql如下操作:  
  61. //      if (!path.exists()) {// 目录存在返回false  
  62. //          path.mkdirs();// 创建一个目录  
  63. //      }  
  64. //      if (!f.exists()) {// 文件存在返回false  
  65. //          try {  
  66. //              f.createNewFile();//创建文件   
  67. //          } catch (IOException e) {  
  68. //              // TODO Auto-generated catch block  
  69. //              e.printStackTrace();  
  70. //          }  
  71. //      }   
  72.     }  
  73.     @Override  
  74.     public void onClick(View v) {    
  75.         try {   
  76. //备注2----如果你使用的是将数据库的文件创建在SD卡中,那么创建数据库mysql如下操作:  
  77. //              mysql = SQLiteDatabase.openOrCreateDatabase(f, null);   
  78. //备注3--- 如果想把数据库文件默认放在系统中,那么创建数据库mysql如下操作:  
  79.                 mysql = myOpenHelper.getWritableDatabase(); // 实例数据库  
  80.             if (v == btn_addOne) {// 添加数据  
  81.                 // ---------------------- 读写句柄来插入---------  
  82.                 // ContentValues 其实就是一个哈希表HashMap, key值是字段名称,  
  83.                 //Value值是字段的值。然后 通过 ContentValues 的 put 方法就可以  
  84.                 //把数据放到ContentValues中,然后插入到表中去!  
  85.                 ContentValues cv = new ContentValues();  
  86.                 cv.put(MySQLiteOpenHelper.TEXT, "测试新的数据");  
  87.                 mysql.insert(MySQLiteOpenHelper.TABLE_NAME, null, cv);  
  88.                 // inser() 第一个参数 标识需要插入操作的表名  
  89.                 // 第二个参数 :默认传null即可  
  90.                 // 第三个是插入的数据  
  91.                 // ---------------------- SQL语句插入--------------  
  92.                 // String INSERT_DATA =  
  93.                 // "INSERT INTO himi (id,text) values (1, '通过SQL语句插入')";  
  94.                 // db.execSQL(INSERT_DATA);  
  95.                 tv.setText("添加数据成功!点击查看数据库查询");  
  96.             } else if (v == btn_deleteone) {// 删除数据  
  97.                 // ---------------------- 读写句柄来删除  
  98.                 mysql.delete("himi", MySQLiteOpenHelper.ID + "=1", null);  
  99.                 // 第一个参数 需要操作的表名  
  100.                 // 第二个参数为 id+操作的下标 如果这里我们传入null,表示全部删除  
  101.                 // 第三个参数默认传null即可  
  102.                 // ----------------------- SQL语句来删除  
  103.                 // String DELETE_DATA = "DELETE FROM himi WHERE id=1";  
  104.                 // db.execSQL(DELETE_DATA);  
  105.                 tv.setText("删除数据成功!点击查看数据库查询");  
  106.             } else if (v == btn_check) {// 遍历数据  
  107. //备注4------  
  108.                 Cursor cur = mysql.rawQuery("SELECT * FROM "  
  109.                         + MySQLiteOpenHelper.TABLE_NAME, null);  
  110.                 if (cur != null) {  
  111.                     String temp = "";  
  112.                     int i = 0;  
  113.                     while (cur.moveToNext()) {//直到返回false说明表中到了数据末尾  
  114.                         temp += cur.getString(0);   
  115.                         // 参数0 指的是列的下标,这里的0指的是id列  
  116.                         temp += cur.getString(1);  
  117.                         // 这里的0相对于当前应该是咱们的text列了  
  118.                         i++;  
  119.                         temp += "  "; // 这里是我整理显示格式 ,呵呵~  
  120.                         if (i % 3 == 0) // 这里是我整理显示格式 ,呵呵~  
  121.                             temp += "/n";// 这里是我整理显示格式 ,呵呵~  
  122.                     }  
  123.                     tv.setText(temp);  
  124.                 }  
  125.             } else if (v == btn_edit) {// 修改数据  
  126.                 // ------------------------句柄方式来修改 -------------  
  127.                 ContentValues cv = new ContentValues();  
  128.                 cv.put(MySQLiteOpenHelper.TEXT, "修改后的数据");  
  129.                 mysql.update("himi", cv, "id " + "=" + Integer.toString(3), null);  
  130.                 // ------------------------SQL语句来修改 -------------  
  131.                 // String UPDATA_DATA =  
  132.                 // "UPDATE himi SET text='通过SQL语句来修改数据'  WHERE id=1";  
  133.                 // db.execSQL(UPDATA_DATA);  
  134.                 tv.setText("修改数据成功!点击查看数据库查询");  
  135.             } else if (v == btn_deleteTable) {// 删除表  
  136.                 mysql.execSQL("DROP TABLE himi");  
  137.                 tv.setText("删除表成功!点击查看数据库查询");  
  138.             } else if (v == btn_newTable) {// 新建表  
  139.                 String TABLE_NAME = "himi";  
  140.                 String ID = "id";  
  141.                 String TEXT = "text";  
  142.                 String str_sql2 = "CREATE TABLE " + TABLE_NAME + "(" + ID  
  143.                         + " INTEGER PRIMARY KEY AUTOINCREMENT," + TEXT  
  144.                         + " text );";  
  145.                 mysql.execSQL(str_sql2);  
  146.                 tv.setText("新建表成功!点击查看数据库查询");  
  147.             }  
  148.             // 删除数据库:  
  149.             // this.deleteDatabase("himi.db");  
  150.         } catch (Exception e) {  
  151.             tv.setText("操作失败!");  
  152.         } finally {// 如果try中异常,也要对数据库进行关闭  
  153.             mysql.close();  
  154.         }  
  155.     }  

      以上代码中我们实现了两种存储方式:

    一种存储默认系统路径/data-data-com.himi-databases下,另外一种则是保存在了/sdcard-himi下,生成数据库文件himi.db

     那么这里两种实现方式大概步骤和区别说下: 

-----------如果我们使用默认系统路径存储数据库文件:

    第一步:新建一个类继承SQLiteOpenHelper;写一个构造,重写两个函数!

    第二步:在新建的类中的onCreate(SQLiteDatabase db) 方法中创建一个表;

    第三步:在进行删除数据、添加数据等操作的之前我们要得到数据库读写句柄得到一个数据库实例;

    注意: 继承写这个辅助类,是为了在我们没有数据库的时候自动为我们生成一个数据库,并且生成数据库文件,这里也同时创建了一张表,因为我们在onCreate里是在数据库中创建一张表的操作;这里还要注意在我们new 这个我们这个MySQLiteOpenHelper 类实例对象的时候并没有创建数据库哟~!而是在我们调用 (备注3)MySQLiteOpenHelper ..getWritableDatabase() 这个方法得到数据库读写句柄的时候,android 会分析是否已经有了数据库,如果没有会默认为我们创建一个数据库并且在系统路径data-data-com.himi-databases下生成himi.db 文件!(如果我们使用sd卡存储数据库文件,就没有必要写这个类了,而是我们自己Open自己的文件得到一个数据库,西西,反而方便~ )

 -----------如果我们需要把数据库文件存储到SD卡中:

    第一步:确认模拟器存在SD卡,关于SD卡的两种创建方法见我的博文:【Android 2D游戏开发之十】

    第二步:(备注1)先创建SD卡目录和路径已经我们的数据库文件!这里不像上面默认路径中的那样,如果没有数据库会默认系统路径生成一个数据库和一个数据库文件!我们必须手动创建数据库文件!

    第三步:在进行删除数据、添加数据等操作的之前我们要得到数据库读写句柄得到一个数据库实例;(备注2)此时的创建也不是像系统默认创建,而是我们通过打开第一步创建好的文件得到数据库实例。这里仅仅是创建一个数据库!!!!

    第四步:在进行删除数据、添加数据等操作的之前我们还要创建一个表!

    第五步:在配置文件AndroidMainfest.xml 声明写入SD卡的权限,上一篇已经介绍权限了,不知道的自己去看下吧。

    有些童鞋不理解什么默认路径方式中就有表?那是因为我们在它默认给我们创建数据库的时候我们有创建表的操作,就是MySQLiteOpenHelper类中的onCreate()方法里的操作!所以我们如果要在进行删除数据、添加数据等操作的之前还要创建一个表,创建表的方法都是一样的。 

    总结:不管哪种方式我们都要-创建数据库-创建表-然后进行操作! 

备注4:

    在Android中查询数据是通过Cursor类来实现的,当我们使用SQLiteDatabase.query()方法时,会得到一个Cursor对象,Cursor指向的就是每一条数据。它提供了很多有关查询的方法,具体方法如下: 

以下是方法和说明: 

move 以当前的位置为参考,将Cursor移动到指定的位置,成功返回true, 失败返回false 

moveToPosition 将Cursor移动到指定的位置,成功返回true,失败返回false 

moveToNext 将Cursor向前移动一个位置,成功返回true,失败返回false 

moveToLast 将Cursor向后移动一个位置,成功返回true,失败返回 false。 

movetoFirst 将Cursor移动到第一行,成功返回true,失败返回false 

isBeforeFirst 返回Cursor是否指向第一项数据之前 

isAfterLast 返回Cursor是否指向最后一项数据之后 

isClosed 返回Cursor是否关闭 

isFirst 返回Cursor是否指向第一项数据 

isLast 返回Cursor是否指向最后一项数据 

isNull 返回指定位置的值是否为null

 getCount 返回总的数据项数

 getInt 返回当前行中指定的索引数据

     对于SQLite的很多童鞋有接触过,但是就不知道怎么存储在SD中,所以我也研究了下,这篇也写了把sd卡中的方式也提供给大家。

     OK 这元旦放假几天就光给大家写这个了,呵呵~凌晨2点了,咳咳~该睡觉了。(我一般凌成3点睡觉,早8点起来上班 呵呵~习惯了~)









本文转自 xiaominghimi 51CTO博客,原文链接:http://blog.51cto.com/xiaominghimi/606759,如需转载请自行联系原作者
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
3月前
|
安全 API Android开发
Android网络和数据交互: 解释Retrofit库的作用。
Android网络和数据交互: 解释Retrofit库的作用。
38 0
|
3月前
|
算法 Java 定位技术
分享104个益智休闲安卓游戏源码,总有一款适合你
分享104个益智休闲安卓游戏源码,总有一款适合你
144 1
|
3天前
|
Android开发 开发者
Android网络和数据交互: 请解释Android中的AsyncTask的作用。
Android&#39;s AsyncTask simplifies asynchronous tasks for brief background work, bridging UI and worker threads. It involves execute() for starting tasks, doInBackground() for background execution, publishProgress() for progress updates, and onPostExecute() for returning results to the main thread.
3 0
|
3天前
|
网络协议 安全 API
Android网络和数据交互: 什么是HTTP和HTTPS?在Android中如何进行网络请求?
HTTP和HTTPS是网络数据传输协议,HTTP基于TCP/IP,简单快速,HTTPS则是加密的HTTP,确保数据安全。在Android中,过去常用HttpURLConnection和HttpClient,但HttpClient自Android 6.0起被移除。现在推荐使用支持TLS、流式上传下载、超时配置等特性的HttpsURLConnection进行网络请求。
5 0
|
17天前
|
XML Java Android开发
Android每点击一次按钮就添加一条数据
Android每点击一次按钮就添加一条数据
21 1
|
1月前
|
存储 Android开发 C++
【Android 从入门到出门】第五章:使用DataStore存储数据和测试
【Android 从入门到出门】第五章:使用DataStore存储数据和测试
30 3
|
2月前
|
JavaScript Java 数据安全/隐私保护
安卓逆向 -- POST数据解密
安卓逆向 -- POST数据解密
25 2
|
3月前
|
编解码 移动开发 人工智能
android游戏源码
android游戏源码
76 0
|
3月前
|
编解码 测试技术 开发工具
如何实现Android视音频数据对接到GB28181平台(SmartGBD)
如何实现Android视音频数据对接到GB28181平台(SmartGBD)
|
3月前
|
数据采集 编解码 图形学
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务