[Android] Common data transfer methods

Demo:https://github.com/Gamin-fzym/DataTransferDemo

1.Intent

When sending an Intent from page A to page B, you can attach data to the Intent through the putExtra() method of the Intent.

In page B, obtain the passed data through the Intent's getXXXExtra() method.

1).Send Intent on page A

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.example.datatransferdemo.databinding.ActivityMainBinding
import com.example.datatransferdemo.pageb.PageB1


class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    // 获取返回结果,在Activity或Fragment中定义
    private val someActivityResultLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            // 执行成功后的操作
            val intent: Intent? = result.data
            when (intent?.tag) {
                "PageB1" -> {
                    var resultValue = intent?.getStringExtra("result_key")
                }
                "PageB2" -> {
                    val bundle = intent?.extras
                }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 1.Intent
        binding.button1.setOnClickListener {
            val intent = Intent(this, PageB1::class.java)
            intent.putExtra("key", "传递字符串") // 可选:添加要传递的数据
            // 启动目标 Activity
            //startActivity(intent)
            // 如果希望在目标 Activity 中获取返回结果,使用ActivityResultLauncher来启动
            someActivityResultLauncher.launch(intent);
        }
    }

}

2).Receive data on page B

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import com.example.datatransferdemo.R

class PageB1 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_page_b1)

        // 获取传递过来的值
        val value = intent.getStringExtra("key")

        // 离开回传数据
        val but = findViewById<Button>(R.id.button)
        but.setOnClickListener {
            val returnIntent = Intent()
            returnIntent.tag = "PageB1"
            returnIntent.putExtra("result_key", "返回字符串")
            setResult(RESULT_OK, returnIntent)

            finish() // 结束当前Activity, 不一定要立即结束。
        }
    }
}

3).Expand Intent to distinguish the returned data

Use extension functions to add a custom tag attribute to the Intent

import android.content.Intent

var Intent.tag: String?
    get() = getStringExtra("tag")
    set(value) {
        putExtra("tag", value)
    }

2.Bundle

Similar to Intents, you can use Bundles to pass data between pages.

In the process of sending page A to page B, the data is put into the Bundle object.

In receiving page B, get the Bundle object from the Intent and extract the data from the Bundle.

1).Send Bundle on page A

val bundle = Bundle()
bundle.putInt("id",123)
bundle.putBoolean("status",true)
bundle.putString("content", "传递字符串")
val intent = Intent(this, PageB2::class.java)
intent.putExtras(bundle)
// 启动目标 Activity
//startActivity(intent)
// 获取返回结果启动
someActivityResultLauncher.launch(intent);

2).Receive data on page B

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.datatransferdemo.R
import com.example.datatransferdemo.tag

class PageB2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_page_b2)

        // 获取传递过来的值
        val bundle = intent.extras
        val receivedID = bundle?.getInt("id") // 根据传递的数据类型使用对应的 getXXX() 方法
        val receivedStatus = bundle?.getBoolean("status")
        val receivedContent = bundle?.getString("content")

        // 离开回传数据
        val returnBundle = Bundle()
        returnBundle.putInt("id",123)
        returnBundle.putBoolean("status",true)
        returnBundle.putString("content", "传递字符串")
        val returnIntent = Intent()
        returnIntent.tag = "PageB2"
        returnIntent.putExtras(returnBundle)
        setResult(RESULT_OK, returnIntent)
    }
}

3. Static variables

Define a static variable in a class, and other pages can directly access the static variable to transfer data.

It is suitable for situations where data needs to be transferred globally, but it is not suitable for temporary or life cycle data transfer.

1).Define static variables

object StaticDataHolder {
    var sharedData = mapOf<String,String>()
}

2).Set data on page A

 StaticDataHolder.sharedData = mapOf<String,String>("id" to "1234", "status" to "1", "content" to "传递字符串")

3). Get data on page B

val receivedData = StaticDataHolder.sharedData

4.SharedPreferences

Use SharedPreferences to store and read key-value data and share data between different pages.

Suitable for situations where long-term storage and sharing of data is required.

1).Set data on page A

