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 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> (六)ContentProvider 实现数据共享 -> 正文阅读

[移动开发](六)ContentProvider 实现数据共享

6.1 ContentProvider 概述

Content Provider 用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为在Android中没有提供所有应用共同访问的公共存储区域。
Content Provider内部如何保存数据由其设计者决定,但是所有的Content Provider都实现一组通用的方法, 用来提供数据的增、删、改、查功能。
客户端通常不会直接使用这些方法,大多数是通过ContentResolver对象实现对Content Provider的操作。开发
人员可以通过调用Activity或者其他应用程序组件的实现类中getContentResolver()方法来获得ContentProvider 对象,例如:

ContentResolver cr = getContentResolver();

使用ContentResolver提供的方法可以获得Content Provider中任何感兴趣的数据。
当开始查询时,Android 系统确认查询的目标Content Provider并确保它正在运行。系统会初始化所有ContentProvider类的对象,开发人员不必完成此类操作,实际上,开发人员根本不会直接使用ContentProvider类的对象。通常,每个类型的ContentProvider仅有一个单独的实例。但是该实例能与位于不同应用程序和进程的多个ContentResolver类对象通信。不同进程之间的通信由ContentProvider类和ContentResolver类处理。

6.1.1 数据模型

Content Provider使用基于数据库模型的简单表格来提供其中的数据,这里每行代表一条记录, 每列代表特定类型和含义的数据。例如,联系人的信息可能以如下的方式提供。

在这里插入图片描述

每条记录包含一个数值型的. JID字段,用于在表格中唯一标识该记录。 ID 能用于匹配相关表格中的记录,例如,在一个表格中查询联系人的电话,在另一表格中查询其照片。

查询返回一个Cursor对象,它能遍历各行各列来读取各个字段的值。对于各个类型的数据,它都提供了专用的方法。因此,为了读取字段的数据,开发人员必须知道当前字段包含的数据类型。

6.1.2 URI 的用法

每个Content Provider 提供公共的URI (使用Uri类包装)来唯一标识其数据集。 管理多个数据集(多个表格)的Content Provider为每个数据集提供了单独的URI。所有为provider提供的URI都以"content//"作为前缀,“content://"模 式表示数据由Content Provider来管理.

如果自定义Content Provider,则应该为其URI也定义一个常量,来简化客户端代码并让日后更新更加简洁。Android为当前平台提供的Content Provider定义了CONTENT_ URI 常量。例如,匹配电话号码到联系人表格的URI和匹配保存联系人照片表格的URI分别如下:

android.provider.Contacts.Phones.CONTENT_ _URI
android.provider.Contacts.Photos.CONTENT_ _URI

URI常量用于所有与Content Provider的交互中。每个ContentResolver方法使用URI作为其第一个参数。 它标识ContentResolver应该使用哪个provider及其中的哪个表格.

在这里插入图片描述

[V] A:标准的前缀,用于标识该数据由Content Provider管理,不需修改。
[V] B: URI的authority部分,用于标识该Content Provider。对于第三方应用,该部分应该是完整的类名(使用小写形式)来保证唯一性。 在 元素的authorities属性中声明authority。
[V] C: Content Provider的路径部分,用于决定哪类数据被请求。如果Content Provider仅提供一种数据类型,可以省略该部分;如果provider提供几种类型,包括子类型,这部分可以由几部分组成。
[V] D:被请求的特定记录的ID值。这是被请求记录的_ ID值。如果请求不仅限于单条记录,该部分及其前面的斜线应该删除。

6.2 预定义 Content Provider

Android系统为常用数据类型提供了很多预定义的Content Provider (声音、视频、图片、联系人等),它们大多位于android.provider包中。开发人员可以查询这些provider以获得其中包含的信息(尽管有些需要适当的权限来读取数据)。 Android 系统提供的常见Content Provider说明如下。

[V] Browser: 读取或修改书签、浏览历史或网络搜索。
[V] CalLog: 查看或更新通话历史。
[V] Contacts: 获取、修改或保存联系人信息。
[V] LiveFolders: 由Content Provider提供内容的特定文件夹。
[V] MediaStore: 访问声音、视频和图片。
[V] Setting: 查看和获取蓝牙设置、铃声和其他设备偏好。
[V] SyncStateContract: 用于使用数据数组账号关联数据的ContentProvider约束。希望使用标准方式保存数据的provider时可以使用。
[V] UserDictionary: 在可预测文本输入时,提供用户定义单词给输入法使用。应用程序和输入法能增加数据到该字典。单词能关联频率信息和本地化信息。

6.2.1查询数据

要查询Content Provider中的数据,需要以下3个信息:
[V]标识该Content Provider的URI.
[V]需要查询的数据字段名称。
[V]字段中数据的类型。

