当前位置: 首页 > ops >正文

Android 开发 - 数据共享(数据共享、内容提供者实现、动态权限申请)

一、数据共享

1、内容提供者
  • 内容提供者 ContentProvider 为 APP 存取内部数据提供统一的外部接口,让不同的应用之间得以共享数据
2、流程理解
  • Client APP 将用户的输入内容通过 ContentProvider 跨进程通信传递给 Server APP
3、数据访问
  • 利用 ContentProvider 只实现服务端 APP 的数据封装,如果客户端 APP 想访问对方的内部数据,就要通过内容解析器 ContentResolver 来访问
4、URI
  • URI(统一资源标识符 Universal Resource ldentifer)代表数据操作的地址,每一个ContentProvider 都会有唯一的地址
content://【authority】/【data path】/【id】
字段说明
content://通用前缀,表示该 URI 用于 ContentProvider 定位资源
authority用来确定具体提供资源的 ContentProvider
一般 authority 都由类的小写全称组成,以保证唯一性
data_path数据路径,用来确定请求的是哪个数据集
id数据编号,用来请求单条数据,如果是多条则忽略此字段

二、内容提供者实现

1、项目创建
  • 创建两个 module,分别为 server 和 client
2、server
(1)数据库操作类
  • UserDBHelper 类
package com.my.server.database;import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;public class UserDBHelper extends SQLiteOpenHelper {private static final String DB_NAME = "user.db";private static final int DB_VERSION = 1;public static final String TABLE_NAME = "user_info";private static UserDBHelper userDBHelper;private UserDBHelper(Context context) {super(context, DB_NAME, null, DB_VERSION);}// 使用单例模式获取数据库帮助器的唯一实例public static UserDBHelper getInstance(Context context) {if (userDBHelper == null) {userDBHelper = new UserDBHelper(context);}return userDBHelper;}@Overridepublic void onCreate(SQLiteDatabase db) {// 创建数据库,执行建表语句String sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +"user_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +"user_name VARCHAR NOT NULL," +"user_age INTEGER NOT NULL" +");";db.execSQL(sql);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
(2)资源类
  • UserInfoContent 类
package com.my.server.content;import android.net.Uri;public class UserInfoContent {public static final String AUTHORITIES = "com.my.server.provider.UserInfoProvider";public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIES + "/user");public static final String USER_ID = "user_id";public static final String USER_NAME = "user_name";public static final String USER_AGE = "user_age";
}
(3)内容提供者
  1. 创建内容提供者,右击包名 -> 【Other】 -> 【Content Provider】 -> 填写相关信息 -> 【Finish】
  • UserInfoProvider 类,默认