val sharedPref = getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
val editor = sharedPref.edit()
editor.putString("key", data) // data 是要传递的数据
editor.apply()

2). Get data on page B

val sharedPref = getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
val receivedData = sharedPref.getString("key", "") // 根据传递的数据类型使用对应的 getXXX() 方法

5.Interface Callback

Define an interface, implement the interface in page A, and pass an instance of the implementation class to page B.

Page B can call the interface method to pass data to page A.

Suitable for situations where there are interaction and callback requirements between pages.

1).Define the interface

interface DataCallback {
    fun onDataReceived(data: String)
}

2).Implement the interface in page A

class PageA : AppCompatActivity(), DataCallback {
  
    // 静态的DataCallback实例
    companion object {
        var callbackInstance: DataCallback? = null
    }
        
    override fun onDestroy() {
        super.onDestroy()
        callbackInstance = null // 防止内存泄漏
    }        
        
    override fun onDataReceived(data: String) {
        // 处理接收到的数据
    }

    // 在需要传递数据的地方将实现类的实例传递给页面 B
    callbackInstance = this;
    val intent = Intent(this, PageB5::class.java)
    startActivity(intent)
}

3). Use the interface to transfer data in page B

  // 调用DataCallback的方法
  if (MainActivity.callbackInstance != null) {
      MainActivity.callbackInstance?.onDataReceived("传递的数据")
  }

6.EventBus

Use the EventBus library for event delivery and data communication between pages.

Page A publishes an event, and Page B subscribes to the event and receives data.

Suitable for situations where decoupling and simplifying communication between pages.

1).Add EventBus to your project dependencies

dependencies {
    implementation 'org.greenrobot:eventbus:3.3.1'
}

2).Create an event class

The purpose of this class is to pass data between components

class MessageEvent(val message: String)

3).Register and logout EventBus

In the component (such as Activity or Fragment) you want to receive events, register and unregister the EventBus.

override fun onStart() {
    super.onStart()
    EventBus.getDefault().register(this)
}

override fun onStop() {
    super.onStop()
    EventBus.getDefault().unregister(this)
}

4).Listen to events

In the same component, add a method to listen to the event. This method needs to be annotated with @Subscribe.

@Subscribe(threadMode = ThreadMode.MAIN)
public fun onMessageEvent(event: MessageEvent) {
    // 处理事件
    val data = event.message
    // ...处理数据
}

5).Publish events

Where data needs to be sent, publish an event instance.

EventBus.getDefault().post(MessageEvent("Hello, EventBus!"))

6).Example

receiver

class ReceiverActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_receiver)
    }

    override fun onStart() {
        super.onStart()
        EventBus.getDefault().register(this)
    }

    override fun onStop() {
        super.onStop()
        EventBus.getDefault().unregister(this)
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public fun onMessageEvent(event: MessageEvent) {
        // 这里处理接收到的事件
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show()
    }
}

sender

class SenderActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_sender)

        // 假设有一个按钮用于发送事件
        val sendButton: Button = findViewById(R.id.sendButton)
        sendButton.setOnClickListener {
            // 当按钮被点击时,发布事件
            EventBus.getDefault().post(MessageEvent("Hello from SenderActivity!"))
        }
    }
}

7.Application class

1).Create a custom Application class

Create a new Kotlin class that inherits from the Application class and define the data you want to pass in it.

class MyApp : Application() {
    var globalData: String? = null
    // 你可以在这里定义更多的变量或方法
}

2).Declare in AndroidManifest.xml

Within the <application> tag in the AndroidManifest.xml file, use the android:name attribute to specify your custom Application class.

<application
    android:name=".MyApp"
    ...>
    ...
</application>

3).Use in Activity or other components

In any Activity or other component, you can get an instance of the custom Application class and access the data defined in it by calling the getApplication() method.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val app = application as MyApp
        app.globalData = "Some data"
    }
}

Precautions

  • When using Application to pass data, pay attention to the life cycle, because when the Android system needs to release memory for other applications, it may kill the background process, which will cause the Application object to be rebuilt and the data may be lost.

  • This method is suitable for passing small amounts of globally needed data. If the amount of data is large or persistent storage is required, you should consider using a database, SharedPreferences or other storage mechanisms.

  • To avoid memory leaks, do not hold Activity, View, or other context-sensitive references in the Application class.

In this way, you can share data between different components, but ensure that access to the shared data is thread-safe.

8.Parcelable

Parcelable and Serializable are two commonly used data transfer methods. Parcelable is an Android-specific interface with better performance than Serializable, but its implementation is slightly more complicated. Serializable is an interface provided by Java, which is simple to implement, but has poor performance.

Implement the Parcelable interface so that objects can be serialized and deserialized and passed between pages.

Suitable for situations where custom objects need to be passed.

1).Create a data class and implement the Parcelable interface.

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class User(val name: String, val age: Int) : Parcelable

Starting with Kotlin 1.1.4, you can use the @Parcelize annotation to automatically implement the Parcelable interface, provided that the kotlin-parcelize plugin is enabled in the project's build.gradle file.

plugins {
    // 其它插件...
    id("org.jetbrains.kotlin.plugin.parcelize") 
}

2). When starting a new Activity, put the Parcelable object into the Intent.

val intent = Intent(this, SecondActivity::class.java).apply {
    val user = User("John Doe", 30)
    putExtra("USER_KEY", user)
}
startActivity(intent)

3). In the receiving Activity, take out the Parcelable object from the Intent.

class SecondActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)

        val user = intent.getParcelableExtra<User>("USER_KEY")
        user?.let {
            // 使用user对象
        }
    }
}

Note:  When using Serializable, all sub-objects in the serialized object must also implement the Serializable interface. Parcelable requires each sub-object to implement the Parcelable interface.

It is recommended to use Parcelable in Android as it is more efficient than Serializable.

9.Serializable

Create a data class that implements the Serializable interface.

Create the data object in the sending page and put it in the Intent.

Get the passed Serializable object from the Intent in the receiving page.

1).Create a data class and implement the Serializable interface.

import java.io.Serializable

data class User(val name: String, val age: Int) : Serializable

2). When starting a new Activity, put the Serializable object into the Intent.

val intent = Intent(this, SecondActivity::class.java).apply {
    val user = User("John Doe", 30)
    putExtra("USER_KEY", user)
}
startActivity(intent)

3). In the receiving Activity, take out the Serializable object from the Intent.

class SecondActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)

        val user = intent.getSerializableExtra("USER_KEY") as? User
        user?.let {
            // 使用user对象
        }
    }
}

10. Database

Use a database to store data and read and write data between different pages.

You can use database frameworks such as SQLite and Room for data persistence and sharing.

Suitable for situations where long-term storage and large amounts of data sharing are required.

1).Add dependencies

In the project's build.gradle file, add the Room database dependency.

plugins {
    id("kotlin-kapt")
}

android {
    ...
    defaultConfig {
        ...
        kapt {
            arguments {
                arg("room.schemaLocation", "$projectDir/schemas")
            }
        }
    }
  
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"
    }
    ...
}
    
dependencies {
    implementation("androidx.room:room-runtime:2.5.0")
    //annotationProcessor("androidx.room:room-compiler:2.5.0")
    // For Kotlin use kapt instead of annotationProcessor
    kapt("androidx.room:room-compiler:2.5.0")
    // optional - Kotlin Extensions and Coroutines support for Room
    implementation("androidx.room:room-ktx:2.5.0")
}

2).Define data model

Create a data class and mark it with @Entity annotation to indicate that it is a database table.

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity
data class User(
    @PrimaryKey val id: Int,
    val name: String,
    val age: Int
)

3).Create DAO (data access object)

Define an interface, marked with @Dao annotation, which contains methods for accessing the database.

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query

@Dao
interface UserDao {
    @Insert
    suspend fun insertUser(user: User): Long

    @Query("SELECT * FROM user WHERE id = :id")
    suspend fun getUserById(id: Int): User?
}

4).Create a database instance

Create an abstract class, inherit from RoomDatabase, and annotate it with @Database.

import androidx.room.Database
import androidx.room.RoomDatabase

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

