精华内容
下载资源
问答
  • 在多个应用中读取共享存储数据时
    千次阅读
    2016-08-13 19:14:13

              Android是如何实现应用程序之间数据共享的?一个应用程序可以将自己的数据完全暴露出去,外界根本看不到,也不用看到这个应用程序暴露的数据是如何存储的,或者是使用数据库还是使用文件,还是通过网上获得,这些一切都不重要,重要的是外界可以通过这一套标准及统一的接口和这个程序里的数据打交道,例如:添加(insert)、删除(delete)、查询(query)、修改(update),当然需要一定的权限才可以。

      如何将应用程序的数据暴露出去? Android提供了ContentProvider,一个程序可以通过实现一个Content provider的抽象接口将自己的数据完全暴露出去,而且Content providers是以类似数据库中表的方式将数据暴露。Content providers存储和检索数据,通过它可以让所有的应用程序访问到,这也是应用程序之间唯一共享数据的方法。要想使应用程序的数据公开化,可通过2种方法:创建一个属于你自己的Content provider或者将你的数据添加到一个已经存在的Content provider中,前提是有相同数据类型并且有写入Content provider的权限。

      如何通过一套标准及统一的接口获取其他应用程序暴露的数据?Android提供了ContentResolver,外界的程序可以通过ContentResolver接口访问ContentProvider提供的数据。

      当前篇主要说明,如何获取其它应用程序共享的数据,比如获取Android 手机电话薄中的信息。

      什么是URI?

      将其分为A,B,C,D 4个部分:
      A:标准前缀,用来说明一个Content Provider控制这些数据,无法改变的;
      B:URI的标识,它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的   类名。这个标识在<provider> 元素的 authorities属性中说明:
      <provider name=”.TransportationProvider”  authorities=”com.example.transportationprovider”  . . .  >
      C:路径,Content Provider使用这些路径来确定当前需要生什么类型的数据,URI中可能不包括路径,也可能包括多个;
      D:如果URI中包含,表示需要获取的记录的ID;如果没有ID,就表示返回全部;
      由于URI通常比较长,而且有时候容易出错,切难以理解。所以,在Android当中定义了一些辅助类,并且定义了一些常量来代替这些长字符串,例如:People.CONTENT_URI

       ContentResolver 介绍说明

      看完这些介绍,大家一定就明白了,ContentResolver是通过URI来查询ContentProvider中提供的数据。除了URI以外,还必须知道需要获取的数据段的名称,以及此数据段的数据类型。如果你需要获取一个特定的记录,你就必须知道当前记录的ID,也就是URI中D部分。

      前面也提到了Content providers是以类似数据库中表的方式将数据暴露出去,那么ContentResolver也将采用类似数据库的操作来从Content providers中获取数据。现在简要介绍ContentResolver的主要接口,如下:

      看到这里,是否感觉与数据库的操作基本一样的?就是这样的,详细解析请参考Android SQLite解析篇中的说明,不在此详细说明。

    更多相关内容
  • Android11/Andorid10分区存储中,两个应用之间如何共享文件呢?比如说我的应用生成了一个jpeg图片,想分享到微信,该怎么搞? 有三种方案: 生成到公共目录下,通过File接口分享(微信支持) 生成到公共目录下,...

    在这里插入图片描述

    在Android11/Andorid10分区存储中,两个应用之间如何共享文件呢?比如说我的应用生成了一个jpeg图片,想分享到微信,该怎么搞?

    有三种方案:

    1. 生成到公共目录下,通过File接口分享(微信支持)
    2. 生成到公共目录下,通过MediaStore接口进行分享(微信不支持)
    3. 生成到私有目录下,通过FileProvider进行分享(微信支持)

    本篇代码比较多,比起废话,Show code更靠谱。

    生成到公共目录下,File接口分享

    对于Android10的分区存储不支持此方案,而Android11的分区存储是支持的。

    • 源应用把内容写入到外部公共目录下,例子为asset下的文件复制到公共目录Pictures/下。
    //通过File的方式分享
    private fun fileShare(): String {
        val file = File(
            Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES
            ),
            "friends_file.jpg"
        )
        if (!file.parentFile.exists()) {
            file.mkdirs()
        }
        val fileOutputStream = FileOutputStream(file)
        val inputStream = this.assets.open("friends.jpg")
        val byteArray = ByteArray(1024)
        try {
            fileOutputStream.use { outputStream ->
                inputStream.use { inputStream ->
                    while (true) {
                        val readLen = inputStream.read(byteArray)
                        if (readLen == -1) {
                            break
                        }
                        outputStream.write(byteArray, 0, readLen)
                    }
                }
            }
        } catch (e: Throwable) {
            Log.e("wfeii", "fileShare:$e")
        }
    
        return file.path
    }
    
    • 目标应用获取外部公共目录文件,并读取内容到自己的外部存储中。
    private fun dealFilePath(intent: Intent?) {
        if (intent == null) {
            return
        }
        val filePath = intent.getStringExtra(FILE_PATH)
        if (filePath == null) {
            return
        }
        val fileInputStream = FileInputStream(filePath)
        val file = File(getExternalFilesDir(""), "mediaStore_file.jpg")
        if (!file.parentFile.exists()) {
            file.parentFile.createNewFile()
        }
        val fileOutputStream = FileOutputStream(file)
        val byteArray = ByteArray(1024)
        try {
            fileInputStream.use { fileInputStream ->
                fileOutputStream.use { fileOutputStream ->
                    while (true) {
                        val readLen = fileInputStream.read(byteArray)
                        if (readLen == -1) {
                            break
                        }
                        fileOutputStream.write(byteArray, 0, readLen)
                    }
                }
            }
        } catch (t: Throwable) {
            Log.e("wfeii", "dealFilePath:$t")
        }
        Toast.makeText(this, "复制成功", Toast.LENGTH_LONG).show()
    }
    

    注意:目标应用需要有READ_EXTERNAL_STORAGE权限。

    生成到公共目录下,通过MediaStore接口分享

    • 源应用把内容写入到外部公共目录下,例子为asset下的文件写入到Image的公共目录下。
    //通过MediaStore存储到公共目录再分享
    private fun mediaStore(): String? {
        val resolver = applicationContext.contentResolver
        val contentValues = ContentValues().apply {
            put(MediaStore.Images.Media.DISPLAY_NAME, "friends.jpg")
        }
    
        val uri = resolver
            .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
    
        if (uri != null) {
            val outputStream = resolver.openOutputStream(uri)
            if (outputStream == null) {
                return null
            }
            val inputStream = this.assets.open("friends.jpg")
            val byteArray = ByteArray(1024)
            try {
                inputStream.use { input ->
                    outputStream.use { output ->
                        while (true) {
                            val readLen = input.read(byteArray)
                            if (readLen == -1) {
                                break
                            }
                            outputStream.write(byteArray, 0, readLen)
                        }
                    }
                }
            } catch (e: Throwable) {
                Log.e("wfeii", "mediaStore e:$e")
            }
    
        }
        Log.e("wfeii", "mediaStore:$uri")
        return uri?.toString()
    }
    
    • 目标应用获取外部公共目录文件,并读取内容到自己的外部存储中。
    private fun dealMediaStoreUrl(intent: Intent?) {
        if (intent == null) {
            return
        }
        val mediaStoreUrl = intent.getStringExtra(MEDIA_STORE)
    
        if (mediaStoreUrl == null) {
            return
        }
        val readOnlyMode = "r"
        val fileDescriptor =
            contentResolver.openFileDescriptor(
                Uri.parse(mediaStoreUrl),
                readOnlyMode
            )?.fileDescriptor
        if (fileDescriptor == null) {
            return
        }
        val fileInputStream: InputStream = FileInputStream(fileDescriptor)
    
        val file = File(getExternalFilesDir(""), "mediaStore.jpg")
        val fileOutputStream = FileOutputStream(file)
        val byteArray = ByteArray(1024)
        try {
            fileInputStream.use { fileDescriptor ->
                fileOutputStream.use { fileOutputStream ->
                    while (true) {
                        val readLen = fileInputStream.read(byteArray)
                        if (readLen == -1) {
                            break
                        }
                        fileOutputStream.write(byteArray, 0, readLen)
                    }
                }
            }
        } catch (e: Throwable) {
            Log.e("wfeii", "dealMediaStoreUrl:$e")
        }
    
        Toast.makeText(this, "复制成功", Toast.LENGTH_LONG).show()
    }
    

    注意:目标应用需要有READ_EXTERNAL_STORAGE权限。

    生成到私有目录下,通过FileProvider分享

    1. 目标应用在manifest中声明FileProvider
    <application>
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.kuaima.sharefileforandroid11.fileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_provider_paths" />
        </provider>
    </application>
    
      1. 使用androidx时,android:name声明为"androidx.core.content.FileProvider"。未使用androidx时,android:name声明为"android.support.v4.content.FileProvider"。
      2. android:authorities声明为域名为${yourApplicationId}+FileProvider。
      3. android:exported=“false” 不能修改为true,为true时就会抛出SecurityException(“Provider must not be exported”)。
      4. android:grantUriPermissions=“true” 不能修改为false,为false时会抛出SecurityException(“Provider must grant uri permissions”)。
      5. meta-data中android:name声明为"android.support.FILE_PROVIDER_PATHS"。
      6. meta-data中android:resource用于声明那些目录可以通过FileProvider访问。
    1. 定义FileProvider可以访问的目录。在file_provider_paths.xml中我们定义如下:

    <paths>
        <external-files-path
            name="my_images"
            path="images/" />
    </paths>
    
      1. external-files-path指定共享的目录
      2. name表示共享的url的path
      3. path指定的是共享目录下那个文件夹

    下表是url,路径直接的对应关系:

    img

    1. 根据File获取url并写入内容
    private fun fileProviderShare(): String? {
        val imagePath = File(this.getExternalFilesDir(null), "images")!!
        val file = File(imagePath, "friends_file_provider.jpg")
    
        if (!file.parentFile.exists()) {
            file.parentFile.mkdirs()
        }
    
        //更具获取Url
        val contentUri: Uri =
            FileProvider.getUriForFile(this, "com.kuaima.sharefileforandroid11.fileProvider", file)
        val fileOutputStream = contentResolver.openOutputStream(contentUri)
        if (fileOutputStream == null) {
            return null
        }
        val inputStream = this.assets.open("friends.jpg")
        val byteArray = ByteArray(1024)
        try {
            fileOutputStream.use { outputStream ->
                inputStream.use { inputStream ->
                    while (true) {
                        val readLen = inputStream.read(byteArray)
                        if (readLen == -1) {
                            break
                        }
                        outputStream.write(byteArray, 0, readLen)
                    }
                }
            }
        } catch (e: Throwable) {
            Log.e("wfeii", "fileProviderShare e:$e")
        }
        Log.e("wfeii", "fileProviderShare:$contentUri")
        return contentUri.toString()
    }
    
    1. 授权临时权限
    grantUriPermission(
        "com.kuaima.destinationapp",
        Uri.parse(fileProviderPath),
        Intent.FLAG_GRANT_READ_URI_PERMISSION
    )
    
    1. 目标应用读取文件
    private fun dealFileProviderPath(intent: Intent?) {
        if (intent == null) {
            return
        }
        val fileProviderUri = intent.getStringExtra(FILE_PROVIDER_URI)
        if (fileProviderUri == null) {
            return
        }
    
        Log.e("wfeii", "dealFileProviderPath fileProviderUri:$fileProviderUri")
        val readOnlyMode = "r"
        val fileDescriptor =
            contentResolver.openFileDescriptor(
                Uri.parse(fileProviderUri), readOnlyMode
            )?.fileDescriptor
        if (fileDescriptor == null) {
            return
        }
    
        val inputStream = FileInputStream(fileDescriptor)
        val file = File(getExternalFilesDir(""), "file_provider_path.jpg")
        val fileOutputStream = FileOutputStream(file)
        val byteArray = ByteArray(1024)
        try {
            inputStream.use { fileDescriptor ->
                fileOutputStream.use { fileOutputStream ->
                    while (true) {
                        val readLen = inputStream.read(byteArray)
                        if (readLen == -1) {
                            break
                        }
                        fileOutputStream.write(byteArray, 0, readLen)
                    }
                }
            }
        } catch (t: Throwable) {
            Log.e("wfeii", "t")
        }
        Toast.makeText(this, "复制成功", Toast.LENGTH_LONG).show()
    }
    

    在分区存储中,这些就是应用间共享文件的常用的三种方案。

    参考文档

    Android 11 中的存储机制更新

    访问应用专属文件

    共享存储空间

    使用存储访问框架打开文件

    管理存储的所有文件

    FileProvider的使用

    代码地址:https://github.com/wfeii/Android11

    限于个人水平,有错误请指出,大家共同学习进步!

    扫码关注公众号,查看更多内容。
    img

    展开全文
  • Android数据存储

    千次阅读 2022-04-18 18:30:37
    存储数据 保存数据一般分为四步骤: 使用Activity类的getSharedPreferences方法获得SharedPreferences对象; 使用SharedPreferences接口的edit获得SharedPreferences.Editor对象; 通过SharedPreferences.Editor...

    Android中的数据存储

    1、SharedPreference存储(共享参数)

    1.1、使用SharedPreferences存储和读取数据的步骤

    存储数据

    保存数据一般分为四个步骤:

    1. 使用Activity类的getSharedPreferences方法获得SharedPreferences对象;
    2. 使用SharedPreferences接口的edit获得SharedPreferences.Editor对象;
    3. 通过SharedPreferences.Editor接口的putXXX方法保存key-value对;
    4. 通过过SharedPreferences.Editor接口的commit方法保存key-value对。

    读取数据

    读取数据一般分为两个步骤:

    1. 使用Activity类的getSharedPreferences方法获得SharedPreferences对象;
    2. 通过SharedPreferences对象的getXXX方法获取数据;

    用到的方法

    • 获取SharedPreferences对象
      (根据name查找SharedPreferences,若已经存在则获取,若不存在则创建一个新的)
    public abstract SharedPreferences getSharedPreferences (String name, int mode)
    

    参数
    name:命名
    mode:模式,包括
    MODE_PRIVATE(只能被自己的应用程序访问)
    MODE_WORLD_READABLE(除了自己访问外还可以被其它应该程序读取)
    MODE_WORLD_WRITEABLE(除了自己访问外还可以被其它应该程序读取和写入)

    若该Activity只需要创建一个SharedPreferences对象的时候,可以使用getPreferences方法,不需要为SharedPreferences对象命名,只要传入参数mode即可

    public SharedPreferences getPreferences (int mode)
    
    • 获取Editor对象(由SharedPreferences对象调用)
    abstract SharedPreferences.Editor edit()
    
    • 写入数据(由Editor对象调用)
    //写入boolean类型的数据
    abstract SharedPreferences.Editor   putBoolean(String key, boolean value)
    //写入float类型的数据
    abstract SharedPreferences.Editor   putFloat(String key, float value)
    //写入int类型的数据
    abstract SharedPreferences.Editor   putInt(String key, int value)
    //写入long类型的数据
    abstract SharedPreferences.Editor   putLong(String key, long value)
    //写入String类型的数据
    abstract SharedPreferences.Editor   putString(String key, String value)
    //写入Set<String>类型的数据
    abstract SharedPreferences.Editor   putStringSet(String key, Set<String> values)
    

    参数
    key:指定数据对应的key
    value:指定的值

    • 移除指定key的数据(由Editor对象调用)
    abstract SharedPreferences.Editor   remove(String key)
    

    参数
    key:指定数据的key

    • 清空数据(由Editor对象调用)
    abstract SharedPreferences.Editor   clear()
    
    • 提交数据(由Editor对象调用)
    abstract boolean    commit()
    
    • 读取数据(由SharedPreferences对象调用)
    //读取所有数据
    abstract Map<String, ?> getAll()
    //读取的数据为boolean类型
    abstract boolean    getBoolean(String key, boolean defValue)
    //读取的数据为float类型
    abstract float  getFloat(String key, float defValue)
    //读取的数据为int类型
    abstract int    getInt(String key, int defValue)
    //读取的数据为long类型
    abstract long   getLong(String key, long defValue)
    //读取的数据为String类型
    abstract String getString(String key, String defValue)
    //读取的数据为Set<String>类型
    abstract Set<String>    getStringSet(String key, Set<String> defValues)
    

    参数
    key:指定数据的key
    defValue:当读取不到指定的数据时,使用的默认值defValue

    1.2、SharedPreferences的使用

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="姓名"/>
    
        <EditText
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:padding="5dp"
            android:hint="请输入用户名"
            android:background="@android:drawable/editbox_background"/>
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="年龄:"
            android:layout_marginTop="10dp"/>
    
        <EditText
            android:id="@+id/et_age"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:padding="5dp"
            android:hint="请输入年龄"
            android:background="@android:drawable/editbox_background"/>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginTop="20dp">
    
            <Button
                android:id="@+id/btn_save"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:textColor="#fff"
                android:text="保存参数"/>
    
            <Button
                android:id="@+id/btn_resume"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:layout_weight="1"
                android:text="恢复参数"/>
        </LinearLayout>
    
    </LinearLayout>
    

    MainActivity.java

    package com.tinno.sharedpreference;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.content.SharedPreferences;
    import android.os.Bundle;
    import android.text.TextUtils;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.Toast;
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        private EditText etName,etAge;
        private Button btnSave,btnResume;
        private SharedPreferences sp;   //共享参数 ---> 本质就是一个map结构的xml结构.
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initView();
    
            sp = getSharedPreferences("configs", MODE_PRIVATE);
        }
    
        private void initView() {
            etName = (EditText) findViewById(R.id.et_name);
            etAge = (EditText) findViewById(R.id.et_age);
            btnSave = (Button) findViewById(R.id.btn_save);
            btnResume = (Button) findViewById(R.id.btn_resume);
    
            btnSave.setOnClickListener(this);
            btnResume.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.btn_save: //保存数据
                    String name = etName.getText().toString().trim();
                    String age = etAge.getText().toString().trim();
                    if (TextUtils.isEmpty(name) || TextUtils.isEmpty(age)){
                        return;
                    }
                    // 编辑器
                    SharedPreferences.Editor editor = sp.edit();
    
                    // 数据存储,只能存储简单的数据类型
                    editor.putString("name",age);
                    editor.putString("age",name);
    
                    // 提交 --> 对数据的一个保存
                    boolean commit = editor.commit();
                    if (commit){
                        Toast.makeText(this, "保存成功", Toast.LENGTH_SHORT).show();
                        etName.setText("");
                        etAge.setText("");
                    }
    
                    break;
                case R.id.btn_resume:   //恢复数据
    
                    String nameValue = sp.getString("name","");
                    String ageValue = sp.getString("age","");
    
                    etName.setText(nameValue);
                    etAge.setText(ageValue);
    
                    break;
            }
        }
    }
    

    效果

    点击保存参数获取输入框的内容,并清空输入框,点击恢复参数,将之前输入的内容再次显示在输入框中

    2、内部存储

    一说内部存储,有人可能会和内存混淆在一起,其实这两个概念很好区分,内部存储是用于持久化存储的,属于ROM,手机关机或者退出App数据是不会丢失的,而内存是RAM,退出App或者关机之后数据就会丢失。所以,内部存储不是内存。所谓的内部存储,其实是手机ROM上的一块存储区域,主要用于存储系统以及应用程序的数据。内部存储在Android系统对应的根目录是 /data/data/,这个目录普通用户是无权访问的,用户需要root权限才可以查看。不过我们可以通过Android Studio的View----Tool Windows----Device File Explorer工具来查看该目录,内部存储目录的大致结构如下所示。
    内部存储目录内部存储目录内部存储目录
    从上图可以看到,/data/data目录是按照应用的包名来组织的,每个应用都是属于自己的内部存储目录,而且目录的名称就是该应用的包名,这个目录是在安装应用的时候自动创建的,当应用被卸载后,该目录也会被系统自动删除。所以,如果你将数据存储于内部存储中,其实就是把数据存储到自己应用包名对应的内部存储目录中。每个应用的内部存储目录都是私有的,也就是说内部存储目录下的文件只能被应用自己访问到,其他应用是没有权限访问的。应用访问自己的内部存储目录时不需要申请任何权限。
    一个应用典型的内部存储目录结构如下所示。
    应用内部存储目录
    相信很多人看到内部存储的目录结构,都有似曾相识的感觉,没错我们平常经常和内部存储打交道,只不过我们不知道罢了,下面我们来看下内部存储目录下各个子目录的作用。

    app_webview:主要用于存储webview加载过程中的数据,例如Cookie,LocalStorage等。
    cache:主要用于存储使用应用过程中产生的缓存数据。
    databases:主要用于存储数据库类型的数据。我们平常创建的数据库文件就是存储在这里。
    files:可以在该目录下存储配置文件,敏感数据等。
    shared_prefs:用于存储SharedPreference文件。我们使用SharedPreference的时候只指定了文件名,并没有指定存储路径,其实SP的文件就是保存到了这个目录下。
    那么有哪些API可以获取到内部存储目录呢,我们主要是使用Context类提供的接口来访问内部存储目录。

    1.getDataDir()  //获取的目录是/data/user/0/package_name,即应用内部存储的根目录
    2.getFilesDir() //获取的目录是/data/user/0/package_name/files,即应用内部存储的files目录
    3.getCacheDir() //获取的目录是/data/user/0/package_name/cache,即应用内部存储的cache目录
    4.getDir(String name, int mode) //获取的目录是/data/user/0/package_name/app_name,如果该目录不存在,系统会自动创建该目录。
    

    Internal Storage:内部存储器。
    我们可以直接将文件保存在设备的内部存储设备上。默认情况下,保存到内部存储的文件对您的应用程序是私有的,其他应用程序无法访问它们(用户也不可以)。当用户卸载您的应用程序时,这些文件将被删除。

    创建和写入私有文件到内部存储

    String content = "abcdefghigk";
    try {
        FileOutputStream os = openFileOutput("internal.txt", MODE_PRIVATE);
        //写数据
        os.write(content.getBytes());
        os.close();//关闭文件
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    1. 调用openFileOutput(),参数:文件的名称和操作模式。这返回一个FileOutputStream。该方式创建的文件路径为:/data/data/<包名>/files/目录下。
    2. write()将字符串写入文件。
    3. close()关闭输出流。

    文件操作模式有以下四种:

    • MODE_PRIVATE 将创建文件(或替换相同名称的文件),并使其对您的应用程序是私有的。
    • MODE_APPEND 如果文件已经存在,则将数据写入现有文件的末尾,而不是将其删除。
    • MODE_WORLD_READABLE 可读(不建议)
    • MODE_WORLD_WRITEABLE 可写(不建议)

    从内部存储读取文件

    byte buffer[] = new byte[4096];
    try {
        FileInputStream in = openFileInput("internal.txt");
        //将数据读到buffer.
        int len = in.read(buffer);
        in.close();//关闭文件
        Log.i(TAG, "buffer="+new String(buffer,0,len));
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    1. 调用openFileInput()并传递要读取的文件的名称。这返回一个FileInputStream。
    2. 从文件中读取字节read()。
    3. 然后关闭流 close()。

    如果要在编译时保存应用程序中的静态文件,请将该文件保存在项目res/raw/目录中。您可以打开它 openRawResource(),传递资源ID(R.raw.)。此方法返回一个 可用于读取文件(但不能写入原始文件)的 InputStream实例。

    保存缓存文件

    如果您想缓存一些数据,而不是持久存储,那么您应该使用它getCacheDir()来打开File代表应用程序应该保存临时缓存文件的内部目录。

    当设备的内部存储空间不足时,Android可能会删除这些缓存文件以恢复空间。但是,您不应该依靠系统来清理这些文件。您应该始终自己维护缓存文件,并保持在合理的空间上限,例如1MB。当用户卸载您的应用程序时,这些文件将被删除。

    其他有用的方法

    • getFilesDir()
      获取保存内部文件的文件系统目录的绝对路径。
    • getDir()
      在内部存储空间内创建(或打开现有的)目录。
    • deleteFile()
      删除保存在内部存储上的文件。
    • fileList()
      返回应用程序当前保存的文件数组。

    内部存储的使用:

    xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        android:orientation="vertical">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
    
            <EditText
                android:id="@+id/et_name"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:hint="请输入文件名"
                android:singleLine="true"
                android:padding="10dp"
                android:background="@android:drawable/editbox_background"/>
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="保存"
                android:background="@android:drawable/edit_text"
                android:onClick="saveFile"/>
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="打开"
                android:background="@android:drawable/edit_text"
                android:onClick="openFile"/>
        </LinearLayout>
    
        <EditText
            android:id="@+id/et_content"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:padding="5dp"
            android:hint="请输入文件内容"
            android:background="@android:drawable/editbox_background"/>
    
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="删除文件"
            android:onClick="deleteFile"
            android:background="@android:drawable/edit_text"/>
    </LinearLayout>
    

    java

    package com.tinno.internalstorage;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Bundle;
    import android.text.TextUtils;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.Toast;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.charset.StandardCharsets;
    
    public class MainActivity extends AppCompatActivity {
    
        // 内部存储,openFileOutput(),openFileInput(),deleteFile()...
        // 文件的保存位置: /data/data/<包名>/files/...
        // 内部存储的特点:内部存储里的东西,会随着app的卸载而被清掉
    
        private EditText etName,etContent;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initView();
        }
    
        private void initView() {
            etName = (EditText) findViewById(R.id.et_name);
            etContent = (EditText) findViewById(R.id.et_content);
        }
    
        /**
         * 保存文件
         * @param view
         */
        public void saveFile(View view){
    
            String fileName = etName.getText().toString().trim();
            String content = etContent.getText().toString().trim();
            if(TextUtils.isEmpty(fileName) || TextUtils.isEmpty(content)){
                return;
            }
            try {
                // 打开一个用来读写的文件,该文件是与当前上下文所在的包有关,而且该方法不需要添加任何权限,因为这是内部存储
                FileOutputStream fos = openFileOutput(fileName,MODE_PRIVATE);
                fos.write(content.getBytes(StandardCharsets.UTF_8));
                fos.close();
                Toast.makeText(this, "保存成功", Toast.LENGTH_SHORT).show();
                etName.setText("");
                etContent.setText("");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 打开文件
         * @param view
         */
        public void openFile(View view){
            String fileName = etName.getText().toString().trim();
            if (TextUtils.isEmpty(fileName)){
                return;
            }
            try {
                // 得到一个用来只读的输入流
                FileInputStream fis = openFileInput(fileName);
                byte[] buffer = new byte[fis.available()];
                fis.read(buffer);
                fis.close();
                etContent.setText(new String(buffer));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        /**
         * 删除文件
         * @param view
         */
        public void deleteFile(View view){
            String fileName = etName.getText().toString().trim();
            if (TextUtils.isEmpty(fileName)){
                return;
            }
            // 删除当前上下文中指定名称的文件
            boolean deleteFile = deleteFile(fileName);
            if(deleteFile){
                Toast.makeText(this, "删除成功", Toast.LENGTH_SHORT).show();
                etName.setText("");
                etContent.setText("");
            }
        }
    }
    

    效果

    可以根据之前写入的文件名,查询该文件名的内容

    在这里插入图片描述

    3、外部存储

    External Storage

    每个Android兼容设备都支持一个共享的“外部存储”,您可以使用它来保存文件。这可以是可移动存储介质(例如SD卡)或内部(不可移动)存储。保存到外部存储器的文件是可读的,用户可以在使用USB大容量存储在计算机上传输文件时进行修改。

    注意:如果用户将外部存储器安装在计算机上或删除了介质,则外部存储可能会变得不可用,并且您保存到外部存储器的文件没有执行安全性。所有应用程序都可以读取和写入放在外部存储上的文件,用户可以将其删除。

    访问外部存储

    为了在外部存储上读取或写入文件,您的应用程序必须获取 **READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE**系统权限。例如:

    <uses-permission android:name = “android.permission.READ_EXTERNAL_STORAGE” />
    <uses-permission android:name = “android.permission.WRITE_EXTERNAL_STORAGE” />
    

    如果您需要读取和写入文件,则需要仅请求 WRITE_EXTERNAL_STORAGE权限,因为它隐含地还需要读取访问权限。

    注意:从Android 4.4开始,如果您正在阅读或仅写入您的应用程序的私有文件,则不需要这些权限。

    检查媒体可用性

    在对外部存储进行任何处理之前,应始终调用**getExternalStorageState()**以检查介质是否可用。介质可能安装到计算机,丢失,只读或处于某种其他状态。这里有几种可用于检查可用性的方法:

    /* Checks if external storage is available for read and write */
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }
    
    /* Checks if external storage is available to at least read */
    public boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) ||
            Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }
    

    该getExternalStorageState()方法返回您可能想要检查的其他状态,如媒体是否被共享(连接到计算机),完全丢失,已被删除等等。您可以使用这些来通知用户更多信息您的应用程序需要访问媒体。
    保存可与其他应用共享的文件

    外部存储的方法

    Environment.getExternalStorageDirectory().getAbsolutePath():/storage/emulated/0 这个方法是获取外部存储的根路径
    Environment.getExternalStoragePublicDirectory().getAbsolutePath():/storage/emulated/0 这个方法是获取外部存储的根路径
    getExternalFilesDir:/storage/emulated/0/Android/data/包名/files 获取某个应用在外部存储中的files路径
    getExternalCacheDir:/storage/emulated/0/Android/data/包名/cache 获取某个应用在外部存储中的cache路径
    

    注意方法3,4在android4.4之前可能返回为null,因为用户可能没有插入外部存储的SD卡,但是4.4后手机自带外部存储,所以不会返回null

    从媒体扫描器隐藏您的文件

    通常,用户可以通过应用程序获取的新文件应该保存到其他应用可以访问的设备上的“公共”位置,用户可以轻松地从设备中复制它们。这样做时,你应该使用的共享的公共目录,如之一Music/,Pictures/和Ringtones/。

    为了得到一个File较合适的公共目录,呼叫getExternalStoragePublicDirectory(),通过它你想要的目录的类型,如 DIRECTORY_MUSIC,DIRECTORY_PICTURES, DIRECTORY_RINGTONES,或其他。通过将文件保存到相应的媒体类型目录,系统的媒体扫描器可以对系统中的文件进行适当的分类(例如,铃声在系统设置中显示为铃声,而不是音乐)。

    例如,以下是在公共图片目录中为新相册创建目录的方法:

    public File getAlbumStorageDir(String albumName) {
        // Get the directory for the user's public pictures directory.
        File file = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), albumName);
        if (!file.mkdirs()) {
            Log.e(LOG_TAG, "Directory not created");
        }
        return file;
    }
    

    保存专用的文件

    如果您正在处理不适用于其他应用程序的文件(例如只有您的应用程序使用的图形纹理或声音效果),则应通过调用在外部存储上使用专用存储目录getExternalFilesDir()。此方法还需要一个type参数来指定子目录的类型(如DIRECTORY_MOVIES)。如果您不需要特定的媒体目录,请传递null以接收您应用的私有目录的根目录。

    从Android 4.4开始,在应用程序的私有目录中读取或写入文件不需要READ_EXTERNAL_STORAGE 或WRITE_EXTERNAL_STORAGE 权限。因此,您可以通过添加maxSdkVersion 属性来声明只能在较低版本的Android上请求权限:

    <uses-permission android:name = “android.permission.WRITE_EXTERNAL_STORAGE” android:maxSdkVersion = “18” />
    

    注意: 当用户卸载您的应用程序时,此目录及其所有内容将被删除。此外,系统介质扫描程序不会读取这些目录中的文件,因此它们无法从MediaStore内容提供程序访问。因此,您不应将这些目录用于最终属于用户的媒体,例如您的应用程序捕获或编辑的照片,或用户使用您的应用购买的音乐 - 这些文件应保存在公共目录中。

    保存缓存文件

    打开一个File,该File代表您应该保存缓存文件的外部存储目录,请调用getExternalCacheDir()。如果用户卸载您的应用程序,这些文件将被自动删除。

    提示: 要保留文件空间并保持应用程序的性能,请务必仔细管理缓存文件,并在整个应用程序的生命周期中删除不再需要的缓存文件。

    4、数据库存储(SQLite数据库)

    一、SQLite数据库简介

    SQLite是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入 式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持 Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如Tcl、PHP、Java、C++、.Net等,还有ODBC接口,同样比起 Mysql、PostgreSQL这两款开源世界著名的数据库管理系统来讲,它的处理速度比他们都快。

    二、SQLite数据类型

    一般数据采用的固定的静态数据类型,而SQLite采用的是动态数据类型,会根据存入值自动判断。SQLite具有以下常用的数据类型:

    (1)NULL: 这个值为空值

    (2)VARCHAR(n):长度不固定且其最大长度为 n 的字串,n不能超过 4000。

    (3)CHAR(n):长度固定为n的字串,n不能超过 254。

    (4)INTEGER: 值被标识为整数,依据值的大小可以依次被存储为1,2,3,4,5,6,7,8.

    (5)REAL: 所有值都是浮动的数值,被存储为8字节的IEEE浮动标记序号.

    (6)TEXT: 值为文本字符串,使用数据库编码存储(TUTF-8, UTF-16BE or UTF-16-LE).

    (7)BLOB: 值是BLOB数据块,以输入的数据格式进行存储。如何输入就如何存储,不改变格式。

    (8)DATA :包含了 年份、月份、日期。

    (9)TIME: 包含了 小时、分钟、秒。

    三、Android之SQLiteOpenHelper介绍

    Android 为了让我们能够更加方便地管理数据库,专门提供了一个SQLiteOpenHelper 帮助类,借助这个类就可以非常简单地对数据库进行创建和管理

    public abstract class SQLiteOpenHelper;

    SQLiteOpenHelper 是一个抽象类,这意味着如果我们想要使用它的话,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper 中有两个抽象方法,分别是onCreate()和onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。

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

    SQLiteOpenHelper 中还有两个非常重要的实例方法, getReadableDatabase() 和getWritableDatabase()。这两个方法都可以创建

    或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对

    象。不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据

    库,而getWritableDatabase()方法则将出现异常。

    public SQLiteDatabase getReadableDatabase();
    
    public SQLiteDatabase getWritableDatabase()
    

    SQLiteOpenHelper 中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。

    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version);
    

    这个构造方法中接收四个参数:
    第一个参数是Context,这个没什么好说的,必须要有它才能对数据库进行操作。
    第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。
    第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null。
    第四个参数表示当前数据库的版本号, 可用于对数据库进行升级操作。

    构建出SQLiteOpenHelper 的实例之后,再调用它的getReadableDatabase()或getWritableDatabase()方法就能够创建数据库了,数据库文件会存放在/data/data//databases/目录下。此时,重写的onCreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。

    下面,将详细讲解SQLiteOpenHelper的使用操作。

    四、创建数据库

    public class DBHelper extends SQLiteOpenHelper {
    
        // 数据库名字
        private static final String DB_NAME = "person.db";
        // 数据库版本 version >= 1
        private static final int DB_VERSION = 1;
    
        // 指定了数据库的名称,版本信息
        public DBHelper(@Nullable Context context) {
            super(context, DB_NAME, null, DB_VERSION);
        }
    
        /**
         * 创建数据库中的表
         * @param db
         */
        @Override
        public void onCreate(SQLiteDatabase db) {
    
            // SQLite数据库里的字段一般是不区分类型的,但是主键除外,主键必须是整形
            String sql = "CREATE TABLE person (_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,name CHAR(10),nickname CHAR(10))";
            db.execSQL(sql);
        }
    
        /**
         * 数据库升级的方法
         * @param db
         * @param oldVersion
         * @param newVersion
         */
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            if (newVersion > oldVersion){
                String sql = "DROP TABLE IF EXITS person";
                db.execSQL(sql);
    
                // 先删表在建表
                onCreate(db);
            }
        }
    }
    

    在MainActivity 中 创建数据库和表:

    需要调用以下两个方法之一,数据库和表才能真正创建出来
    正常情况下,getReadableDatabase()getWritableDatabase() 得到的结果是一样的

    非正常情况,比如明确要求以只读的方式来打开数据库,或者磁盘满了,此时getReadableDatabase()得到的只是只读的数据库

    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            DBHelper dbHelper = new DBHelper(this);
    
            // 需要调用以下两个方法之一,数据库和表才能真正创建出来
            // 正常情况下,getReadableDatabase() 和 getWritableDatabase()得到的结果是一样的
            // 非正常情况,比如明确要求以只读的方式来打开数据库,或者磁盘满了,此时getReadableDatabase()得到的只是只读的数据库
            //SQLiteDatabase readableDatabase = dbHelper.getReadableDatabase();
            SQLiteDatabase writableDatabase = dbHelper.getWritableDatabase();
        }
    }
    

    使用abd查看数据的存储位置:

    1. 进入数据库路径:

      cd data/data/ <你的项目名> / databases/

    2. 进入sqlite数据库

      sqlite3 数据库名

    五、数据库中的CURD(增删改查)

    1、添加数据的方法

    • execSQL方法进行添加数据

       /**
       * SQL插入语句:
       * INSERT INTO Book(name,author,pages,price) VALUES
      * ("The Da Vinci Code" ,"Dan Brown",454,16.96);
      */
      db.execSQL("INSERT INTO Book(name,author,pages,price) VALUES(?,?,?,?",
      new String[]{"The Lost Symbol", "Dan Brown", "510", "19.95"});
      
    • 使用SQLiteDatabase 中提供了一个 insert() 方法,这个方法就是专门用于添加数据的。

      insert() 方法接收三个参数,第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般用不到这个功能,直接传入 null 即可。第三个参数是一个 ContentValues 对象,它提供了一系列的 put() 方法重载,用于向 ContentValues 中添加数据,只需要将表中的每个列名及相对应的待添加数据传入即可。

      public void onClick(View v) {
                      //获取 SQLiteDatabase 对象
                      SQLiteDatabase db = dbHelper.getWritableDatabase();
                      //使用ContentValues 对数据进行组装
                      ContentValues values = new ContentValues();
                      //开始组装第一条数据
                      values.put("name", "The Da Vinci Code");
                      values.put("author", "Dan Brown");
                      values.put("pages", 454);
                      values.put("price", 16.96);
                      //插入第一条数据
                      db.insert("Book", null, values);
                      values.clear();
                      //开始组装第二条数据
                      values.put("name", "The Lost Symbol");
                      values.put("author", "Dan Brown");
                      values.put("pages", 510);
                      values.put("price", 19.95);
                      //插入第二条数据
                      db.insert("Book", null, values);
      }
      

      这里只对Book表里其中四列的数据进行了组装,id并没有赋值。因为创建表的时候就将 id 列设置为自动增长了,它的值会在入库的时候自动生成,不需要手动赋值。

    2、删除数据的方法

    • execSQL方法进行添加数据

              /**
               *SQL删除语句:
               * DELETE FROM Book WHERE pages > 500;
               */
              db.execSQL("DELETE FROM Book WHERE pages > ?",new String[]{"500"});
      
    • SQLiteDatabase 中提供了一个 delete()方法专门用于删除数据,这个方法接收三个参数,第一个参数仍然是表名,第二、第三个参数用于约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。

                  public void onClick(View v) {
                      SQLiteDatabase db = dbHelper.getWritableDatabase();
                      db.delete("Book", "pages > ?", new String[]{"500"});
                  }
      

    3、修改(更新)数据的方法

    • execSQL方法进行添加数据

              /**
               * SQL更新语句:
               * UPDATE Book SET price = 10.99 WHERE name = "The Da Vinci Code" ;
               */
              db.execSQL("UPDATE Book SET price = ? WHERE name = ?",
                      new String[]{"10.99", "The Da Vinci Code"});
      
    • SQLiteDatabase 中提供了一个非常好用的 update() 方法用于对数据进行更新,

      这个方法接收四个参数,第一个参数和 insert()方法一样,也是表名,在这里指定去更新哪张表里的数据。第二个参数是 ContentValues 对象,把要更新的数据在这里组装进去。第三、第四个参数用于去约束更新某一行的数据,不指定的话默认就是更新所有行。

                  public void onClick(View v) {
                      SQLiteDatabase db = dbHelper.getWritableDatabase();
                      ContentValues values = new ContentValues();
                      values.put("price", 10.99);
                      //?是一个占位符,通过字符串数组为每个占位符指定相应的内容
                      db.update("Book", values, "name = ?", new String[]{"The Da Vinci Code"});
                  }
      

    4、查询数据的方法

    • execSQL方法进行添加数据

              /**
               * SQL查询语句:
               * SELECT * FROM BOOK ;
               */
              db.rawQuery("SELECT * FROM BOOK", null);
      
    • SQLiteDatabase 中还提供了一个 query() 方法用于对数据进行查询。这个方法的参数非常复杂,最短的一个方法重载也需要传入七个参数。

      query()方法参数及对应SQL:

      table:指定查询的表名,对应 from table_name

      columns:指定查询的列名,对应 select column1,column2 …

      selection:指定 where 的约束条件,where column = value

      selectionArgs:为 where 中的占位符提供具体的值

      groupBy:指定需要分组的列,group by column

      having:对分组后的结果进一步约束,having column = value

      orderBy:指定查询结果的排序方式,order by column

      虽然 query()方法的参数非常多,但是不必每条查询语句都指定上所有的参数,多数情况下只需传入少数几个参数就可以完成查询操作了。

      调用 query()方法后会返回一个 Cursor 对象,查询到的所有数据都将从这个对象中取出

                  public void onClick(View v) {
                      SQLiteDatabase db = dbHelper.getWritableDatabase();
                      //查询Book表中的所有数据
                      Cursor cursor = db.query("Book", null, null, null, null, null, null, null);
                      //遍历Curosr对象,取出数据并打印
                      while (cursor.moveToNext()) {
                          String name = cursor.getString(cursor.getColumnIndex("name"));
                          String author = cursor.getString(cursor.getColumnIndex("author"));
                          int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                          double price = cursor.getDouble(cursor.getColumnIndex("price"));
                          Log.d("woider", "Book Name:" + name + " Author:" 
                                  + author + " Pages:" + pages + " Price:" + price);
                      }
                      //关闭Cursor
                      cursor.close();
                  }
      

    5、网络存储

    不同存储方法的使用场景

    1、SharedPreference存储:简单的信息……

    2、内部存储:保存在app内部存储空间,非公开……

    3、外部存储:公共空间……

    4、数据库存储:结构化数据……

    5、网络存储:云……

    展开全文
  • 数据治理、共享应用

    千次阅读 2019-07-23 11:47:31
    如何让数据资产工具更好的用户体验,实现数据“好找、好用、好看、实时和共享”,需借助大数据、云搜索、微应用等先进技术,搭建企业数据资产管理体系,推动企业数据资产管理规范和创新,丰富数据应用与消费工具,...

    如何让数据资产工具更好的用户体验,实现数据“好找、好用、好看、实时和共享”,需借助大数据、云搜索、微应用等先进技术,搭建企业数据资产管理体系,推动企业数据资产管理规范和创新,丰富数据应用与消费工具,提升了数字资产应用价值,解决了企业数据资产查找难,应用难,管理难等问题,实现了企业数据价值挖掘及数据资产变现升值。

    主数据管理是数据资产管理最重要和核心的内容,本文通过“一平台、两体系、三性特征、四个统一、五个超越、六类服务 ”应用场景,比较体系化详尽的介绍主数据治理、共享和应用全过程。(本文谈数据管理对象范围主要涉及到参考数据、主数据、指标数据)

     

    主数据管理要点概览

    主数据是数据之源,是数据资产管理的核心,是信息系统互联互通的基石,是信息化和数字化的重要基础。

    主数据指满足跨部门业务协同需要的、反映核心业务实体状态属性的基础信息。主数据相对交易数据而言,属性相对稳定,准确度要求更高,唯一识别。

    主数据管理是一系列规则、应用和技术,用以协调和管理与企业的核心业务实体相关的活动。通俗来讲,良好的主数据治理、共享和应用主要包含:搭建一个数据治理平台、建立两个体系、实现主数据三性特征、达到四个统一、实现五个超越、提供六类服务。

    图1 一体化数据治理、共享和应用全景图

    1)一平台:搭建一个一体化数据治理和共享平台;

    2)两体系:建立两个体系(数据标准体系和保障体系);

    3)三性特征:确保主数据三性特征(唯一性、准确性、共享性);

    4)四个统一:达到四个统一(统一标准、统一来源、统一接口、统一服务);

    5)五个超越:实现五个超越(超越部门、超越流程、超越主题、超越系统、超越技术);

    6)六类服务:提供六种数据服务(数据订阅/分发服务、主数据查询/申请服务、数据调用API服务、公共数据资源池、数据资源服务、数据即时服务)。

    一体化数据治理、共享和应用详解

    1.1

    一平台

    建立企业级的、一体化的数据治理和共享平台,确保数据资源中心的数据质量和安全管理。详细功能描述可以参照数据治理平台工具前世今生

    图2 一体化数据治理与服务平台功能架构

    基于Spring boot框架, 引入Eureka、Zuul、Feign、Ribbon等Spring cloud相关组件,形成微服务解决方案,前端页面应用服务实现前后端分离。

    图3 数据治理平台技术框架

    云计算为数据管理工具提供了能够满足“共享服务”功能的新的架构模式,采用微服务技术满足数据管理工具的高可用性、稳定性和易用性。人工智能为主数据清洗提供了自动化思路,利用自然语言处理及数据标准库提升数据质量。

    基于统一技术架构、统一指标数据驱动的元数据(业务元数据、管理元数据、技术元数据等)、统一治理工具、统一安全管控,通过不同的模块组合,形成不同的数据服务和治理解决方案;

    图4 一体化数据治理和服务应用场景

    实现数据资产“三全管理”:

    1)全生命周期(时间):基于数据指标驱动的元数据的数据资产全过程管理,采集、存储、应用及管理过程的全记录与监控。权衡效率和需求之间的关系,合理分级存储和保留、销毁数据;

    2)全流程(空间):基于数据指标驱动的、元数据的数据资产溯源管理(血缘与影响分析),数据来源、存储位置、处理方式、流转过程、安全稽查规则,能追本溯源的发现所有资产的“前世今生”。

    3)全景式(场景):基于指标数据驱动的资产全场景视图,从应用场景的维度,既有全局规划的管理者,也有关注细节定义的使用者,还有加工、运维的开发者,提供多层次的图形化展示,满足应用场景的图形查询和辅助分析。

    两个体系

    2.1

    数据管理体系

    图5 数据管理体系架构

    数据管理体系包含:数据标准管理体系、数据管控体系、数据技术服务体系、数据质量要求、数据安全要求等。

    1)数据标准管理体系包含业务标准(编码规则、分类规则、描述规则等)、数据模型标准。数据标准管理体系在建设梳理的过程中,一般会衍生出一套代码体系表或称主数据资产目录。数据管理标准体系是数据管理工作的重中之重,通过主数据标准化,才能为实现部门和系统间的数据集成和共享,打通企业横向产业链和纵向管控奠定数据基础。

    2)数据管控体系包括主数据管理组织、制度、流程、应用及管理评价五部分。

    3)数据技术服务体系:数据管理工具及技术服务体系。

    4)数据质量要求:数据质量标准、质量评估等。

    5)数据安全要求:数据分类、分级授权,数据模型等管理。

    2.2

    数据运维体系

    建五位一体数据运维服务体系,加强应用推广的组织和培训指导,有序推进数据共享、应用

    图6 五位一体数据运维服务体系

    主数据三性特征

    3.1

    唯一性

    在一个系统、一个平台,甚至一个企业范围内,主数据实体要求具有唯一标识即数据编码,同名同义,保证同一个对象在应用的唯一性,如:统一员工和组织主数据,对所有系统的员工和组织进行规范。

    3.2

    准确性

    主数据实体内容正确性,数据内容符合预期, 真实反映被描述对象;格式合规性,数据格式 (包括数据类型、数值范围、数据取值) 。进入主数据管理的数据实体一定要确保的准确性,不能同名不同义。在主数据日常管理中,需要优先从权威途径获得主数据,如将国家各部委基础库以及企业通用主数据库(“人口基础信息库”、“法人单位基础信息库”、“自然资源和空间地理基础信息库”、“宏观经济信息数据库”)融合连通,为企事业单位提供服务。

    3.3

    共享性

    跨部门、跨系统高度共享的数据,可以被多个认识主体接收和利用。明确主数据的获取来源、加快数据的获取效率催生了跨业务协同和跨系统共享的管理实施模式,这些概念使得各自为政的信息系统在共享整合过程中有据可循、有标可依。

    图7 主数据代码库

    支持企业多业务类型、多地域经营的应用,在流程规范、系统集成、主题共享、系统数据一致性等方面都需要通过数据标准化来支撑,从而最终提高管理效率,加强管控落地。

    图8 数据共享性示意图

    数据治理工作在提升企业整体价值的同时,也为企业内部的数据共享等具体提供了良好的和可持续的数据基础,为数据的进一步挖掘和分析夯实基础。

    四个统一

    4.1

    统一标准

    在组织范围统一了数据标准,所有成员单位都遵守同样的数据标准,使标准有法可依;

    4.2

    统一来源

    统一数据产生来源,统一配码,确保数据唯一性,避免重复填报、不一致;

    4.3

    统一接口

    采用数据总线的集成方式、对数据集成工作进行标准化  的统一化处理,简化工作,避免了网状结构的对接复杂接口。

    4.4

    统一服务

    为目标系统和应用提供统一数据服务,避免了分散管理,数据不一致。

    五个超越

    5.1

    超越部门

    超越部门:主数据不是那种局限于某个具体职能部门的数据库。主数据是满足跨部门业务协同需要的,是各个职能部门在开展业务过程中都需要的数据,是所有职能部门及其业务过程的“最大公约数据”。

    5.2

    超越流程

    超越流程:主数据不依赖于某个具体的业务流程,但却是主要业务流程都需要的。主数据的核心是反映对象的状态属性,它不随某个具体流程而发生改变,而是作为其完整流程的不变要素。

    5.3

    超越主题

    超越主题:与信息工程方法论中通过聚类方法选择主题数据不同,主数据是不依赖于特定业务主题却又服务于所有业务主题的有关业务实体的核心信息。例如:物料主数据,它有自身的自然属性,如:规格、材质,也有业务赋予的核心属性,如:设计参数、工艺参数、采购、库存要求、计量要求、财务要求等。同时,主数据也要服务于业务,可谓是———从业务中来到业务中去。

    5.4

    超越系统

    超越系统:主数据管理系统是信息系统建设的基础,应该保持相对独立,它服务于但是高于其它业务信息系统,因此对主数据的管理要集中化、系统化、规范化。

    5.5

    超越技术

    超越技术:由于主数据要满足跨部门的业务协同,因而必须适应采用不同技术规范的不同业务系统,所以主数据必须应用一种能够为各类异构系统所兼容的技术条件。从这个意义上讲,面向微服务架构为主数据的实施提供了有效的工具。在不同环境、不同场景下,主数据的技术是可以灵活应对的。主数据的集成架构是多样的,如:总线型结构、星型结构、端到端结构;集成技术也是多样的,如:webservice、REST、ETL、MQ、kafka等;不论是架构还是技术,没有最好的只有更合适的。企业在做技术选型的时候,要充分考虑企业的核心业务需求和未来的发展要求去构建自身的主数据技术体系。

    六种数据服务

    以下为6种比较常用的数据服务方式,其中,1-4是主数据应用的服务,5-6已经超越主数据的概念,可以针对交易数据,指标数据提供数据共享服务。

    图9 数据服务6种方式

    6.1

    数据订阅/分发服务

    简单来讲,就是传统的主数据订阅分发模式,通常通过ESB来分发;

    图10 主数据订阅分发模式

    将所有类型的主数据注册到ESB平台上,各目标系统提供接收各类主数据的接口,到ESB平台自主订阅相应的主数据,所有目标系统通过ESB平台订阅规范(参照ESB平台订阅文档)即可完成数据的订阅服务。

    6.2

    主数据查询/申请服务

    6.2.1主数据查询服务:

    基于页面的查询服务,通常是提供给人来检索的,将系统内部的查询页面或者外部企查查的相关页面封装成服务,供其他系统嵌套调用;

    图11 主数据查询服务

    数据治理平台面向企业所有终端用户的查询封装成服务,集成到各应用系统,各应用系统可以方便快捷地查询各类主数据,提高查询效率,更好的用户体验。

    针对外部数据来源(如:企查查),数据治理平台首先集成企查查相关页面,其他各应用系统再与数据治理平台进行集成,减少对接成本。

    6.2.2主数据申请服务:

    主数据将所有数据的申请功能封装成服务,供应用系统调用。当业务系统有添加主数据的需求时,不需要登陆主数据平台,只需要正常在业务系统提交新增申请,就可以以任务的形式提交到数据治理平台,数据治理平台处理完成后,以消息的形式反馈至业务系统;此服务部署在API网关上。

    图12 主数据申请服务

    应用系统将主数据申请功能封装成服务,供各应用系统调用。主数据申请服务注册在ESB数据服务总线平台上,提供主数据申请服务的数据类型如港口、外部单位,具体调用地址各应用系统可联系集成平台。业务系统通过调用申请服务给主数据传递一个申请任务后,由主数据运维人员进行处理,处理完成后以消息形式反馈至业务系统。

    6.3

    数据调用API服务

    指定数据类型,通过关键字查询数据详细信息。基于接口层面的点对点的方式的查询服务(模糊查询、精确查询、组合查询),通常时提供给系统接口调用的,业务系统指定关键字来调用;此服务部署在API网关上;

    图13 数据调用API服务

    6.4

    公共数据资源池

    开放主数据平台基础库只读权限,各应用系统可直接查询调用。公共数据资源池相当于主数据生产库的一个镜像,业务系统可直接访问,只有只读权限。一般用于业务系统需要进行大量数据初始化操作的时候采用这种方式;

    图14 公共数据资源池

    该资源池数据库与主数据生产库具有实时同步机制,业务系统直接调用主数据系统的主数据,真正确保唯一源头,实现基础数据准确和一致性。具体对接方式一般是业务系统确认采取这种方式后,指明需要哪几类数据,我们再给出访问资源池数据库的连接信息,字段说明等。

    6.5

    数据资源服务

    通过相关工具(Sqoop等)将源系统的数据(HR、财务、业务等)抽取到大数据平台,经过整合、清洗、归并后形成各种主题数据,对外提供不同的数据服务(主数据、交易数据、指标数据);数据资源服务是各类数据高级应用,是将源系统数据大集中在数据资源中心,通过大数据技术工具,提供各类数据自助式服务。

    6.6

    数据即时服务

    基于ES(ElasticSearch)搜索服务器,面向所有业务系统提供快速查询检索的服务。主要原理是将不同类型的全量主数据同步到ES存储服务器中,然后业务通过ES提供的API接口进行查询,解决了大数据量查询时,查询效率比较低的问题,提供了数据的及时服务。

    ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

    展开全文
  • 共享存储数据库集群

    千次阅读 2020-12-31 17:22:38
    DM 共享存储数据库集群,允许多个数据库实例同时访问、操作同一数据库, 具有高可用、高性能、负载均衡等特性。 DMDSC 支持故障自动切换和故障自动重加入,某一个数据库实例故障后,不会导致数据库服务无法提供。 本...
  • 众所周知,数据存储在个应用中都会用到,那所用到的技术应该怎么选呢,这里Android给开发者提供了几种方法去保存常用应用数据,至于你想选择哪一种方式,取决于你的特定需求;例如这个数据是本应用私有的还是跟...
  • (1)ContentProvider 实现 通过 Uri 实现 对 数据的增删改查; (2)可以跨应用 调用数据; (3)ContentProvider 通过 ... (5)ContentResolver 请求数据的App封装,后通过 生成数据App端提供的Uri地址 实
  • 单机环境下我们session是存储在应用服务的内存,但是分布式环境 下,这种存储在应用服务器内存的方案显然不能实现session共享。本次我们将介绍SpringBoot+Redis实现分布式环境下Session共享方案。
  • 第1章 数据存储抽象概述 1.1 数据共享数据持久化基本概念 1.2 不同Pod之间的数据共享诉求与可能的方案 第2章 网络分布式文件系统 2.1网络文件系统NFS 2.2 Gluster File System 2.3 Ceph 第3章 网络分布式...
  • 0、前言 当前,大部分企业不再建设从源数据采集到分析应用的烟囱式系统,更...阿里的中台是从管理的角度出发,以中台事业部集中数据搜索,技术及产品,数据共享多个部门的功能。其他组织或企业建设数据中台不一定需
  • 企业信息化建设过程,由于各业务系统建设和实施数据管理系统的阶段性、技术性以及其它经济和人为因素等因素影响,导致企业发展过程积累了大量采用不同存储方式的业务数据,包括采用的数据管理系统也大不相同...
  • 实际应用中,一个会话应该有多个请求,根据HTTP协议无状态的特点,会话的每个请求之间无法共享数据,但是实际应用多个请求之间是需要共享数据,因此,我们需要解决解决一个会话多个请求之间如何共享数据的问题 ...
  • 万字详解数据仓库、数据湖、数据中台和湖仓一体

    千次阅读 多人点赞 2022-02-22 09:18:01
    数字化转型浪潮卷起各种新老概念满天飞,数据湖、数据仓库、数据中台轮番朋友圈刷屏,有人说“数据中台算啥,数据湖才是趋势”,有人说“再见了数据湖、数据仓库,数据中台已成气候”…… 企业还没推开数字化...
  • Android存储数据的5种方式

    千次阅读 2021-06-02 13:59:54
    使用键值对的方式存储数据2.通常用于:保存用户的偏好设置、选择是否保存密码、记录文档阅读的位置等3.实现方式(写入):a).获取SharedPreferences对象 getSharedPreferences("文件名",MODE_PRIVATE/MODE_MULTI_...
  • servlet数据存储

    千次阅读 多人点赞 2019-06-14 10:24:54
    servlet基础,我们: 用以下几种方式实现数据存储共享: 1)客户端页面和服务器端程序之间,用request...3)同一servlet对象,可以用servletConfig(xml)来共享数据(主要是获取配置信息) getServ...
  • 数据中台之结构化大数据存储设计

    千次阅读 2019-08-21 12:32:57
    这也是为何目前大多数企业都构建数据中台的原因,数据处理的技术已经是核心竞争力。完备的技术架构中,通常也会由应用系统以及数据系统构成。应用系统负责处理业务逻辑,而数据系统负责处理数据。 传统的...
  • 当一个应用程序Android安装后,我们使用应用的过程会产生很的数据,应用都有自己的数据,那么我们应该如何存储数据呢?数据存储方式Android 的数据存储有5种方式:1. SharedPreferences存储数据  ...
  • from: http://blog.csdn.net/heiyeshuwu/article/details/7363828 来源:...共享内存是一种相同机器应用程序之间交换数据的有效方式。一进程可创
  • Linux HA集群——共享存储

    千次阅读 2018-05-21 17:55:28
    为什么需要共享存储 绝大多数集群都使用共享存储。...而当数据时动态的而且频繁变化,那么就需要共享存储了。通常,集群资源使用三类文件:一、二进制可执行文件,最好分别安装节点上。这...
  • 为什么要引入锁多个用户同时对数据库的并发操作会带来以下数据不一致的问题:丢失更新A,B两个用户读同一数据并进行修改,其中一个用户的修改结果破坏了另一个修改的结果,比如订票系统脏读A用户修改了数据,随后...
  • Android数据存储几种方式用法总结

    千次阅读 2016-12-11 12:16:11
    Android数据存储几种方式用法总结 ... Android提供了5种方式来让用户保存持久化应用程序数据。根据自己的需求来做选择,比如数据是否是应用...① 使用SharedPreferences存储数据  ② 文件存储数据 ③ 
  • 数据共享与交换让交通行业协同效率快速提升。
  • Redis基本数据类型前言为什么需要NoSql数据库什么是RedisRedis基本知识介绍常用数据库操作命令Redis数据类型1.Binary-safe strings(二进制安全字符串)常用命令应用场景2.Lists(列表)常用命令3.Sets(集合)常用命令4....
  • 大数据时代,凡是AI类项目的落地,都需要具备数据、算法、场景、计算力四基本元素,缺一不可。...目前,外界与业内很多人对于数据中台的理解存在误区,一直只是强调技术的作用,强调技术对于业务的推动作用
  • Android数据存储存储路径

    千次阅读 2019-10-04 14:13:32
    Setting/Application info里面,可以看到每个应用程序,都有Clear data和Clear cache选项。 具体这些目录哪里呢? 用adb连接上设备。如果是连接真实设备,需要有设备的root权限。 cd data/data 这个目录下...
  • 大数据:70多个网站让你免费获取大数据存储库 你是否需要大量的数据来检验你的APP性能?最简单的方法是从网上免费数据存储库下载数据样本。但这种方法最大的缺点是数据很少有独特的内容并且不一定能达到预期的结果...
  • 大数据平台、从数仓 到 数据中

    千次阅读 2020-05-06 11:31:06
    数据应用到各个角落,除了之前可以支撑的决策分析以外,大数据与线上事务系统(OLTP)的联动场景非常,比如我们电商平台查询个人所有历史订单,再比如一些刷单、反作弊的实时拦截,以及一些实时推荐等,这些都是...
  • 大数据

    千次阅读 多人点赞 2020-08-28 11:17:11
    尽管数据中台的文章很,但是一千人眼里有一千个数据中台,到底什么是数据中台?数据中台包含什么? 当企业需要数据化转型、精细化运营,进而产生大规模数据应用需求的时候,就需要建设数据中台。数据中台是高质量...
  • 现代数据环境下,如何做数据集成?这11靠谱实践收藏了
  • 共享内存是一种相同机器应用程序之间交换数据的有效方式。一进程可创建一可供其他进程访问的内存段,只要它分配了正确的权限。每内存段拥有一惟一的 ID(称为 shmid),这 ID 指向一物理内存区域...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 440,705
精华内容 176,282
关键字:

在多个应用中读取共享存储数据时