package com.my.server.provider;import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;public class UserInfoProvider extends ContentProvider {public UserInfoProvider() {}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {// Implement this to handle requests to delete one or more rows.throw new UnsupportedOperationException("Not yet implemented");}@Overridepublic String getType(Uri uri) {// TODO: Implement this to handle requests for the MIME type of the data// at the given URI.throw new UnsupportedOperationException("Not yet implemented");}@Overridepublic Uri insert(Uri uri, ContentValues values) {// TODO: Implement this to handle requests to insert a new row.throw new UnsupportedOperationException("Not yet implemented");}@Overridepublic boolean onCreate() {// TODO: Implement this to initialize your content provider on startup.return false;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {// TODO: Implement this to handle query requests from clients.throw new UnsupportedOperationException("Not yet implemented");}@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {// TODO: Implement this to handle requests to update one or more rows.throw new UnsupportedOperationException("Not yet implemented");}
}
  1. 在清单文件 AndroidManifest.xml 中配置 provider 组件
  • AndroidManifest.xml
<applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.AndroidDevelop"><!-- 将 authorities 设置为内容提供者的全类名 --><providerandroid:name=".provider.UserInfoProvider"android:authorities="com.my.server.provider.UserInfoProvider"android:enabled="true"android:exported="true"></provider>...</application>
  • UserInfoProvider 类
package com.my.server.provider;import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;import com.my.server.content.UserInfoContent;
import com.my.server.database.UserDBHelper;public class UserInfoProvider extends ContentProvider {private static final String TAG = UserInfoProvider.class.getSimpleName();private UserDBHelper userDBHelper;private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);private static final int USES = 1;static {URI_MATCHER.addURI(UserInfoContent.AUTHORITIES, "/user", USES);}@Overridepublic boolean onCreate() {Log.i(TAG, "-------------------- onCreate");userDBHelper = UserDBHelper.getInstance(getContext());// 返回 true 代表 provider 实例化成功,返回 false 代表失败return true;}@Overridepublic Uri insert(Uri uri, ContentValues values) {Log.i(TAG, "-------------------- insert");if (URI_MATCHER.match(uri) == USES) {SQLiteDatabase wd = userDBHelper.getWritableDatabase();wd.insert(UserDBHelper.TABLE_NAME, null, values);}return uri;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {Log.i(TAG, "-------------------- query");Cursor cursor = null;if (URI_MATCHER.match(uri) == USES) {SQLiteDatabase rd = userDBHelper.getReadableDatabase();cursor = rd.query(userDBHelper.TABLE_NAME, null, selection, selectionArgs,null, null, null);}return cursor;}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {int count = 0;if (URI_MATCHER.match(uri) == USES) {SQLiteDatabase wd = userDBHelper.getWritableDatabase();count = wd.delete(userDBHelper.TABLE_NAME, selection, selectionArgs);wd.close();}return count;}@Overridepublic String getType(Uri uri) {// TODO: Implement this to handle requests for the MIME type of the data// at the given URI.throw new UnsupportedOperationException("Not yet implemented");}@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {// TODO: Implement this to handle requests to update one or more rows.throw new UnsupportedOperationException("Not yet implemented");}
}
2、client
(1)清单文件
<!-- Android 11 要求实现声明需要访问的软件包 -->
<queries><package android:name="com.my.server"></package><!-- 也可以声明需要访问的 provider --><!-- <provider android:authorities="com.my.server.provider.UserInfoProvider"></provider> -->
</queries>
(2)实体类
  • User 类
package com.my.client.entity;public class User {public Integer id;public String name;public Integer age;public User() {}public User(String name, Integer age) {this.name = name;this.age = age;}public User(Integer id, String name, Integer age) {this.id = id;this.name = name;this.age = age;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", age=" + age +'}';}
}
(3)资源类
  • UserInfoContent
package com.my.client.content;import android.net.Uri;public class UserInfoContent {public static final String AUTHORITIES = "com.my.server.provider.UserInfoProvider";public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIES + "/user");public static final String USER_ID = "user_id";public static final String USER_NAME = "user_name";public static final String USER_AGE = "user_age";
}
(4)XML 文件
  • activity_client_input.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="5dp"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="20dp"android:text="姓名:" /><EditTextandroid:id="@+id/et_name"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="5dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="20dp"android:text="年龄:" /><EditTextandroid:id="@+id/et_age"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content" /></LinearLayout><Buttonandroid:id="@+id/btn_insert"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="5dp"android:text="插入" /><Buttonandroid:id="@+id/btn_query_by_name"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="5dp"android:text="根据姓名查询" /><Buttonandroid:id="@+id/btn_delete_by_name"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="5dp"android:text="根据姓名删除" />
</LinearLayout>
(5)Java 代码
  • ClientInputActivity 类
package com.my.client;import androidx.appcompat.app.AppCompatActivity;import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;import com.my.client.content.UserInfoContent;
import com.my.client.entity.User;public class ClientInputActivity extends AppCompatActivity implements View.OnClickListener {private static final String TAG = ClientInputActivity.class.getSimpleName();private EditText et_name;private EditText et_age;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_client_input);et_name = findViewById(R.id.et_name);et_age = findViewById(R.id.et_age);Button btn_insert = findViewById(R.id.btn_insert);Button btn_query_by_name = findViewById(R.id.btn_query_by_name);Button btn_delete_by_name = findViewById(R.id.btn_delete_by_name);btn_insert.setOnClickListener(this);btn_query_by_name.setOnClickListener(this);btn_delete_by_name.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_insert:ContentValues values = new ContentValues();values.put(UserInfoContent.USER_NAME, et_name.getText().toString());values.put(UserInfoContent.USER_AGE, Integer.parseInt(et_age.getText().toString()));getContentResolver().insert(UserInfoContent.CONTENT_URI, values);Toast.makeText(this, "插入完成", Toast.LENGTH_SHORT).show();break;case R.id.btn_query_by_name:Cursor cursor = getContentResolver().query(UserInfoContent.CONTENT_URI, null, "user_name=?",new String[]{et_name.getText().toString()}, null);if (cursor != null) {while (cursor.moveToNext()) {User user = new User();@SuppressLint("Range") int id = cursor.getInt(cursor.getColumnIndex(UserInfoContent.USER_ID));@SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex(UserInfoContent.USER_NAME));@SuppressLint("Range") int age = cursor.getInt(cursor.getColumnIndex(UserInfoContent.USER_AGE));user.id = id;user.name = name;user.age = age;Log.i(TAG, "---------- " + user);}cursor.close();}break;case R.id.btn_delete_by_name:int count = getContentResolver().delete(UserInfoContent.CONTENT_URI,"user_name=?", new String[]{et_name.getText().toString()});if (count > 0) {Toast.makeText(this, "删除完成", Toast.LENGTH_SHORT).show();}break;}}
}

三、动态权限申请