5).Use database

In your Activity or Fragment, get the database instance and perform database operations.

val db = Room.databaseBuilder(
    applicationContext,
    AppDatabase::class.java, "database-name"
).build()

val userDao = db.userDao()

// 插入用户
GlobalScope.launch {
    userDao.insertUser(User(1, "John Doe", 30))// 测试时每次修改一下ID,不然存在相同ID会报错
}

// 查询用户
GlobalScope.launch {
    val user = userDao.getUserById(1)
    // 使用user对象
}

Notice:

  • The above code uses Kotlin coroutines to handle asynchronous database operations.

  • Room.databaseBuilder() requires a Context object, typically you would call it in an Activity or Application.

  • Database operations (such as inserts and queries) should not be performed on the main thread because they may block the UI, so they should be run in coroutines or other asynchronous mechanisms.

  • The use of GlobalScope is not recommended in real applications because its lifecycle is the entire application and you should use a scope with a shorter lifecycle like lifecycleScope or viewModelScope.

11.File

Store data in files and transfer data between different pages by reading and writing files.

Files can be created and accessed using internal storage or external storage.

Suitable for situations where large amounts of data or persistent storage is required.

1). Define a helper class or function to handle file reading and writing operations

Here is a simple example:

import android.content.Context
import java.io.*

class FileHelper(private val context: Context) {

    fun writeToFile(fileName: String, data: String) {
        context.openFileOutput(fileName, Context.MODE_PRIVATE).use { outputStream ->
            outputStream.write(data.toByteArray())
        }
    }

    fun readFromFile(fileName: String): String {
        return context.openFileInput(fileName).bufferedReader().useLines { lines ->
            lines.fold("") { some, text ->
                "$some\n$text"
            }
        }
    }
}

2).In your Activity or Fragment, you can use this helper class to store data to a file.

class SomeActivity : AppCompatActivity() {
    private lateinit var fileHelper: FileHelper

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_some)

        fileHelper = FileHelper(this)

        // 保存数据到文件
        val dataToSave = "Some data to be shared"
        fileHelper.writeToFile("shared_data.txt", dataToSave)
    }

    private fun loadData() {
        // 从文件中读取数据
        val data = fileHelper.readFromFile("shared_data.txt")
        // 使用读取的数据
        // ...
    }
}

3).In another page, you can use the same FileHelper instance to read the previously written file.

class AnotherActivity : AppCompatActivity() {
    private lateinit var fileHelper: FileHelper

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_another)

        fileHelper = FileHelper(this)

        // 读取数据
        val sharedData = fileHelper.readFromFile("shared_data.txt")
        // 使用数据
        // ...
    }
}

Make sure you consider thread safety and performance issues when using files for data transfer. For large amounts of data or frequent read and write operations, you may want to consider using other data delivery methods, such as databases or SharedPreferences. Additionally, for sensitive data, ensure appropriate encryption to keep user data safe.

12. Network Request

Use network requests to pass data, and data can be exchanged through HTTP requests or other network protocols.

The sender sends data to the receiver over the network, and the receiver obtains the data by parsing the network response.

Suitable for remote data exchange or communication with the server.

1).Add network permissions to your AndroidManifest.xml file

<uses-permission android:name="android.permission.INTERNET" />

2).Choose a network request library

Such as Retrofit, OkHttp or Volley, here we take Retrofit as an example.

3).Add the dependencies of the selected network library to your build.gradle file

dependencies {
    // Retrofit & Gson
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
}

4).Create a model class to represent your data

data class User(val name: String, val email: String)

5). Define an interface to describe HTTP requests

import retrofit2.Call
import retrofit2.http.GET

interface ApiService {
    @GET("users/info")
    fun getUserInfo(): Call<User>
}

6).Use the Retrofit builder to instantiate your service

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitClient {
    private const val BASE_URL = "https://your.api.url/"

    val apiService: ApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

7).Send network requests and process responses