如果查询特定的记录,则还需要提供该记录的ID值。
为了查询Content Provider中的数据,开发人员需要使用ContentResolver.query()或Activity.managedQuery()方法。这两个方法使用相同的参数,并且都返回Cursor对象。但是managedQuery()方法导致Activity管理Cursor的生命周期。托管的Cursor处理所有的细节,如当Activity 暂停时卸载自身,当Activity重启时加载自身。调用Activity.startManagingCursor()方法可以让Activity管理未托管的Cursor对象
query0和managedQuery0方法的第一-个参 数是provider的URI,即标识特定ContentProvider和数据集的CONTENT_ URI常量。
为了限制仅返回一条记录,可以在URI结尾增加该记录的_ ID 值,即将匹配ID值的字符串作为URI路径部分的结尾片段。例如,ID值是10, URI将是:

content://.. ./10

有些辅助方法,特别是ContentUrs.withAppendedId()和Uri.withAppendedPath()方法,能轻松地将ID增加到URI。这两个方法都是静态方法,并返回一个增加了ID的Uri对象。

query()和managedQuery()方法的其他参数用来更加细致地限制查询结果,它们是:
[V]应该返回的数据列名称。null 值表示返回全部列;否则,仅返回列出的列。全部预定义Content Provider为其列都定义了常量。例如,android.provider.Contacts.Phones类定义了_ ID、NUMBER、
NUMBER_ KEY、NAME等常量。

[V]决定哪些行被返回的过滤器,格式类似SQL的WHERE语句(但是不包含WHERE自身)。null值表
示返回全部行(除非URI限制查询结果为单行记录)。
[V]选择参数。
[V]返回记录的排序器,格式类似SQL的ORDER BY语句(但是不包含ORDERBY自身)。null 值表示以默认顺序返回记录,这可能是无序的。

查询返回一组0条或多条数据库记录。列名、默认顺序和数据类型对每个Content Provider都是特别的。但是每个provider都有一个_ ID 列,它为每条记录保存唯一的数值 ID。每个provider也能使用_ COUNT报告返回结果中记录的行数,该值在各行都是相同的。
获得数据使用Cursor对象处理,它能向前或向后遍历整个结果集。开发人员可以使用Cursor对象来读取数据,而增加、修改和删除数据则必须使用ContentResolver对象。

6.2.2 增加记录

为了向Content Provider中增加新数据,首先需要在ContentValues对象中建立键值对映射,这里每个键匹配Content Provider中列名,每个值是该列中希望增加的值。然后调用ContentResolver. insert()方法并传递给它provider的URI参数和ContentValues映射。该方法返回新记录的完整URI,即增加了新记录ID的URI。开发人员可以使用该URI来查询并获取该记录的Cursor,以便修改该记录。

6.2.3 增加新值

一旦记录存在,开发人员可以向其中增加新信息或者修改已经存在的信息。增加记录到Contacts数据库的最佳方式是增加保存新数据的表名到代表记录的URI,然后使用组装好的URI来增加新数据。每个Contacts表格以CONTENT_ DIRECTORY常量的方式提供名称。
开发人员可以调用使用byte数组作为参数的ContentV alues.put()方法向表格中增加少量二进制数据,这适用于类似小图标的图片、短音频片段等。然而,如果需要增加大量二进制数据,如图片或者完整的歌曲等,则需要保存代表数据content:URI 到表格,然后使用文件URI调用ContentResolver. openOutputStream0方法。这导致ContentProvider保存數据到文件并在记录的隐藏字段保存文件路径。

6.2.4 批量更新记录

要批量更新数据(例如,将全部字段中“NY”替换成“New York"),可使用ContentResolver.update()方法并提供需要修改的列名和值。

6.2.5 删除记录

如果需要删除单条记录,可调用ContentResolver.delete(方法并提供特定行的URI。
如果需要删除多条记录,可调用 ContentResolver. delete0方法并提供删除记录类型的URI (如android.provider.Contacts.People.CONTENT URI)和一个SQL WHERE语句,它定义哪些行需要删除。
注意:请确保提供 了一个合适的WHERE语句,否则可能删除全部数据。

6.2.6 案例 1:查询联系人ID和姓名

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/result"
        android:textColor="@android:color/black"
        android:textSize="25dp"
        />
</LinearLayout>

MainActivity.java

package com.jingyi.dataproj;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ContentResolver;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Contacts;
import android.provider.ContactsContract;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private String[] columns={
            ContactsContract.Contacts._ID,//ID值
            ContactsContract.Contacts.DISPLAY_NAME,//姓名
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        TextView textView = (TextView) findViewById(R.id.result);
        textView.setText(getQueryData());
    }

    private String getQueryData() {
        StringBuilder stringBuilder = new StringBuilder();
        ContentResolver contentResolver = getContentResolver();
        Cursor cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, columns, null, null, null);//查询记录

        int idIndex = cursor.getColumnIndex(columns[0]);//获取ID记录的索引值
        int nameIndex = cursor.getColumnIndex(columns[1]);//姓名记录的索引
        for (cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()){
            int id = cursor.getInt(idIndex);
            String name = cursor.getString(nameIndex);
            stringBuilder.append("id:"+id+","+"name:"+name);
        }
        cursor.close();//关闭Cursor
        return  stringBuilder.toString();//返回查询结果
    }
}