1、基本介绍
  • Android 系统为了防止某些 APP 滥用权限,从 6.0 开始引入了运行时权限管理机制,允许 APP 在运行过程中动态检查是否拥有某项权限,一旦发现缺少某种必需的权限,则系统会自动弹出小窗提示用户去开启该权限
2、动态权限申请步骤
  1. 检查 APP 是否开启了指定权限
  • 调用 ContextCompat 的 checkSelfPermission 方法
  1. 请求系统弹窗,以便用户选择是否开启权限
  • 调用 ActivityCompat 的 requestPermissions 方法,即可命令系统自动弹出权限申请窗口
  1. 判断用户的权限选择结果
  • 重写活动页面的权限请求回调方法 onRequestPermissionsResult,在该方法内部处理用户的权限选择结果
3、Lazy 模式
(1)清单文件
  • AndroidManifest.xml
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" /><uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
(2)工具类
  • PermissionUtil 类
package com.my.client.util;import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;public class PermissionUtil {// 检查多个权限,返回 true 表示已完全启用权限,返回 false 表示未完全启用权限public static boolean checkPermission(Activity act, String[] permissions, int requestCode) {// Android 6.0 之后开始采用动态权限管理if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 有权限,PackageManager.PERMISSION_GRANTED// 无权限,PackageManager.PERMISSION_DENIED// 保存一份权限查看结果的标识int check = PackageManager.PERMISSION_GRANTED;// 遍历权限列表,判断对应权限是否开启for (String permission : permissions) {check = ContextCompat.checkSelfPermission(act, permission);// 只要有一个权限未开启,就退出遍历,进行权限申请if (check != PackageManager.PERMISSION_GRANTED) {break;};}// 权限未开启,请求系统弹窗,让用户选择是否开启权限if (check != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(act, permissions, requestCode);return false;}}return true;}// 检查权限结果数组,返回 true 表示权限都已开启,返回 false 表示至少有一个权限未开启public static boolean checkGrant(int[] grantResults) {if (grantResults != null) {for (int grantResult : grantResults) {if (grantResult != PackageManager.PERMISSION_GRANTED) {return false;}}return true;}return false;}
}
  • ToastUtil 类
package com.my.client.util;import android.app.Activity;
import android.widget.Toast;public class ToastUtil {public static void show(Activity act, String str) {Toast.makeText(act, str, Toast.LENGTH_SHORT).show();}
}
(3)XML 文件
  • activity_permission_lazy.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="5dp"><Buttonandroid:id="@+id/btn_contact"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginBottom="5dp"android:text="读取通讯录"android:textSize="15dp" /><Buttonandroid:id="@+id/btn_sms"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="收发短信"android:textSize="15dp" />
</LinearLayout>
(4)Java 代码
  • PermissionLazyActivity 类
package com.my.client;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.Manifest;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;import com.my.client.util.PermissionUtil;
import com.my.client.util.ToastUtil;public class PermissionLazyActivity extends AppCompatActivity implements View.OnClickListener {private static final String[] PERMISSIONS_CONTACTS = new String[]{Manifest.permission.READ_CONTACTS,Manifest.permission.WRITE_CONTACTS};private static final String[] PERMISSIONS_SMS = new String[]{Manifest.permission.SEND_SMS,Manifest.permission.READ_SMS};private static final int REQUEST_CODE_CONTACTS = 1;private static final int REQUEST_CODE_SMS = 2;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_permission_lazy);Button btn_contact = findViewById(R.id.btn_contact);Button btn_sms = findViewById(R.id.btn_sms);btn_contact.setOnClickListener(this);btn_sms.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_contact:PermissionUtil.checkPermission(this, PERMISSIONS_CONTACTS, REQUEST_CODE_CONTACTS);break;case R.id.btn_sms:PermissionUtil.checkPermission(this, PERMISSIONS_SMS, REQUEST_CODE_SMS);break;}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode) {case REQUEST_CODE_CONTACTS:if (PermissionUtil.checkGrant(grantResults)) {ToastUtil.show(this, "通讯录权限获取成功");} else {ToastUtil.show(this, "通讯录权限获取失败");jumpToSetting();}break;case REQUEST_CODE_SMS:if (PermissionUtil.checkGrant(grantResults)) {ToastUtil.show(this, "收发短信权限获取成功");} else {ToastUtil.show(this, "收发短信权限获取失败");jumpToSetting();}break;}}// 跳转到应用设置界面public void jumpToSetting() {Intent intent = new Intent();intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);intent.setData(Uri.fromParts("package", getPackageName(), null));intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);}
}
4、Hungry 模式
(1)清单文件
  • 同 【三 - 3 - (1)】
(2)工具类
  • 同 【三 - 3 - (2)】
(3)XML 文件
  • activity_permission_hungry.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="5dp"><Buttonandroid:id="@+id/btn_contact"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginBottom="5dp"android:text="读取通讯录"android:textSize="15dp" /><Buttonandroid:id="@+id/btn_sms"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="收发短信"android:textSize="15dp" />