import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        RetrofitClient.apiService.getUserInfo().enqueue(object : Callback<User> {
            override fun onResponse(call: Call<User>, response: Response<User>) {
                if (response.isSuccessful) {
                    val userInfo = response.body()
                    // 使用userInfo数据
                }
            }

            override fun onFailure(call: Call<User>, t: Throwable) {
                // 处理请求失败的情况
            }
        })
    }
}

13.ContentProvider

Use ContentProvider for cross-application data sharing and delivery.

ContentProvider provides standard interfaces and methods to operate data, and can access data through URI.

Suitable for situations where data needs to be shared between different applications.

1).Define a ContentProvider class

First, create a class that inherits from ContentProvider and implements its abstract methods.

class MyContentProvider : ContentProvider() {

    // 初始化ContentProvider
    override fun onCreate(): Boolean {
        // 初始化数据库等操作
        return true
    }

    // 查询数据
    override fun query(
        uri: Uri,
        projection: Array<String>?,
        selection: String?,
        selectionArgs: Array<String>?,
        sortOrder: String?
    ): Cursor? {
        // 处理查询请求
        return null
    }

    // 插入数据
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // 处理插入请求
        return null
    }

    // 更新数据
    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<String>?
    ): Int {
        // 处理更新请求
        return 0
    }

    // 删除数据
    override fun delete(
        uri: Uri,
        selection: String?,
        selectionArgs: Array<String>?
    ): Int {
        // 处理删除请求
        return 0
    }

    // 返回MIME类型
    override fun getType(uri: Uri): String? {
        // 根据URI返回正确的MIME类型
        return null
    }
}

2).Register ContentProvider in AndroidManifest.xml

<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.myapp.provider"
    android:exported="true" />

android:authorities should be unique, usually prefixed with the application's package name.

3).Use ContentResolver to access ContentProvider

Other applications can use ContentResolver to query your ContentProvider.

Get ContentResolver instance:

In your application, you can obtain an instance of ContentResolver by calling the getContentResolver() method. This is usually done in an Activity or Service.

val contentResolver = context.contentResolver

Build URI:

When accessing ContentProvider data, you need to specify a URI. This URI points to the data collection or individual data item you want to access. URIs usually follow the format: content://<authority>/<path>, where <authority> is specified when registering the ContentProvider in AndroidManifest.xml and is unique. <path> is the data table or data type you wish to access.

val uri: Uri = Uri.parse("content://com.example.myapp.provider/table_name")

Query data:

Use ContentResolver's query() method to request data. You can specify column names, selection criteria, selection parameters, and sort order.

val cursor: Cursor? = contentResolver.query(
    uri,
    projection, // String数组,代表你想要返回的列。
    selection, // SQL中的where子句,但不包括"WHERE"本身。
    selectionArgs, // 与selection中的占位符相匹配的值。
    sortOrder // 结果的排序方式。
)

Process the returned Cursor:

If the query is successful, the query() method will return a Cursor object. Through this Cursor, you can traverse and read data.

cursor?.let {
    while (it.moveToNext()) {
        // 使用Cursor获取数据
        val columnValue = it.getString(it.getColumnIndex("column_name"))
        // 处理数据...
    }
}
cursor?.close() // 最后记得关闭Cursor

Insert, update and delete data:

In addition to querying data, ContentResolver also provides insert(), update(), and delete() methods to add, modify, and query data.

// 插入数据
val newUri: Uri? = contentResolver.insert(uri, contentValues)

// 更新数据
val rowsUpdated: Int = contentResolver.update(uri, contentValues, selection, selectionArgs)

// 删除数据
val rowsDeleted: Int = contentResolver.delete(uri, selection, selectionArgs)

Processing permissions:

If the ContentProvider contains private data or needs to restrict access, you need to declare the corresponding permissions in AndroidManifest.xml and request these permissions when accessing.

Security considerations:

When your ContentProvider exposes data to other applications, you need to consider data security. Ensure that incoming URIs are validated to avoid security vulnerabilities such as SQL injection, and access is authenticated and authorized as needed.

In this way, ContentResolver and ContentProvider together provide a powerful mechanism that allows applications to share data safely and efficiently.

Guess you like

Origin blog.csdn.net/u012881779/article/details/134619941