AndroidManifest.xml

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

    <application
        android: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.Application">
        <activity
            android: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>
    <!--读取联系人记录权限-->
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
</manifest>

在这里插入图片描述

在这里插入图片描述

6.3 自定义 Content Provider

如果开发人员希望共享自己的数据,则有以下两个选择:
[V]创建自定义的Content Provider (一 个ContentProvider类的子类)。
[V] 如果有预定义的provider, 管理相同的数据类型并且有写入权限,则可以向其中增加数据。前面已经详细介绍了如何使用系统预定义的Content Provider,下 面将介绍如何自定义Content Provider。如果自定义Content Provider,开发人员需要完成以下操作:
[V]建立数据存储系统。大多数Content Provider使用Android文件存储方法或者SQLite数据库保存数据,但是开发人员可以使用任何方式存储。Android 提供了SQLiteOpenHelper 类帮助创建数据库,SQLiteDatabase类帮助管理数据库。
[V]继承ContentProvider类来提供数据访问方式
[V]在应用程序的AndroidManifest文件中声明Content Provider.
下面主要介绍继承ContentProvider类和声明Content Provider的操作。

6.3.1 继承ContentProvider 类

开发人员定义ContentProvider类的子类,以便使用ContentResolver和Cursor类来共享数据。原则上,这意味着需要实现ContentProvider类定义的6个抽象方法,其语法格式如下:

public boolean onCreate()
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortorder)
public Uri insert(Uri uri, ContentValues values)
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
public int delete(Uri uri, String selection, String[] selectionArgs)
public String getType(Uri uri)

在这里插入图片描述

query0方法必须返回Cursor对象,用于遍历查询结果。Cursor 自身是一个接口,Android 提供了该接口的一些实现类,例如,SQLiteCursor 能遍历存储在SQLite 数据库中的数据。通过调用SQLiteDatabase类的query0方法可以获得Cursor对象,它们都位于android.database包中,其继承 关系如图所示。

在这里插入图片描述

圆角矩形表示接口,矩形表示类

由于这些ContentProvider方法能被位于不同进程和线程的不同ContentResolver对象调用,它们必须以线程安全的方式实现
此外,开发人员也可以调用ContentResolver.notifyChange0方法,以便在 数据修改时通知监听器。除了定义子类自身,还应采取一些措施以简化客户端工作并让类更加易用:

1.定义public static final Uri CONTENT_ URI变量(CONTENT URI 是变量名称)。该字符串表示自定义的Content Provider处理的完整content:URI。开发人员必须为该值定义唯一的字符串 。最佳的解决方式是使用ContentProvider的完整类名(小写)。例如,LearmProvider 的URI可能按如下方式定义:

public static final Uri CONTENT_URI= Uri. parse("content://com.android.learnprovider");

如果provider包含子表,也应该为各个子表定义URI。这些URI应该有相同authority ( 因为它标识Content:Provider)使用路径进行区分,例如:

content://com.android.Learnprovider/dba
content://com.android.Learnprovider/programmer
content://com.android.Learnprovider/ceo

2.定义Content Provider将返回给客户端的列名。如果开发人员使用底层数据库,这些列名通常与SQL数据库列名相同。同样,定义public static String常量,客户端用它们来指定查询中的列和其他指令。确保包含名为“ID"的整数列来作为记录的ID值。无论记录中其他字段是否唯一,如URL,开发人员都应该包含该字段。如果打算使用SQLite数据库,_ ID 字段类型如下:

INTEGER PRIMARY KEY AUTOINCR EMENT

3.仔细注释每列的数据类型,客户端需要使用这些信息来读取数据。
4.如果开发人员正在处理新数据类型,则必须定义新的MIME类型,以便在ContentProvider. getType)方法实现中返回。
5.如果开发人员提供的byte 数据太大而不能放到表格中,如bitmap文件,提供给客户端的字段应该包含content:URI字符串。

6.3.2 声明Content Provider

