前言
本章主要讲解Android中IPC的通信方式。。。
1.Bundle
Bundle实现了Parcelable接口,且四大组件中Activity、Service、Receiver都支持在Intent中传递Bundle。传输的数据必须能够被序列化,如基本类型、实现了了Parcelable以及Serializable接口的对象。
接下来使用Bundle在A和B之间进行跨进程数据传递:
A的AndroidManifest.xml配置文件:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
B的AndroidManifest.xml配置文件:
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
android:exported="true" 总体来说它的主要作用是:是否支持其它应用调用当前组件。
默认值:如果包含有intent-filter 默认值为true; 没有intent-filter默认值为false。
A和B中添加实现序列化Parcelable接口的实体类User.kt:(必须位于同名包下)
package com.wdl.model
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import java.io.Serializable
/**
* author: wdl
* time: 2018/10/15 14:58
* des: TODO
*/
@Parcelize
data class User constructor(var userName: String, var age: Int) : Parcelable,Serializable
A的MainActivity携带Bundle参数跳转至B的MainActivity:
package com.wdl.ipcone
import android.content.ComponentName
import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.wdl.model.User
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//第一种方式 通过Intent指定component跳转
mGoIpcTwo.setOnClickListener {
val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_DEFAULT)
val componentName = ComponentName("com.wdl.ipctwo", "com.wdl.ipctwo.MainActivity")
intent.component = componentName
val user = User("wdl",23)
val bundle = Bundle()
bundle.putParcelable("parcelable",user)
intent.putExtra("bundle",bundle)
intent.putExtra("msg","我是来自第一个APP的值")
startActivity(intent)
}
}
}
B的MainActivity:
package com.wdl.ipctwo
import android.annotation.SuppressLint
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.wdl.model.User
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
intent?.let {
val content = it.getStringExtra("msg")
val bundle = it.getBundleExtra("bundle")
bundle?.classLoader = User::class.java.classLoader
val user = bundle?.getParcelable<User>("parcelable")
mBundle.text = "通过A传来的String值:$content 序列化传来的对象:${user?.toString()}"
Log.e("wdl", "收到来自IPCOne的数据:$content")
}
}
}
启动并测试:
Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.wdl.model.User
at android.os.Parcel.readParcelableCreator(Parcel.java:2535)
at android.os.Parcel.readParcelable(Parcel.java:2461)
at android.os.Parcel.readValue(Parcel.java:2364)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2717)
at android.os.BaseBundle.unparcel(BaseBundle.java:269)
at android.os.Bundle.getParcelable(Bundle.java:840)
at com.wdl.ipctwo.MainActivity.onCreate(MainActivity.kt:19)
at android.app.Activity.performCreate(Activity.java:6662)
造成上述的原因是:两个User.kt实体类不是位于同一包名下,因此要想正常解析数据必须把实体类放置在包名一样的包中
Caused by: java.lang.ClassNotFoundException: Didn't find class "com.wdl.model.User" on path: DexPathList[[zip file "/data/app/com.wdl.ipctwo-2/base.apk"],nativeLibraryDirectories=[/data/app/com.wdl.ipctwo-2/lib/x86, /system/lib, /vendor/lib]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
at java.lang.ClassLoader.loadClass(ClassLoader.java:380)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:400)
at android.os.Parcel.readParcelableCreator(Parcel.java:2507)
at android.os.Parcel.readParcelable(Parcel.java:2461)
at android.os.Parcel.readValue(Parcel.java:2364)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2717)
at android.os.BaseBundle.unparcel(BaseBundle.java:269)
at android.os.Bundle.getParcelable(Bundle.java:840)
at com.wdl.ipctwo.MainActivity.onCreate(MainActivity.kt:19)
at android.app.Activity.performCreate(Activity.java:6662)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2599)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
导致上述原因是:
Android有两种不同的classloaders:framework classloader和apk classloader,其中framework classloader知道怎么加载android classes,apk classloader知道怎么加载你的代码,即可以知道你自定义的类,apk classloader继承自framework classloader,所以也知道怎么加载android classes。在应用刚启动时,默认class loader是apk classloader,但在系统内存不足应用被系统回收会再次启动,这个默认class loader会变为framework classloader了,所以对于自己的类会报ClassNotFoundException。
解决方法:设置相应的classloader
bundle?.classLoader = User::class.java.classLoader
效果:
2. 使用文件共享
两个进程通过读写同一个文件夹来交换数据
在A中MainActivity中的onResume中序列化一个对象到sd卡的一个文件上:
override fun onResume() {
super.onResume()
persistToFile()
}
private fun persistToFile() {
Thread(Runnable {
val user = User("wdl",23)
val dir = File(MyConstants.CHAPTER_2_PATH)
if (!dir.exists())dir.mkdirs()
val fileCache = File(MyConstants.CACHE_FILE_PATH)
val objectOutputStream = ObjectOutputStream(FileOutputStream(fileCache))
objectOutputStream.writeObject(user)
objectOutputStream.close()
}).start()
}
在B中MainActivity中的onResume中反序列化sd卡的一个文件到User对象:
override fun onResume() {
super.onResume()
recoverFromFile()
}
private fun recoverFromFile() {
Thread(Runnable {
val cachedFile = File(MyConstants.CACHE_FILE_PATH)
if (!cachedFile.exists()) {
cachedFile.createNewFile()
}
val objectInputStream = ObjectInputStream(FileInputStream(cachedFile))
val user = objectInputStream.readObject() as User
objectInputStream.close()
mBundle.text = "$user"
Log.e("wdl", user.toString())
}).start()
}
效果: