首页 > 代码库 > 黎活明8天快速掌握android视频教程--20_采用ContentProvider对外共享数据

黎活明8天快速掌握android视频教程--20_采用ContentProvider对外共享数据

1、内容提供者是让当前的app的数据可以让其他应用访问,其他应该可以通过内容提供者访问当前app的数据库

contentProvider的主要目的是提供一个开发的接口,让其他的应该能够访问当前应用的数据

2、建立的操作类必须继承自定义的内容提供者必须继承ContentProvider

3、创建的创建的PersonProvider必须在应用的主包名和主包名的子目录下,现在应用的主包名是 package="test.weiyuan.sqllite1"

4、内容提供者编写好之后需要在清单文件中进行注册,Android的四大组件都需要在清单文件中进行注册,因为provide是让外部的应用通过provide能够访问当前应用的数据,所以需要指明provide的访问路径   android:authorities,一般采用包名+provider的名字的形式

我们看下清单配置文件:

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

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        //对内容提供者进行注册
        <provider
            android:authorities="test.weiyuan.sqllite1.PersonProvider"
            android:name=".PersonProvider"
            android:exported="true">

            </provider>
        <activity
            android:name=".MyActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

上面的三个属性必须必须填写,其中Android:exported为true表示该数据允许外面的程序访问,不要忘记填写

整个程序的代码框架也是采用mvc的架构

技术分享

我们来一一分析

DbOpenHelper 类:
package dB;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;


public class DbOpenHelper extends SQLiteOpenHelper
{

    public DbOpenHelper(Context context) {

        super(context, "wy.db", null, 2);
    }

    @Override
    public void onCreate(SQLiteDatabase db)
    {
     db.execSQL("CREATE TABLE person(personid integer primary key autoincrement, name varchar(20), phone VARCHAR(12) NULL)");





    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

业务库操作接口类:

可以有下面的两种方式操作数据库:

/**
 * 文件名:PersonService.java
 * 版权:版权所有 (C) 中国电科30所三部
 * 描述:
 * 修改人: wei.yuan
 * 修改时间:2015/1/9
 * 修改内容:新增
 */
package service;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

import dB.DbOpenHelper;
import domain.Person;

/**
 * 项目名称:SQLLite1
 * 类描述:
 * 创建人:wei.yuan
 * 创建时间:2015/1/9 11:08
 * 修改人:wei.yuan
 * 修改时间:2015/1/9 11:08
 * 修改备注:
 * 版权:版权所有 (C) 中国电科30所三部
 */
public class PersonService {
private DbOpenHelper dbOpenHelper;

    public PersonService(Context context) {
        this.dbOpenHelper = new DbOpenHelper(context);
    }
    public void save(Person person){
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        db.execSQL("insert into person(name, phone) values(?,?)",
                new Object[]{person.getName(), person.getPhone()});
    }
    /**
     * 删除记录
     * @param name 记录ID
     */
    public void delete(String name,String phone){
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        db.execSQL("delete from person where name=? and phone=?", new Object[]{name,phone});
    }
    /**
     * 更新记录
     * @param person
     */
    public void update(Person person,String name,String phone){
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        db.execSQL("update person set name=?,phone=? where name=? and phone=?",
                new Object[]{person.getName(), person.getPhone(),name,phone});
    }
    /**
     * 查询记录
     * @param name 记录ID
     * @return
     */
    public Person find(String name,String phone){
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select * from person where name=? and phone = ?", new String[]{name,phone});
        if(cursor.moveToNext()){
            int personid = cursor.getInt(cursor.getColumnIndex("personid"));
            String name1 = cursor.getString(cursor.getColumnIndex("name"));
            String phone1 = cursor.getString(cursor.getColumnIndex("phone"));
            return new Person( name1, phone1);
        }
        cursor.close();
        return null;
    }
    /**
     * 分页获取记录
     * @param offset 跳过前面多少条记录
     * @param maxResult 每页获取多少条记录
     * @return
     */
    public List<Person> getScrollData(int offset, int maxResult){
        List<Person> persons = new ArrayList<Person>();
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select * from person order by personid asc limit ?,?",
                new String[]{String.valueOf(offset), String.valueOf(maxResult)});
        while(cursor.moveToNext()){
            int personid = cursor.getInt(cursor.getColumnIndex("personid"));

            /*这里也可以写成
            *   String name = cursor.getString(1);
            String phone = cursor.getString(2);
             默认的表自带的id字段为0 ,name为第一个字段所有为1 ,phone为第二个字段为2*/
            String name = cursor.getString(cursor.getColumnIndex("name"));
            String phone = cursor.getString(cursor.getColumnIndex("phone"));
            persons.add(new Person( name, phone));
        }
        cursor.close();
        return persons;
    }

    /**
     * 获取记录总数
     * @return
     */
    public long getCount(){
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select count(*) from person", null);
        cursor.moveToFirst();
        long result = cursor.getLong(0);//统计之后只有一个默认的字段,所以为0
        cursor.close();
        return result;
    }


    /*使用 SimpleCursorAdapter加装数据的时候,创建的数据库表的主键必须是_id,
    * 这里我们使用personid as _id,将创建表的主键personid变成_id
    * 返回cursor对象的时候,千万不能关闭cursor对象:cursor.close();*/
    public Cursor getScrollCursorData(int offset, int maxResult){
        List<Person> persons = new ArrayList<Person>();
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select personid as _id,name,phone from person order by personid asc limit ?,?",
                new String[]{String.valueOf(offset), String.valueOf(maxResult)});

        return cursor;//返回cursor对象之前,千万不能关闭cursor对象cursor.close();
    }

}