</LinearLayout>
(4)Java 代码
  • PermissionHungryActivity 类
package com.my.client;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;import com.my.client.util.PermissionUtil;
import com.my.client.util.ToastUtil;public class PermissionHungryActivity extends AppCompatActivity implements View.OnClickListener {private static final String[] PERMISSIONS_ALL = new String[]{Manifest.permission.READ_CONTACTS,Manifest.permission.WRITE_CONTACTS,Manifest.permission.SEND_SMS,Manifest.permission.READ_SMS};private static final String[] PERMISSIONS_CONTACTS = new String[]{Manifest.permission.READ_CONTACTS,Manifest.permission.WRITE_CONTACTS};private static final String[] PERMISSIONS_SMS = new String[]{Manifest.permission.SEND_SMS,Manifest.permission.READ_SMS};private static final int REQUEST_CODE_CONTACTS = 1;private static final int REQUEST_CODE_SMS = 2;private static final int REQUEST_ALL = 3;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_permission_hungry);Button btn_contact = findViewById(R.id.btn_contact);Button btn_sms = findViewById(R.id.btn_sms);btn_contact.setOnClickListener(this);btn_sms.setOnClickListener(this);PermissionUtil.checkPermission(this, PERMISSIONS_ALL, REQUEST_ALL);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_contact:PermissionUtil.checkPermission(this, PERMISSIONS_CONTACTS, REQUEST_CODE_CONTACTS);break;case R.id.btn_sms:PermissionUtil.checkPermission(this, PERMISSIONS_SMS, REQUEST_CODE_SMS);break;}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode) {case REQUEST_CODE_CONTACTS:if (PermissionUtil.checkGrant(grantResults)) {ToastUtil.show(this, "通讯录权限获取成功");} else {ToastUtil.show(this, "通讯录权限获取失败");jumpToSetting();}break;case REQUEST_CODE_SMS:if (PermissionUtil.checkGrant(grantResults)) {ToastUtil.show(this, "收发短信权限获取成功");} else {ToastUtil.show(this, "收发短信权限获取失败");jumpToSetting();}break;case REQUEST_ALL:if (PermissionUtil.checkGrant(grantResults)) {ToastUtil.show(this, "所有权限获取成功");} else {ok: for (int i = 0; i < grantResults.length; i++) {if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {switch (permissions[i]) {case Manifest.permission.READ_CONTACTS:case Manifest.permission.WRITE_CONTACTS:ToastUtil.show(this, "通讯录权限获取失败");jumpToSetting();break ok;case Manifest.permission.SEND_SMS:case Manifest.permission.READ_SMS:ToastUtil.show(this, "收发短信权限获取失败");jumpToSetting();break ok;}}}}break;}}// 跳转到应用设置界面public void jumpToSetting() {Intent intent = new Intent();intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);intent.setData(Uri.fromParts("package", getPackageName(), null));intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);}
}
http://www.xdnf.cn/news/18898.html

相关文章:

  • 深度学习篇---模型参数保存
  • [肥用云计算] Serverless 多环境配置
  • PCM转音频
  • 面试之HashMap
  • LightRAG
  • 文档格式转换软件 一键Word转PDF
  • PPT处理控件Aspose.Slides教程:在 C# 中将 PPTX 转换为 Markdown
  • 【qml-7】qml与c++交互(自动补全提示)
  • [n8n] 全文检索(FTS)集成 | Mermaid图表生成
  • Android 使用MediaMuxer+MediaCodec编码MP4视频
  • 辅助驾驶出海、具身智能落地,稀缺的3D数据从哪里来?
  • 介绍智慧城管十大核心功能之一:风险预警系统
  • 架构评审:构建稳定、高效、可扩展的技术架构(下)
  • Java8-21的核心特性以及用法
  • 揭开.NET Core 中 ToList () 与 ToArray () 的面纱:从原理到抉择
  • ⸢ 贰 ⸥ ⤳ 安全架构:数字银行安全体系规划
  • 上海控安:GB 44495-2024《汽车整车信息安全技术要求》标准解读和测试方案
  • 修改win11任务栏时间字体和小图标颜色
  • vue实现表格轮播
  • 力扣18:四数之和
  • Python 实现冒泡排序:从原理到代码
  • PDFMathTranslate:让科学PDF翻译不再难——技术原理与实践指南
  • 2024中山大学研保研上机真题
  • (附源码)基于Spring Boot公务员考试信息管理系统设计与实现
  • 2025年渗透测试面试题总结-36(题目+回答)
  • 数据结构Java--8
  • Linux基础优化(Ubuntu、Kylin)
  • vue2实现背景颜色渐变
  • Java基础 8.27
  • 神经网络|(十六)概率论基础知识-伽马函数·上