diff --git a/app-catalog/samples/foundation/build.gradle b/app-catalog/samples/foundation/build.gradle index ddff10a..7813486 100644 --- a/app-catalog/samples/foundation/build.gradle +++ b/app-catalog/samples/foundation/build.gradle @@ -12,6 +12,7 @@ android { } buildFeatures { viewBinding = true + aidl = true } namespace 'com.wintmain.foundation' } diff --git a/app-catalog/samples/foundation/src/main/AndroidManifest.xml b/app-catalog/samples/foundation/src/main/AndroidManifest.xml index 655cee0..b185f15 100644 --- a/app-catalog/samples/foundation/src/main/AndroidManifest.xml +++ b/app-catalog/samples/foundation/src/main/AndroidManifest.xml @@ -84,155 +84,155 @@ android:exported="true" android:theme="@style/Theme.AppCompat.DayNight" /> @@ -244,92 +244,92 @@ @@ -338,7 +338,7 @@ @@ -346,12 +346,18 @@ + + \ No newline at end of file diff --git a/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/INewPeopleListener.aidl b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/INewPeopleListener.aidl new file mode 100644 index 0000000..79bdb21 --- /dev/null +++ b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/INewPeopleListener.aidl @@ -0,0 +1,10 @@ +// INewPeopleListener.aidl +package com.wintmain.foundation; + +import com.wintmain.foundation.People; + +// AIDL中无法使用普通接口,所以创建此文件 + +interface INewPeopleListener { + void onNewPeople(in People newPeople); +} \ No newline at end of file diff --git a/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/IPeopleManager.aidl b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/IPeopleManager.aidl new file mode 100644 index 0000000..205e754 --- /dev/null +++ b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/IPeopleManager.aidl @@ -0,0 +1,19 @@ +// IPeopleManager.aidl +// AIDL接口中只支持方法,不支持声明静态常量 +package com.wintmain.foundation; + +// 自定义的Parcelable对象和AIDL对象不管是否和当前的AIDL文件位于同一个包内,也必须要显式import进来 + +import com.wintmain.foundation.People; +import com.wintmain.foundation.INewPeopleListener; + +interface IPeopleManager { + List getPeopleList(); + // addPeople方法的参数中有in关键字,因为aidl中除了基本数据类型,其它类型的参数必须标上方向:in、out或者inout, + // 我们需根据实现需要去指定参数类型,因为这在底层实现是有开销的。 + void addPeople(in People people); + + // 扩展新增代码:新增接口 + void registerListener(INewPeopleListener listener); + void unregisterListener(INewPeopleListener listener); +} diff --git a/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/People.aidl b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/People.aidl new file mode 100644 index 0000000..d27e4c5 --- /dev/null +++ b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/People.aidl @@ -0,0 +1,20 @@ +// People.aidl +package com.wintmain.foundation; + +// Declare any non-default types here with import statements + +// 在Android的AIDL中,仅支持的数据类型有: +// 1. 基本数据类型(int、long、char、boolean、double等) +// 2. String和CharSequence +// 3. List:只支持ArrayList,里面的每个元素都必须能够被AIDL支持 +// 4. Map:只支持HashMap,里面的每个元素都必须能够被AIDL支持 +// 5. Parcelable:所有实现了Parcelable接口的对象 +// 6. AIDL:所有的AIDL接口本身也可以在AIDL文件中使用 + +// Parcelable的作用是序列化对象,因为跨进程通信传输对象必须是以序列化和反序列化的方式进行。 +// Parcelable的实现有些繁琐,但性能效率很高。 + +import com.wintmain.foundation.People; + +// People should be declared in a file called com/wintmain/foundation/People.aidl +parcelable People; diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/People.java b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/People.java new file mode 100644 index 0000000..6c386c7 --- /dev/null +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/People.java @@ -0,0 +1,86 @@ +/* + * Copyright 2023-2025 wintmain + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wintmain.foundation; + +import android.os.Parcel; +import android.os.Parcelable; +import androidx.annotation.NonNull; + +public class People implements Parcelable { + + public int pId; + public String pName; + + public People(Parcel in) { + pId = in.readInt(); + pName = in.readString(); + } + + public People(int pId, String pName) { + this.pId = pId; + this.pName = pName; + } + + public String getName() { + return pName; + } + + public void setName(String pName) { + this.pName = pName; + } + + public int getId() { + return pId; + } + + public void setId(int pId) { + this.pId = pId; + } + + // 内容描述功能由describeContents方法来完成 + // 几乎在所有情况下这方法都应该返回0,仅当当前对象中存在文件描述符时才返回1。 + @Override + public int describeContents() { + return 0; + } + + // 序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法来完成的。 + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(pId); + dest.writeString(pName); + } + + // 反序列化功能由CREATOR来完成,其内部标明了如何创建序列化对象和数组, + // 并通过Parcel的一系列read方法来完成反序列化过程。 + public static final Parcelable.Creator CREATOR = new Parcelable.Creator<>() { + public People createFromParcel(Parcel in) { + return new People(in); + } + + public People[] newArray(int size) { + return new People[size]; + } + }; + + + @NonNull + @Override + public String toString() { + return String.format("pId:%s, pName:%s", pId, pName); + } +} diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PeopleRemoteService.kt b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PeopleRemoteService.kt new file mode 100644 index 0000000..8c38ab2 --- /dev/null +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PeopleRemoteService.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2023-2025 wintmain + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wintmain.foundation + +import android.app.Service +import android.content.Intent +import android.os.Binder +import android.os.IBinder +import android.os.RemoteCallbackList +import java.util.concurrent.CopyOnWriteArrayList + +// 服务端代码,客户端见[PlaceHoldActivity.java] +class PeopleRemoteService : Service() { + val mPeopleList = CopyOnWriteArrayList() + // 客户端注册和移除注册过程中使用的虽是同一个客户端对象, + // 但通过Binder传递到服务端后,产生了两个不同的对象。 + // 因为对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程 + val mListenList = RemoteCallbackList() + + private val mBinder: Binder = object : IPeopleManager.Stub() { + override fun getPeopleList(): List { + return mPeopleList + } + + override fun addPeople(people: People) { + mPeopleList.add(people) + + // 扩展新增代码:新加对象后执行监听回调 + for (i in 0 until mListenList.beginBroadcast()) { + val listener: INewPeopleListener = mListenList.getBroadcastItem(i) + listener.onNewPeople(people) + } + mListenList.finishBroadcast() + } + + // RemoteCallbackList是系统专门提供的用于删除跨进程listener接口,它是一个泛型,支持管理任意AIDL接口。 + // 另外它内部自动实现了线程同步的功能。 + // beginBroadcast和finishBroadcast必须要配对使用,哪怕仅仅获取RemoteCallbackList中的元素个数 + override fun registerListener(listener: INewPeopleListener) { + mListenList.beginBroadcast() + mListenList.register(listener) + mListenList.finishBroadcast() + } + + override fun unregisterListener(listener: INewPeopleListener) { + mListenList.beginBroadcast() + mListenList.unregister(listener) + mListenList.finishBroadcast() + } + } + + override fun onBind(intent: Intent): IBinder { + return mBinder + } +} \ No newline at end of file diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PlaceHolderActivity.java b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PlaceHolderActivity.java index 65f464a..cdc2af3 100644 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PlaceHolderActivity.java +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PlaceHolderActivity.java @@ -20,7 +20,10 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; import android.provider.AlarmClock; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; @@ -36,12 +39,19 @@ description = "一个为了测试API调用的入口", tags = "A-Self_demos") public class PlaceHolderActivity extends AppCompatActivity { + private static final String TAG = PlaceHolderActivity.class.getSimpleName(); + IPeopleManager peopleManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.pft_activity_main); + // AIDL示例 S + Intent intent = new Intent(this, PeopleRemoteService.class); + bindService(intent, mConnection, BIND_AUTO_CREATE); + // AIDL示例 E + // 初始化一些三方库 initLibs(getApplication()); @@ -62,9 +72,12 @@ public void onTitleClick(TitleBarExt titleBar) { newIntent.setAction(AlarmClock.ACTION_SET_ALARM); newIntent.putExtra(AlarmClock.EXTRA_SKIP_UI, true); newIntent.setComponent(new ComponentName("com.android.deskclock", "com.android.deskclock.HandleApiCalls")); - startActivity(newIntent); - - ToastUtils.show("你点击了中间,并且新建了一个9:30的闹钟"); + try { + startActivity(newIntent); + ToastUtils.show("你点击了中间,并且新建了一个9:30的闹钟"); + } catch (Exception e) { + ToastUtils.show("你点击了中间,但是新建闹钟失败"); + } } @Override @@ -74,6 +87,20 @@ public void onRightClick(TitleBarExt titleBar) { }); } + @Override + protected void onDestroy() { + // 移除监听 + if (peopleManager != null && peopleManager.asBinder().isBinderAlive()) { + try { + peopleManager.unregisterListener(mNewPeopleListener); + } catch (RemoteException e) { + android.util.Log.d(TAG, "catch:" + e.getMessage()); + } + } + unbindService(mConnection); + super.onDestroy(); + } + private void initLibs(Application application) { // 初始化 TitleBar 默认样式 TitleBarExt.setDefaultStyle( @@ -97,4 +124,34 @@ public TextView newRightView(Context context) { // 初始化 Toast ToastUtils.init(this.getApplication()); } + + + private final ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + android.util.Log.d(TAG, "onServiceConnected..."); + peopleManager = IPeopleManager.Stub.asInterface(service); + try { + People people = new People(1000, "wintmain"); + // 注册监听 + peopleManager.registerListener(mNewPeopleListener); + peopleManager.addPeople(people); + } catch (RemoteException e) { + android.util.Log.d(TAG, "catch:" + e.getMessage()); + } + } + public void onServiceDisconnected(ComponentName className) { + android.util.Log.d(TAG, "onServiceDisconnected!!!"); + ToastUtils.show("onServiceDisconnected!!!"); + peopleManager = null; + } + }; + + + // 定义监听接口 + private final INewPeopleListener mNewPeopleListener = new INewPeopleListener.Stub() { + @Override + public void onNewPeople(People newPeople) { + ToastUtils.show("onNewPeople: " + newPeople); + } + }; } diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/job/DbEntryActivity.kt b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/overview/DbEntryActivity.kt similarity index 95% rename from app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/job/DbEntryActivity.kt rename to app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/overview/DbEntryActivity.kt index 15d148f..9dac248 100644 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/job/DbEntryActivity.kt +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/overview/DbEntryActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 wintmain + * Copyright 2023-2025 wintmain * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.wintmain.foundation.job +package com.wintmain.foundation.overview import android.os.Bundle import android.widget.Button @@ -37,10 +37,10 @@ class DbEntryActivity : AppCompatActivity(R.layout.activity_db_entry) { // Initialize DbHelper dbHelper = DbHelper(applicationContext) dbHelper.writableDatabase // Ensure database is created - addlisteners() + addListeners() } - private fun addlisteners() { + private fun addListeners() { findViewById