方式二:

package service;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import java.util.ArrayList;
import java.util.List;

import dB.DbOpenHelper;
import domain.Person;


public class OtherPersonService {
private DbOpenHelper dbOpenHelper;

    public OtherPersonService(Context context) {
        this.dbOpenHelper = new DbOpenHelper(context);
    }
    public void save(Person person){
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("name",person.getName());
        values.put("phone",person.getPhone());
        db.insert("person",null,values);
    }
    /**
     * 删除记录
     * @param name 记录ID
     */
    public void delete(String name,String phone){
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
       /* db.execSQL("delete from person where name=? and phone=?", new Object[]{name,phone});*/
        db.delete("person", "name=? and phone= ?", new String[]{name ,phone});
    }
    /**
     * 更新记录
     * @param person
     */
    public void update(Person person,String name,String phone){
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
       /* db.execSQL("update person set name=?,phone=? where name=? and phone=?",
                new Object[]{person.getName(), person.getPhone(),name,phone});*/
        ContentValues values = new ContentValues();
        values.put("name",person.getName());
        values.put("phone",person.getPhone());
        db.update("person",values,"name=? and phone=?",new String[]{name ,phone});
    }
    /**
     * 查询记录
     * @param name 记录ID
     * @return
     */
    public Person find(String name,String phone){
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
       /* Cursor cursor = db.rawQuery("select * from person where name=? and phone = ?", new String[]{name, phone});*/
        Cursor  cursor = db.query("person",null,"name=? and phone=?",new String[]{name,phone},null,null,null);
        if(cursor.moveToNext()){
            int personid = cursor.getInt(cursor.getColumnIndex("personid"));
            String name1 = cursor.getString(cursor.getColumnIndex("name"));
            String phone1 = cursor.getString(cursor.getColumnIndex("phone"));
            return new Person( name1, phone1);
        }
        cursor.close();
        return null;
    }
    /**
     * 分页获取记录
     * @param offset 跳过前面多少条记录
     * @param maxResult 每页获取多少条记录
     * @return
     */
    public List<Person> getScrollData(int offset, int maxResult){
        List<Person> persons = new ArrayList<Person>();
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
       /* Cursor cursor = db.rawQuery("select * from person order by personid asc limit ?,?",*/
        Cursor  cursor = db.query("person",null,null,null,null,null,"personid asc",offset+ ","+ maxResult);
        while(cursor.moveToNext()){
            int personid = cursor.getInt(cursor.getColumnIndex("personid"));

            /*这里也可以写成
            *   String name = cursor.getString(1);
            String phone = cursor.getString(2);
             默认的表自带的id字段为0 ,name为第一个字段所有为1 ,phone为第二个字段为2*/
            String name = cursor.getString(cursor.getColumnIndex("name"));
            String phone = cursor.getString(cursor.getColumnIndex("phone"));
            persons.add(new Person( name, phone));
        }
        cursor.close();
        return persons;
    }

    /**
     * 获取记录总数
     * @return
     */
    public long getCount(){
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        /*Cursor cursor = db.rawQuery("select count(*) from person", null);*/
        Cursor cursor = db.query("person", new String[]{"count(*)"}, null, null, null, null, null);
        cursor.moveToFirst();
        long result = cursor.getLong(0);//统计之后只有一个默认的字段,所以为0
        cursor.close();
        return result;
    }

    /*使用 SimpleCursorAdapter加装数据的时候,创建的数据库表的主键必须是_id,
  * 这里我们使用personid as _id,将创建表的主键personid变成_id
  * 返回cursor对象的时候,千万不能关闭cursor对象:cursor.close();*/
    public Cursor getScrollCursorData(int offset, int maxResult){
        List<Person> persons = new ArrayList<Person>();
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select personid as _id,name,phone from person order by personid asc limit ?,?",
                new String[]{String.valueOf(offset), String.valueOf(maxResult)});

        return cursor;//返回cursor对象之前,千万不能关闭cursor对象cursor.close();
    }
}

javaBenn对象:

package domain;


public class Person
{

    private String name;
    private String phone;

    @Override
    public String toString() {
        return "Person{" +
                "name=‘" + name + ‘\‘‘ +
                ", phone=‘" + phone + ‘\‘‘ +
                ‘}‘;
    }

    public Person(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

下面也是我们最为关键的内容提供者操作类:通过该类其他的应该就可以访问当前应用的数据,只要是业务操作,业务操作的异常都不要内部进行try catch捕获,而应该将业务操作的异常返回给调用者,当调用者进行业务操作的时候,如果遇到了异常就知道该业务操作是成功还是失败了,然后做出相应的处理。相当的关键

package test.weiyuan.sqllite1;

import android.content.ContentProvider;
import android.content.ContentUris;
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 dB.DbOpenHelper;

/**
 * Created by wei.yuan on 2015/6/3.
 * 一定要在清单文件中对内容提供者进行注册
 * PersonProvider必须在应用的包名目录下或者包名的子目录下
 */
public class PersonProvider extends ContentProvider {

    private DbOpenHelper dbOpenHelper;
    //该类用于检测的外部应用输入的URL是否正确,如果正确返回码就是codePerson,不正确返回UriMatcher.NO_MATCH
    private    static  final int codePerson = 1;
    private    static  final int codePerson1 = 2;
    //该类用于检测的外部应用输入的URL是否正确,如果不正确返回码就是new UriMatcher(UriMatcher.NO_MATCH)构造函数中的参数,这里是UriMatcher.NO_MATCH
  private    static  final UriMatcher  uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static
    {
        //这个url表示对整个person表进行操作,如果url匹配返回的值是codePerson 
uriMatcher.addURI("test.weiyuan.sqllite1.PersonProvider","person",codePerson); //这个url表示对person表中的某个记录进行操作,#表示的是数字,就是对应的person表中的某个记录 // android中#代表数字,*表示任意的字符,如果url匹配返回的值是codePerson1
uriMatcher.addURI("test.weiyuan.sqllite1.PersonProvider","person/#",codePerson1); } //onCreate()之后被调用一次,在调用的时候创建数据库,记得返回值为true @Override public boolean onCreate() { dbOpenHelper = new DbOpenHelper(getContext()); return true; } //查询数据表的数据 @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase database = dbOpenHelper.getWritableDatabase(); switch (uriMatcher.match(uri)) { //更新整个表 case codePerson: return database.query("person",projection,selection,selectionArgs,null,null,sortOrder); //更新表中的某条记录 case codePerson1: long row_id = ContentUris.parseId(uri); String where = " personid="+row_id; Log.i("weiyuan",""+row_id); if (selection!=null&&!"".equals(selection.trim())) { where += "and "+selection; } return database.query("person",null,where,selectionArgs,null,null,sortOrder); default: //如果传人的url不正确,抛出一个异常提示用户 throw new IllegalArgumentException("this is a unkown url-->"+uri); } } //返回要操作数据的内容类型,如txt文本就是plain/text类型 @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { //操作的是多条数据记录 case codePerson: return "vnd.android.cursor.dir/person"; //操作的是单条数据 case codePerson1: return "vnd.android.cursor.item/person"; default: throw new IllegalArgumentException("this is a unkown url-->"+uri); } } //可以让外面的应用向内容提供者插入数据 @Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase database = dbOpenHelper.getWritableDatabase(); switch (uriMatcher.match(uri)) { case codePerson: //这里如果values为空,就必须需要第二个参数有值这里为"name",如果values有值,第二个参数默认为空值 long rowid = database.insert("person",null,values); // Uri insertUri = Uri.parse("context://test.weiyuan.providers.PersonProvider/person/"+rowid);可以使用下面的方法http://blog.csdn.net/feng88724/article/details/6331396 Uri insertUri = ContentUris.withAppendedId(uri,rowid); if(database!=null) { database.close(); } return insertUri; default: //如果传人的url不正确,抛出一个异常提示用户 if(database!=null) { database.close(); } throw new IllegalArgumentException("this is a unkown url-->"+uri); } } //返回值int表示你删除了多少条记录 @Override public int delete(Uri uri, String selection, String[] selectionArgs) { SQLiteDatabase database = dbOpenHelper.getWritableDatabase(); int num = 0;//删除的记录数目 switch (uriMatcher.match(uri)) { //删除整个表 case codePerson: num =database.delete("preson",selection,selectionArgs); if(database!=null) { database.close(); } break; //删除表中的某条记录 case codePerson1: // 从url中要删除的记录的id long row_id = ContentUris.parseId(uri); String where = " personid="+ row_id; //外面传递进行的条件 if (selection!=null&&!"".equals(selection.trim())) { where += "and "+selection; } num = database.delete("person",where,selectionArgs); if(database!=null) { database.close(); } break; default: //如果传人的url不正确,抛出一个异常提示用户 throw new IllegalArgumentException("this is a unkown url-->"+uri); } return num; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { SQLiteDatabase database = dbOpenHelper.getWritableDatabase(); switch (uriMatcher.match(uri)) { //更新整个表 case codePerson: database.update("preson",values,selection,selectionArgs); if(database!=null) { database.close(); } break; //更新表中的某条记录 case codePerson1: long row_id = ContentUris.parseId(uri); String where = " personid="+row_id; Log.i("weiyuan",""+row_id); if (selection!=null&&!"".equals(selection.trim())) { where += "and "+selection; } database.update("person",values,where,selectionArgs); if(database!=null) { database.close(); } break; default: if(database!=null) { database.close(); } //如果传人的url不正确,抛出一个异常提示用户 throw new IllegalArgumentException("this is a unkown url-->"+uri); } return 0; } }

 

 

 技术分享

 

注意点2:

技术分享

 

首先先让数据库这个app运行起来,现在我们在新建立一个app,在这个app中访问数据库app中的数据

我们使用测试工具类的办法来实现Android studio 中测试类必须继承AndroidTestCase,该类中的所有测试方法必须以test开头

package testSQLLite;

import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.test.AndroidTestCase;
import android.util.Log;

/**
 * Created by wei.yuan on 2015/6/3.
 */
public class TestContextProvider extends AndroidTestCase {
    final  static String  TAG ="weiyuan";

    //测试插入数据
     public  void testInsert() throws Exception
     {
         //注意uri中的字符串一般是小写,下面写成大写不太规范,一般遵循包名+类名,注意conten不要写成context了
         Uri uri = Uri.parse("content://test.weiyuan.sqllite1.PersonProvider/person");
         ContentResolver resolver = getContext().getContentResolver();
         ContentValues values = new ContentValues();
         values.put("name","aonier");
         values.put("phone","28");
         // 下面 resolver.insert(uri,values)这个函数本质上是调用了PersonProvider这个内容提供者中的insert函数
         resolver.insert(uri,values);
         Log.i(TAG,"数据插入成功!");

     }


    //删除数据
    public  void testDelete() throws Exception
    {
        Uri uri = Uri.parse("content://test.weiyuan.sqllite1.PersonProvider/person/1");
        long id = ContentUris.parseId(uri);
        Log.i(TAG,id+"");
        ContentResolver resolver = getContext().getContentResolver();

        resolver.delete(uri,null,null);
        Log.i(TAG,"数据删除成功!");
    }

    //更新表中的数据
    public  void testUpdate() throws Exception
    {
        Uri uri = Uri.parse("content://test.weiyuan.sqllite1.PersonProvider/person/1");
        long id = ContentUris.parseId(uri);

        ContentResolver resolver = getContext().getContentResolver();
        ContentValues values = new ContentValues();
        values.put("name","aonier");
        values.put("phone","34");
        resolver.update(uri,values,null,null);
        Log.i(TAG, "数据更新成功!");
    }


    //查询数据表的数据
    public  void testQuery() throws Exception
    {
        Uri uri = Uri.parse("content://test.weiyuan.sqllite1.PersonProvider/person");

        ContentResolver resolver = getContext().getContentResolver();
         Cursor cursor =resolver.query(uri, null, null, null, null);
         while (cursor.moveToNext())
         {
             Log.i(TAG,"数据查询成功!"+cursor.getString(cursor.getColumnIndex("name")));

         }
        //加载自定义适配器的数据


    }

}

 

对应上面两种数据库的两种业务操作也可以使用测试类的方式进行测试:

package testSQLLite;

import android.test.AndroidTestCase;
import android.util.Log;

import java.util.List;

import dB.DbOpenHelper;
import domain.Person;
import service.OtherPersonService;

public class OtherTestSQLLite extends AndroidTestCase {
      final  static String  TAG ="weiyuan";
    public  void testCreateDb()
    {
        DbOpenHelper dbOpenHelper = new DbOpenHelper(getContext());
        dbOpenHelper.getWritableDatabase();
        Log.i(TAG,"数据库创建成功");
    }
    public void testSave() throws Exception{
        OtherPersonService service = new OtherPersonService(this.getContext());
        for(int i = 0;i<20;i++)
        {
            service.save(new Person("weiyuan"+i,"12345"+i));
        }
        Log.i(TAG,"数据保存成功");
    }

      /*主要查找的是姓名和电话一起查找,只要满足了姓名和电话,才正确*/
    public void testFind() throws Exception{
        OtherPersonService service = new OtherPersonService(this.getContext());
        Person person = service.find("weiyuan1","123451");
        Log.i(TAG, person.toString());
        Log.i(TAG,"数据查找成功");

    }

    /*删除某个记录*/
    public void testdelete() throws Exception{
        OtherPersonService service = new OtherPersonService(this.getContext());
        service.delete("weiyuan1","123451");
        Log.i(TAG,"数据删除成功");

    }
    /*给新某个记录*/
    public void testupdate() throws Exception{
        OtherPersonService service = new OtherPersonService(this.getContext());
        service.update(new Person("chendong","456789"),"weiyuan2","123452");
        Log.i(TAG, "数据修改成功");

    }
        /*获得分页的数据*/
    public void testScrollData() throws Exception{
        OtherPersonService service = new OtherPersonService(this.getContext());
        List<Person> persons = service.getScrollData(0, 5);
        for(Person person : persons){
            Log.i(TAG, person.toString());
        }

    }

    public void testCount() throws Exception{
        OtherPersonService service = new OtherPersonService(this.getContext());
        long result = service.getCount();
        Log.i(TAG, result+"");
    }


}
package testSQLLite;

import android.test.AndroidTestCase;
import android.util.Log;
import android.widget.Toast;

import java.util.List;

import dB.DbOpenHelper;
import domain.Person;
import service.PersonService;


public class TestSQLLite extends AndroidTestCase {
      final  static String  TAG ="weiyuan";
    public  void testCreateDb()
    {
        DbOpenHelper dbOpenHelper = new DbOpenHelper(getContext());
        dbOpenHelper.getWritableDatabase();
        Log.i(TAG,"数据库创建成功");
    }
    public void testSave() throws Exception{
        PersonService service = new PersonService(this.getContext());
        for(int i = 0;i<20;i++)
        {
            service.save(new Person("weiyuan"+i,"12345"+i));
        }
        Log.i(TAG,"数据保存成功");
    }

      /*主要查找的是姓名和电话一起查找,只要满足了姓名和电话,才正确*/
    public void testFind() throws Exception{
        PersonService service = new PersonService(this.getContext());
        Person person = service.find("chendong","456789");
        Log.i(TAG, person.toString());
        Log.i(TAG,"数据查找成功");

    }

    /*删除某个记录*/
    public void testdelete() throws Exception{
        PersonService service = new PersonService(this.getContext());
        service.delete("weiyuan1","123451");
        Log.i(TAG,"数据删除成功");

    }
    /*给新某个记录*/
    public void testupdate() throws Exception{
        PersonService service = new PersonService(this.getContext());
        service.update(new Person("chendong","456789"),"weiyuan2","123452");
        Log.i(TAG, "数据修改成功");

    }
        /*获得分页的数据*/
    public void testScrollData() throws Exception{
        PersonService service = new PersonService(this.getContext());
        List<Person> persons = service.getScrollData(0, 5);
        for(Person person : persons){
            Log.i(TAG, person.toString());
        }

    }

    public void testCount() throws Exception{
        PersonService service = new PersonService(this.getContext());
        long result = service.getCount();
        Log.i(TAG, result+"");
    }


}

 

黎活明8天快速掌握android视频教程--20_采用ContentProvider对外共享数据