为了让Android系统知道开发人员编写的Content Provider,应该在应用程序的AndroidManifest.xml文件中定义元素。没有在配置文件中声明的自定义Content Provider,对于Android系统不可见。
Name属性的值是ContentProvider类的子类的完整名称; authorities 属性是provider定义的content:URI中authority部分; ContentProvider 的子类是LearnProvider。 元素应该如下:

<provider
android :name="com.android.LearnProvider"
android :authorities="com.android.learnprovider"
...>
               
</ provider>

注意: authorities 属性删除了content:URI 中的路径部分。

其他属性能设置读写数据的权限、提供显示给用户的图标或文本、启用或禁用provider等。如果数据不需要在多个运行的Content Provider间同步,则设置multiprocess为true。这允许在各个客户端进程创建一个provider实例,从而避免执行IPC。

6.4 应用实例

6.4.1 查询联系人姓名和电话

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/result"
        android:textColor="@android:color/black"
        android:textSize="25dp"
        />
</LinearLayout>

MainActivity.java

package com.jingyi.dataproj;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ContentResolver;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Contacts;
import android.provider.ContactsContract;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private String[] columns={
            ContactsContract.Contacts._ID,//ID值
            ContactsContract.Contacts.DISPLAY_NAME,//姓名
            ContactsContract.CommonDataKinds.Phone.NUMBER,//电话号码
            ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        TextView textView = (TextView) findViewById(R.id.result);
        textView.setText(getQueryData());
    }

    private String getQueryData() {
        StringBuilder stringBuilder = new StringBuilder();
        ContentResolver contentResolver = getContentResolver();
        Cursor cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);//查询记录
        while(cursor.moveToNext()){
            int idIndex = cursor.getColumnIndex(columns[0]);//获取ID记录的索引值
            int nameIndex = cursor.getColumnIndex(columns[1]);//姓名记录的索引
            int id = cursor.getInt(idIndex);
            String name = cursor.getString(nameIndex);

            Cursor phone = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, columns[3] + "=" + id, null, null);
            while (phone.moveToNext()){
                int phoneColumnIndex = phone.getColumnIndex(columns[2]);//电话的索引
                String phoneString = phone.getString(phoneColumnIndex);//电话数据
                stringBuilder.append("name:"+name+","+"phone:"+phoneString);
            }
        }
        cursor.close();//关闭Cursor
        return  stringBuilder.toString();//返回查询结果
    }
}

AndroidManifest.xml

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

    <application
        android: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.Application">
        <activity
            android: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>
    <!--读取联系人记录权限-->
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
</manifest>
6.4.2 自动补全联系人姓名

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/title"
        android:layout_gravity="center"
        android:text="标题"
        android:textColor="@android:color/black"
        android:textSize="30dp"
        />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/textView"
            android:layout_margin="5dp"
            android:text="姓名"
            android:textColor="@android:color/black"
            android:textSize="25dp"
            />
        <AutoCompleteTextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/edit"
            android:completionThreshold="1"
            android:textColor="@android:color/black"
            >
            <requestFocus/>
        </AutoCompleteTextView>
    </LinearLayout>
</LinearLayout>

ContactListAdapter.java

public class ContactListAdapter extends CursorAdapter implements Filterable {
    private ContentResolver resolver;
    private String[] columns=new String[]{ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME};


    public  ContactListAdapter(Context context,Cursor con){
        super(context,con);
        resolver = context.getContentResolver();
    }
    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        LayoutInflater inflater = LayoutInflater.from(context);
        TextView view = (TextView) inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
        return view;
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        ((TextView)view).setText(cursor.getString(1));
    }

    @Override
    public CharSequence convertToString(Cursor cursor) {
        return cursor.getString(1);
    }

    @Override
    public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
        FilterQueryProvider filter = getFilterQueryProvider();
        if (filter!=null){
            return  filter.runQuery(constraint);
        }
        Uri uri= Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,Uri.encode(constraint.toString()));
        return resolver.query(uri,columns,null,null,null);
    }

}

MainActivity.java

package com.jingyi.dataproj;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ContentResolver;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Contacts;
import android.provider.ContactsContract;
import android.widget.AutoCompleteTextView;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private String[] columns={
            ContactsContract.Contacts._ID,//ID值
            ContactsContract.Contacts.DISPLAY_NAME,//姓名
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ContentResolver resolver = getContentResolver();
        Cursor cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, columns, null, null, null);
        ContactListAdapter adapter = new ContactListAdapter(this, cursor);
        AutoCompleteTextView autoCompleteTextView = (AutoCompleteTextView) findViewById(R.id.edit);
        autoCompleteTextView.setAdapter(adapter);
    }


}

在这里插入图片描述

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-04-04 12:22:54  更:2022-04-04 12:26:29 
 
开发: 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/24 21:08:17-

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