IT大鲨鱼
IP:
0关注数
0粉丝数
0获得的赞
工作年
编辑资料
链接我:

创作·54

全部
问答
动态
项目
学习
专栏
IT大鲨鱼

Android Jetpack系列之Lifecycle

Lifecycle介绍Lifecycle可以让某一个类变成Activity、Fragment的生命周期观察者类,监听其生命周期的变化并可以做出响应。Lifecycle使得代码更有条理性、精简、易于维护。Lifecycle中主要有两个角色:LifecycleOwner: 生命周期拥有者,如Activity/Fragment等类都实现了该接口并通过getLifecycle()获得Lifecycle,进而可通过addObserver()添加观察者。LifecycleObserver: 生命周期观察者,实现该接口后就可以添加到Lifecycle中,从而在被观察者类生命周期发生改变时能马上收到通知。实现LifecycleOwner的生命周期拥有者可与实现LifecycleObserver的观察者完美配合。场景case假设我们有一个在屏幕上显示设备位置的 Activity,我们可能会像下面这样实现:internal class MyLocationListener( private val context: Context, private val callback: (Location) -> Unit) { fun start() { // connect to system location service } fun stop() { // disconnect from system location service } } class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } public override fun onStart() { super.onStart() myLocationListener.start() // manage other components that need to respond // to the activity lifecycle } public override fun onStop() { super.onStop() myLocationListener.stop() // manage other components that need to respond // to the activity lifecycle } } 注:上面代码来自官方示例~我们可以在Activity 或 Fragment 的生命周期方法(示例中的onStart/onStop)中直接对依赖组件进行操作。但是,这样会导致代码条理性很差且不易扩展。那么有了Lifecycle,可以将依赖组件的代码从Activity/Fragment生命周期方法中移入组件本身中。Lifecycle使用根目录下build.gradle:allprojects { repositories { google() // Gradle小于4.1时,使用下面的声明替换: // maven { // url 'https://maven.google.com' // } // An alternative URL is 'https://dl.google.com/dl/android/maven2/' } } app下的build.gradle: dependencies { def lifecycle_version = "2.3.1" def arch_version = "2.1.0" // ViewModel implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" // LiveData implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" // Lifecycles only (without ViewModel or LiveData) implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" // Saved state module for ViewModel implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" // Annotation processor kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // 可选 - 如果使用Java8,使用下面这个代替lifecycle-compiler implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" // 可选 - 在Service中使用Lifecycle implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version" // 可选 - Application中使用Lifecycle implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version" // 可选 - ReactiveStreams support for LiveData implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version" // 可选 - Test helpers for LiveData testImplementation "androidx.arch.core:core-testing:$arch_version" } Activity/Fragment中使用Lifecycle首先先来实现LifecycleObserver观察者:open class MyLifeCycleObserver : LifecycleObserver { @OnLifecycleEvent(value = Lifecycle.Event.ON_START) fun connect(owner: LifecycleOwner) { Log.e(JConsts.LIFE_TAG, "Lifecycle.Event.ON_CREATE:connect") } @OnLifecycleEvent(value = Lifecycle.Event.ON_STOP) fun disConnect() { Log.e(JConsts.LIFE_TAG, "Lifecycle.Event.ON_DESTROY:disConnect") } } 在方法上,我们使用了@OnLifecycleEvent注解,并传入了一种生命周期事件,其类型可以为ON_CREATE、ON_START、ON_RESUME、ON_PAUSE、ON_STOP、ON_DESTROY、ON_ANY中的一种。其中前6个对应Activity中对应生命周期的回调,最后一个ON_ANY可以匹配任何生命周期回调。所以,上述代码中的connect()、disConnect()方法分别应该在Activity的onStart()、onStop()中触发时执行。接着来实现我们的Activity:class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.e(JConsts.LIFE_TAG, "$ACTIVITY:onCreate") //添加LifecycleObserver观察者 lifecycle.addObserver(MyLifeCycleObserver()) } override fun onStart() { Log.e(JConsts.LIFE_TAG, "$ACTIVITY:onStart") super.onStart() } override fun onResume() { Log.e(JConsts.LIFE_TAG, "$ACTIVITY:onResume") super.onResume() } override fun onPause() { Log.e(JConsts.LIFE_TAG, "$ACTIVITY:onPause") super.onPause() } override fun onStop() { Log.e(JConsts.LIFE_TAG, "$ACTIVITY:onStop") super.onStop() } override fun onDestroy() { Log.e(JConsts.LIFE_TAG, "$ACTIVITY:onDestroy") super.onDestroy() } } 可以看到在Activity中我们只是在onCreate()中添加了这么一行代码:lifecycle.addObserver(MyLifeCycleObserver()) 其中getLifecycle()是LifecycleOwner中的方法,返回的是Lifecycle对象,并通过addObserver()的方式添加了我们的生命周期观察者。接下来看执行结果,启动Activity:2021-06-30 20:57:58.038 11257-11257/ E/Lifecycle_Study: ACTIVITY:onCreate //onStart() 传递到 MyLifeCycleObserver: connect() 2021-06-30 20:57:58.048 11257-11257/ E/Lifecycle_Study: ACTIVITY:onStart 2021-06-30 20:57:58.049 11257-11257/ E/Lifecycle_Study: Lifecycle.Event.ON_START:connect 2021-06-30 20:57:58.057 11257-11257/ E/Lifecycle_Study: ACTIVITY:onResume 关闭Activity:2021-06-30 20:58:02.646 11257-11257/ E/Lifecycle_Study: ACTIVITY:onPause //onStop() 传递到 MyLifeCycleObserver: disConnect() 2021-06-30 20:58:03.149 11257-11257/ E/Lifecycle_Study: ACTIVITY:onStop 2021-06-30 20:58:03.161 11257-11257/ E/Lifecycle_Study: Lifecycle.Event.ON_STOP:disConnect 2021-06-30 20:58:03.169 11257-11257/ E/Lifecycle_Study: ACTIVITY:onDestroy 可以看到我们的MyLifeCycleObserver中的connect()/disconnect()方法的确是分别在Activity的onStart()/onStop()回调时执行的。自定义LifecycleOwner在AndroidX中的Activity、Fragmen实现了LifecycleOwner,通过getLifecycle()能获取到Lifecycle实例(Lifecycle是抽象类,实例化的是子类LifecycleRegistry)。public interface LifecycleOwner { @NonNull Lifecycle getLifecycle(); } public class LifecycleRegistry extends Lifecycle { } 如果我们想让一个自定义类成为LifecycleOwner,可以直接实现LifecycleOwner:class CustomLifeCycleOwner : LifecycleOwner { private lateinit var registry: LifecycleRegistry fun init() { registry = LifecycleRegistry(this) //通过setCurrentState来完成生命周期的传递 registry.currentState = Lifecycle.State.CREATED } fun onStart() { registry.currentState = Lifecycle.State.STARTED } fun onResume() { registry.currentState = Lifecycle.State.RESUMED } fun onPause() { registry.currentState = Lifecycle.State.STARTED } fun onStop() { registry.currentState = Lifecycle.State.CREATED } fun onDestroy() { registry.currentState = Lifecycle.State.DESTROYED } override fun getLifecycle(): Lifecycle { //返回LifecycleRegistry实例 return registry } } 首先我们的自定义类实现了接口LifecycleOwner,并在getLifecycle()返回LifecycleRegistry实例,接下来就可以通过LifecycleRegistry#setCurrentState来传递生命周期状态了。到目前为止,已经完成了大部分工作,最后也是需要去添加LifecycleObserver://注意:这里继承的是Activity,本身并不具备LifecycleOwner能力 class MainActivity : Activity() { val owner: CustomLifeCycleOwner = CustomLifeCycleOwner() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.e(JConsts.LIFE_TAG, "$ACTIVITY:onCreate") //自定义LifecycleOwner owner.init() //添加LifecycleObserver owner.lifecycle.addObserver(MyLifeCycleObserver()) } override fun onStart() { Log.e(JConsts.LIFE_TAG, "$ACTIVITY:onStart") super.onStart() owner.onStart() } override fun onResume() { Log.e(JConsts.LIFE_TAG, "$ACTIVITY:onResume") super.onResume() owner.onResume() } override fun onPause() { Log.e(JConsts.LIFE_TAG, "$ACTIVITY:onPause") super.onPause() owner.onPause() } override fun onStop() { Log.e(JConsts.LIFE_TAG, "$ACTIVITY:onStop") super.onStop() owner.onStop() } override fun onDestroy() { Log.e(JConsts.LIFE_TAG, "$ACTIVITY:onDestroy") super.onDestroy() owner.onDestroy() } } 很简单,主要是在onCreate()里实例化LifecycleOwner并调用init()完成LifecycleRegistry实例化。接着跟androidX中的Activity一样了,通过getLifecycle()得到LifecycleRegistry实例并通过addObserver()注册LifecycleObserver,最后代码执行结果跟上面的结果一致,不再重复贴了。Application中使用Lifecycle首先记得要先引入对应依赖:implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version" 然后代码编写如下://MyApplicationLifecycleObserver.kt class MyApplicationLifecycleObserver : LifecycleObserver { @OnLifecycleEvent(value = Lifecycle.Event.ON_START) fun onAppForeground() { Log.e(JConsts.LIFE_APPLICATION_TAG, "onAppForeground") } @OnLifecycleEvent(value = Lifecycle.Event.ON_STOP) fun onAppBackground() { Log.e(JConsts.LIFE_APPLICATION_TAG, "onAppBackground") } } //MyApplication.kt class MyApplication : Application() { override fun onCreate() { super.onCreate() ProcessLifecycleOwner.get().lifecycle.addObserver(MyApplicationLifecycleObserver()) } } //manifest.xml <application android:name=".MyApplication"> </application> 启动应用:2021-06-30 21:55:11.657 14292-14292/ E/Lifecycle_App_Study: onAppForeground 点击Home键,应用切到后台:2021-06-30 21:55:35.741 14292-14292/ E/Lifecycle_App_Study: onAppBackground 再切回来:2021-06-30 21:55:11.657 14292-14292/ E/Lifecycle_App_Study: onAppForeground ProcessLifecycleOwner可以很方便地帮助我们检测App前后台状态。Service中使用Lifecycle在Service中使用Lifecycle同样需要先引入依赖:implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version" 然后继承LifecycleService://MyService.kt class MyService : LifecycleService() { override fun onCreate() { Log.e(JConsts.SERVICE, "Service:onCreate") super.onCreate() lifecycle.addObserver(MyLifeCycleObserver()) } override fun onStart(intent: Intent?, startId: Int) { Log.e(JConsts.SERVICE, "Service:onStart") super.onStart(intent, startId) } override fun onDestroy() { Log.e(JConsts.SERVICE, "Service:onDestroy") super.onDestroy() } } //MainActivity.kt /** * 启动Service */ private fun startLifecycleService() { val intent = Intent(this, MyService::class.java) startService(intent) } /** * 关闭Service */ fun closeService(view: View) { val intent = Intent(this, MyService::class.java) stopService(intent) } LifecycleService中实现了LifecycleOwner接口,所以子类中可以直接通过getLifecycle()添加生命周期Observer,在Activity中启动Service:2021-07-01 14:10:09.703 7606-7606/ E/SERVICE: Service:onCreate 2021-07-01 14:10:09.709 7606-7606/ E/SERVICE: Service:onStart 2021-07-01 14:10:09.712 7606-7606/ E/SERVICE: Lifecycle.Event.ON_START:connect 操作停止Service:2021-07-01 14:10:13.062 7606-7606/ E/SERVICE: Service:onDestroy 2021-07-01 14:10:13.063 7606-7606/ E/SERVICE: Lifecycle.Event.ON_STOP:disConnect 结果也很简单,这里注意一点:因为Service中没有onPause/onStop状态,所以在Service#onDestroy()之后,LifecycleService 里会同时分发Lifecycle.Event.ON_STOP、Lifecycle.Event.ON_DESTROY两个Event,所以我们的观察者监听哪个都是可以的。完整代码地址完整代码地址参见:Jetpack Lifecycle例子源码解析Lifecycle.javapublic abstract class Lifecycle { @NonNull AtomicReference<Object> mInternalScopeRef = new AtomicReference<>(); @MainThread public abstract void addObserver(@NonNull LifecycleObserver observer); @MainThread public abstract void removeObserver(@NonNull LifecycleObserver observer); @MainThread @NonNull public abstract State getCurrentState(); //生命周期事件 对应于Activity/Fragment生命周期 public enum Event { ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY, /** * An constant that can be used to match all events. */ ON_ANY } //生命周期状态 public enum State { //onStop()之后,此状态是LifecycleOwner终态,Lifecycle不在分发Event DESTROYED, //初始化状态 INITIALIZED, //onCreate()或onStop()之后 CREATED, //onStart()或onPause()之后 STARTED, //onResume()之后 RESUMED; public boolean isAtLeast(@NonNull State state) { return compareTo(state) >= 0; } } } Lifecycle中的两个重要枚举:Event:生命周期事件,包括ON_CREATE、ON_START、ON_RESUME、ON_PAUSE、ON_STOP、ON_DESTROY、ON_ANY,对应于Activity/Fragment生命周期。State:生命周期状态,包括DESTROYED、INITIALIZED、CREATED、STARTED、RESUMED,Event的改变会使得State也发生改变。两者关系如下:Event生命周期事件分发&接收我们的Activity继承自AppCompatActivity,继续往上找AppCompatActivity的父类,最终能找到了ComponentActivity:public class ComponentActivity extends androidx.core.app.ComponentActivity implements LifecycleOwner{ //省略其他代码 只显示Lifecycle相关代码 private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //将生命周期的事件传递交给ReportFragment ReportFragment.injectIfNeededIn(this); if (mContentLayoutId != 0) { setContentView(mContentLayoutId); } } } @CallSuper @Override protected void onSaveInstanceState(@NonNull Bundle outState) { Lifecycle lifecycle = getLifecycle(); if (lifecycle instanceof LifecycleRegistry) { ((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED); } super.onSaveInstanceState(outState); mSavedStateRegistryController.performSave(outState); } @NonNull @Override public Lifecycle getLifecycle() { return mLifecycleRegistry; } } 可以看到getLifecycle()返回的是LifecycleRegistry实例,并且在onSaveInstanceState()中分发了Lifecycle.State.CREATED状态,但是其他生命周期回调中并没有写了呀,嗯哼?再细看一下,onCreate()中有个ReportFragment.injectIfNeededIn(this),直接进去看看:public class ReportFragment extends Fragment { private static final String REPORT_FRAGMENT_TAG = "androidx.lifecycle" + ".LifecycleDispatcher.report_fragment_tag"; public static void injectIfNeededIn(Activity activity) { if (Build.VERSION.SDK_INT >= 29) { activity.registerActivityLifecycleCallbacks( new LifecycleCallbacks()); } android.app.FragmentManager manager = activity.getFragmentManager(); if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) { manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit(); // Hopefully, we are the first to make a transaction. manager.executePendingTransactions(); } } @SuppressWarnings("deprecation") static void dispatch(@NonNull Activity activity, @NonNull Lifecycle.Event event) { //已经被标注为@Deprecated if (activity instanceof LifecycleRegistryOwner) { ((LifecycleRegistryOwner) activity).getLifecycle().handleLifecycleEvent(event); return; } if (activity instanceof LifecycleOwner) { Lifecycle lifecycle = ((LifecycleOwner) activity).getLifecycle(); if (lifecycle instanceof LifecycleRegistry) { ((LifecycleRegistry) lifecycle).handleLifecycleEvent(event); } } } static ReportFragment get(Activity activity) { return (ReportFragment) activity.getFragmentManager().findFragmentByTag( REPORT_FRAGMENT_TAG); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); dispatchCreate(mProcessListener); dispatch(Lifecycle.Event.ON_CREATE); } @Override public void onStart() { super.onStart(); dispatchStart(mProcessListener); dispatch(Lifecycle.Event.ON_START); } @Override public void onResume() { super.onResume(); dispatchResume(mProcessListener); dispatch(Lifecycle.Event.ON_RESUME); } @Override public void onPause() { super.onPause(); dispatch(Lifecycle.Event.ON_PAUSE); } @Override public void onStop() { super.onStop(); dispatch(Lifecycle.Event.ON_STOP); } @Override public void onDestroy() { super.onDestroy(); dispatch(Lifecycle.Event.ON_DESTROY); // just want to be sure that we won't leak reference to an activity mProcessListener = null; } private void dispatch(@NonNull Lifecycle.Event event) { if (Build.VERSION.SDK_INT < 29) { dispatch(getActivity(), event); } } //API29及以上直接使用Application.ActivityLifecycleCallbacks来监听生命周期 static class LifecycleCallbacks implements Application.ActivityLifecycleCallbacks { @Override public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) { } @Override public void onActivityPostCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { dispatch(activity, Lifecycle.Event.ON_CREATE); } @Override public void onActivityStarted(@NonNull Activity activity) { } @Override public void onActivityPostStarted(@NonNull Activity activity) { dispatch(activity, Lifecycle.Event.ON_START); } @Override public void onActivityResumed(@NonNull Activity activity) { } @Override public void onActivityPostResumed(@NonNull Activity activity) { dispatch(activity, Lifecycle.Event.ON_RESUME); } @Override public void onActivityPrePaused(@NonNull Activity activity) { dispatch(activity, Lifecycle.Event.ON_PAUSE); } @Override public void onActivityPaused(@NonNull Activity activity) { } @Override public void onActivityPreStopped(@NonNull Activity activity) { dispatch(activity, Lifecycle.Event.ON_STOP); } @Override public void onActivityStopped(@NonNull Activity activity) { } @Override public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) { } @Override public void onActivityPreDestroyed(@NonNull Activity activity) { dispatch(activity, Lifecycle.Event.ON_DESTROY); } @Override public void onActivityDestroyed(@NonNull Activity activity) { } } } 代码的逻辑很清晰,主要通过一个透明的Fragment来分发生命周期事件,这样对于Activity来说是无侵入的。分成两部分逻辑:当API>=29时,直接使用Application.ActivityLifecycleCallbacks来分发生命周期事件;而当API<29时,在Fragment的生命周期回调中进行了事件分发。但殊途同归,两者最终都会走到dispatch(Activity activity, Lifecycle.Event event)方法中,该方法内部又调用了LifecycleRegistry#handleLifecycleEvent(event),我们继续去看该方法的实现://LifecycleRegistry.java public void handleLifecycleEvent(@NonNull Lifecycle.Event event) { State next = getStateAfter(event); moveToState(next); } static State getStateAfter(Event event) { switch (event) { case ON_CREATE: case ON_STOP: return CREATED; case ON_START: case ON_PAUSE: return STARTED; case ON_RESUME: return RESUMED; case ON_DESTROY: return DESTROYED; case ON_ANY: break; } throw new IllegalArgumentException("Unexpected event value " + event); } private void moveToState(State next) { //如果与当前状态一致 直接返回 if (mState == next) { return; } mState = next; if (mHandlingEvent || mAddingObserverCounter != 0) { mNewEventOccurred = true; // we will figure out what to do on upper level. return; } mHandlingEvent = true; sync(); mHandlingEvent = false; } getStateAfter()根据传入的Event返回State,比如ON_CREATE、ON_STOP之后对应的是CREATED,这里再把之前的这张图贴出来就一目了然了:得到state后,传入了moveToState()中,方法内部做了一些校验判断,然后又走到了sync()中:/** * Custom list that keeps observers and can handle removals / additions during traversal. * * Invariant: at any moment of time for observer1 & observer2: * if addition_order(observer1) < addition_order(observer2), then * state(observer1) >= state(observer2), */ private FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap = new FastSafeIterableMap<>(); private void sync() { LifecycleOwner lifecycleOwner = mLifecycleOwner.get(); if (lifecycleOwner == null) { throw new IllegalStateException("LifecycleOwner of this LifecycleRegistry is already" + "garbage collected. It is too late to change lifecycle state."); } while (!isSynced()) { mNewEventOccurred = false; // no need to check eldest for nullability, because isSynced does it for us. if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) { backwardPass(lifecycleOwner); } Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest(); if (!mNewEventOccurred && newest != null && mState.compareTo(newest.getValue().mState) > 0) { forwardPass(lifecycleOwner); } } mNewEventOccurred = false; } private boolean isSynced() { if (mObserverMap.size() == 0) { return true; } State eldestObserverState = mObserverMap.eldest().getValue().mState; State newestObserverState = mObserverMap.newest().getValue().mState; //最新状态和最老状态一致 且 当前状态与最新状态一致(传进来的状态与队列中的状态一致) 两个条件都符合时,即认为是状态同步完 return eldestObserverState == newestObserverState && mState == newestObserverState; } private void forwardPass(LifecycleOwner lifecycleOwner) { Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator = mObserverMap.iteratorWithAdditions(); while (ascendingIterator.hasNext() && !mNewEventOccurred) { Entry<LifecycleObserver, ObserverWithState> entry = ascendingIterator.next(); ObserverWithState observer = entry.getValue(); while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred && mObserverMap.contains(entry.getKey()))) { pushParentState(observer.mState); observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState)); popParentState(); } } } private void backwardPass(LifecycleOwner lifecycleOwner) { Iterator<Entry<LifecycleObserver, ObserverWithState>> descendingIterator = mObserverMap.descendingIterator(); while (descendingIterator.hasNext() && !mNewEventOccurred) { Entry<LifecycleObserver, ObserverWithState> entry = descendingIterator.next(); ObserverWithState observer = entry.getValue(); while ((observer.mState.compareTo(mState) > 0 && !mNewEventOccurred && mObserverMap.contains(entry.getKey()))) { Event event = downEvent(observer.mState); pushParentState(getStateAfter(event)); observer.dispatchEvent(lifecycleOwner, event); popParentState(); } } } FastSafeIterableMap< LifecycleObserver, ObserverWithState>实现了在遍历过程中的安全增删元素。LifecycleObserver是观察者,ObserverWithState则是对观察者的封装。isSynced()用来判断所有的观察者状态是否同步完,如果队列中新老状态不一致或者传进来的State与队列中的不一致,会继续往下走进入while循环,如果传进来的状态小于队列中的最大状态,backwardPass()将队列中所有大于当前状态的观察者同步到当前状态;如果存在队列中的状态小于当前状态的,那么通过forwardPass()将队列中所有小于当前状态的观察者同步到当前状态。同步过程都会执行到ObserverWithState#dispatchEvent()方法:static class ObserverWithState { State mState; LifecycleEventObserver mLifecycleObserver; ObserverWithState(LifecycleObserver observer, State initialState) { mLifecycleObserver = Lifecycling.lifecycleEventObserver(observer); mState = initialState; } void dispatchEvent(LifecycleOwner owner, Event event) { State newState = getStateAfter(event); mState = min(mState, newState); mLifecycleObserver.onStateChanged(owner, event); mState = newState; } } ObserverWithState#dispatchEvent()中调用了mLifecycleObserver.onStateChanged(),这个mLifecycleObserver是LifecycleEventObserver类型(父类是接口LifecycleObserver),在构造方法中通过Lifecycling.lifecycleEventObserver()创建的,最终返回的是ReflectiveGenericLifecycleObserver://ReflectiveGenericLifecycleObserver.java class ReflectiveGenericLifecycleObserver implements LifecycleEventObserver { private final Object mWrapped; private final CallbackInfo mInfo; ReflectiveGenericLifecycleObserver(Object wrapped) { mWrapped = wrapped; mInfo = ClassesInfoCache.sInstance.getInfo(mWrapped.getClass()); } @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Event event) { mInfo.invokeCallbacks(source, event, mWrapped); } } ClassesInfoCache内部存了所有观察者的回调信息,CallbackInfo是当前观察者的回调信息。getInfo()中如果从内存mCallbackMap中有对应回调信息,直接返回;否则通过createInfo()内部解析注解OnLifecycleEvent对应的方法并最终生成CallbackInfo返回。//ClassesInfoCache.java CallbackInfo getInfo(Class<?> klass) { CallbackInfo existing = mCallbackMap.get(klass); if (existing != null) { return existing; } existing = createInfo(klass, null); return existing; } private void verifyAndPutHandler(Map<MethodReference, Lifecycle.Event> handlers, MethodReference newHandler, Lifecycle.Event newEvent, Class<?> klass) { Lifecycle.Event event = handlers.get(newHandler); if (event == null) { handlers.put(newHandler, newEvent); } } private CallbackInfo createInfo(Class<?> klass, @Nullable Method[] declaredMethods) { Class<?> superclass = klass.getSuperclass(); Map<MethodReference, Lifecycle.Event> handlerToEvent = new HashMap<>(); if (superclass != null) { CallbackInfo superInfo = getInfo(superclass); if (superInfo != null) { handlerToEvent.putAll(superInfo.mHandlerToEvent); } } Class<?>[] interfaces = klass.getInterfaces(); for (Class<?> intrfc : interfaces) { for (Map.Entry<MethodReference, Lifecycle.Event> entry : getInfo( intrfc).mHandlerToEvent.entrySet()) { verifyAndPutHandler(handlerToEvent, entry.getKey(), entry.getValue(), klass); } } Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass); boolean hasLifecycleMethods = false; //遍历寻找OnLifecycleEvent注解对应的方法 for (Method method : methods) { OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class); if (annotation == null) { continue; } hasLifecycleMethods = true; Class<?>[] params = method.getParameterTypes(); int callType = CALL_TYPE_NO_ARG; if (params.length > 0) { callType = CALL_TYPE_PROVIDER; //第一个方法参数必须是LifecycleOwner if (!params[0].isAssignableFrom(LifecycleOwner.class)) { throw new IllegalArgumentException( "invalid parameter type. Must be one and instanceof LifecycleOwner"); } } Lifecycle.Event event = annotation.value(); if (params.length > 1) { callType = CALL_TYPE_PROVIDER_WITH_EVENT; //第2个参数必须是Lifecycle.Event if (!params[1].isAssignableFrom(Lifecycle.Event.class)) { throw new IllegalArgumentException( "invalid parameter type. second arg must be an event"); } //当有2个参数时,注解必须是Lifecycle.Event.ON_ANY if (event != Lifecycle.Event.ON_ANY) { throw new IllegalArgumentException( "Second arg is supported only for ON_ANY value"); } } if (params.length > 2) { throw new IllegalArgumentException("cannot have more than 2 params"); } MethodReference methodReference = new MethodReference(callType, method); verifyAndPutHandler(handlerToEvent, methodReference, event, klass); } CallbackInfo info = new CallbackInfo(handlerToEvent); mCallbackMap.put(klass, info); mHasLifecycleMethods.put(klass, hasLifecycleMethods); return info; } //CallbackInfo.java static class CallbackInfo { final Map<Lifecycle.Event, List<MethodReference>> mEventToHandlers; final Map<MethodReference, Lifecycle.Event> mHandlerToEvent; CallbackInfo(Map<MethodReference, Lifecycle.Event> handlerToEvent) { mHandlerToEvent = handlerToEvent; mEventToHandlers = new HashMap<>(); for (Map.Entry<MethodReference, Lifecycle.Event> entry : handlerToEvent.entrySet()) { Lifecycle.Event event = entry.getValue(); List<MethodReference> methodReferences = mEventToHandlers.get(event); if (methodReferences == null) { methodReferences = new ArrayList<>(); mEventToHandlers.put(event, methodReferences); } methodReferences.add(entry.getKey()); } } void invokeCallbacks(LifecycleOwner source, Lifecycle.Event event, Object target) { invokeMethodsForEvent(mEventToHandlers.get(event), source, event, target); invokeMethodsForEvent(mEventToHandlers.get(Lifecycle.Event.ON_ANY), source, event, target); } private static void invokeMethodsForEvent(List<MethodReference> handlers, LifecycleOwner source, Lifecycle.Event event, Object mWrapped) { if (handlers != null) { for (int i = handlers.size() - 1; i >= 0; i--) { handlers.get(i).invokeCallback(source, event, mWrapped); } } } 最终调用到了MethodReference#invokeCallback()://MethodReference.java static class MethodReference { final int mCallType; final Method mMethod; MethodReference(int callType, Method method) { mCallType = callType; mMethod = method; mMethod.setAccessible(true); } void invokeCallback(LifecycleOwner source, Lifecycle.Event event, Object target) { //noinspection TryWithIdenticalCatches try { //OnLifecycleEvent注解对应的方法入参 switch (mCallType) { //没有参数 case CALL_TYPE_NO_ARG: mMethod.invoke(target); break; //一个参数:LifecycleOwner case CALL_TYPE_PROVIDER: mMethod.invoke(target, source); break; //两个参数:LifecycleOwner,Event case CALL_TYPE_PROVIDER_WITH_EVENT: mMethod.invoke(target, source, event); break; } } catch (InvocationTargetException e) { throw new RuntimeException("Failed to call observer method", e.getCause()); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MethodReference that = (MethodReference) o; return mCallType == that.mCallType && mMethod.getName().equals(that.mMethod.getName()); } @Override public int hashCode() { return 31 * mCallType + mMethod.getName().hashCode(); } } 根据不同入参个数通过反射来初始化并执行观察者相应方法,整个流程就从LifecycleOwner中的生命周期Event传到了LifecycleObserver中对应的方法。到这里整个流程就差不多结束了,最后是LifecycleOwner的子类LifecycleRegistry添加观察者的过程://LifecycleRegistry.java @Override public void addObserver(@NonNull LifecycleObserver observer) { State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED; ObserverWithState statefulObserver = new ObserverWithState(observer, initialState); //key是LifecycleObserver,value是ObserverWithState ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver); //如果已经存在,直接返回 if (previous != null) { return; } LifecycleOwner lifecycleOwner = mLifecycleOwner.get(); if (lifecycleOwner == null) { // it is null we should be destroyed. Fallback quickly return; } boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent; //目标State State targetState = calculateTargetState(observer); mAddingObserverCounter++; //循环遍历,将目标State连续同步到Observer中 while ((statefulObserver.mState.compareTo(targetState) < 0 && mObserverMap.contains(observer))) { pushParentState(statefulObserver.mState); statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState)); popParentState(); // mState / subling may have been changed recalculate targetState = calculateTargetState(observer); } if (!isReentrance) { // we do sync only on the top level. sync(); } mAddingObserverCounter--; } private State calculateTargetState(LifecycleObserver observer) { Entry<LifecycleObserver, ObserverWithState> previous = mObserverMap.ceil(observer); State siblingState = previous != null ? previous.getValue().mState : null; State parentState = !mParentStates.isEmpty() ? mParentStates.get(mParentStates.size() - 1) : null; return min(min(mState, siblingState), parentState); } 添加观察者,并通过while循环,将最新的State状态连续同步到Observer中,虽然可能添加Observer比LifecyleOwner分发事件晚,但是依然能收到所有事件,类似于事件总线的粘性事件。最后画一下整体的类图关系:
0
0
0
浏览量2009
IT大鲨鱼

Kotlin | Flow数据流的几种使用场景

一 Flow使用注意事项多个Flow不能放到一个lifecycleScope.launch里去collect{},因为进入collect{}相当于一个死循环,下一行代码永远不会执行;如果就想写到一个lifecycleScope.launch{}里去,可以在内部再开启launch{}子协程去执行。示例,下面是错误写法: //NOTE: 下面的示例是错误写法 lifecycleScope.launch { mFlowModel.caseFlow1 .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .collect {} mFlowModel.caseFlow2 .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .collect {} }正确写法: lifecycleScope.launch { launch { mFlowModel.caseFlow1 .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .collect {} } launch { mFlowModel.caseFlow2 .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .collect {} } }当然,直接启动多个 lifecycleScope.launch也是可以的。二 几种使用场景2.1、处理复杂、耗时逻辑一般在处理复杂逻辑、耗时操作时,我们会将其放到子线程中去处理,避免在主线程中处理导致卡顿。而Flow可以方便地进行线程切换,所以处理复杂逻辑、耗时操作时,可以考虑使用Flow来进行处理,下面来看一个例子:假设我们想读取本地Assets目录下的person.json文件,并将其解析出来,json文件中的内容// assets目录下person.json{ "name": "小马快跑", "age": 18, "interest": "money! lots of money!"}下面通过Flow的方式实现在IO线程中读取json文件,并最终在主线程中输出结果:/** * 通过Flow方式,获取本地文件 */ private fun getFileInfo() { lifecycleScope.launch { flow { //解析本地json文件,并生成对应字符串 val configStr = getAssetJsonInfo(requireContext(), "person.json") //最后将得到的实体类发送到下游 emit(configStr) } .map { json -> Gson().fromJson(json, PersonModel::class.java) //通过Gson将字符串转为实体类 } .flowOn(Dispatchers.IO) //在flowOn之上的所有操作都是在IO线程中进行的 .onStart { log("onStart") } .filterNotNull() .onCompletion { log("onCompletion") } .catch { ex -> log("catch:${ex.message}") } .collect { log("collect parse result:$it") } } }/** * 读取Assets下的json文件 */private fun getAssetJsonInfo(context: Context, fileName: String): String { val strBuilder = StringBuilder() var input: InputStream? = null var inputReader: InputStreamReader? = null var reader: BufferedReader? = null try { input = context.assets.open(fileName, AssetManager.ACCESS_BUFFER) inputReader = InputStreamReader(input, StandardCharsets.UTF_8) reader = BufferedReader(inputReader) var line: String? while ((reader.readLine().also { line = it }) != null) { strBuilder.append(line) } } catch (ex: Exception) { ex.printStackTrace() } finally { try { input?.close() inputReader?.close() reader?.close() } catch (e: IOException) { e.printStackTrace() } } return strBuilder.toString()}执行结果:11:11:32.178 E onStart11:11:32.197 E collect parse result:PersonModel(name=小马快跑, age=18, interest=money! lots of money!)11:11:32.198 E onCompletion可以看到在collect{}中得到了正确的数据,这里注意一下flowOn()的作用域是在自身之上的操作,上述例子中flowOn(Dispatchers.IO) 意味着在flowOn之上的所有操作都是在IO线程中进行的。2.2、存在依赖关系的接口请求如果最终展示依赖多个接口且接口之间是有依赖关系的,之前我们可能会在第一个接口请求成功的回调里继续调用第二个接口,以此类推,这样虽然能实现,但是会导致回调层级很深,也就是所谓的回调地狱;此时可以使用Flow的flatMapConcat将多个接口串联起来。lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { //将两个flow串联起来 先搜索目的地,然后到达目的地 mFlowModel.getSearchFlow() .flatMapConcat { //第二个flow依赖第一个的结果 mFlowModel.goDestinationFlow(it) }.collect { mTvCallbackFlow.text = it ?: "error" } }}2.3、组合多个接口的数据有这样一种场景:数据的最终展示依赖多个接口请求到的数据,有两种实现方式:· 一个个串行去请求接口,拿到数据后最终拼到一起;· 所有接口并行去请求,拿到数据后最终拼到一起。串行请求虽然可以,但是效率并不高;更优的方式是采用接口并行,可以使用Flow的zip操作符,如下要获取电费、水费、网费的总花销,对应的花费需要各自请求自己的接口,最终把数据进行合并统计: //ViewModel中 //分别请求电费、水费、网费,Flow之间是并行关系 suspend fun requestElectricCost(): Flow<ExpendModel> = flow { delay(500) emit(ExpendModel("电费", 10f, 500)) }.flowOn(Dispatchers.IO) suspend fun requestWaterCost(): Flow<ExpendModel> = flow { delay(1000) emit(ExpendModel("水费", 20f, 1000)) }.flowOn(Dispatchers.IO) suspend fun requestInternetCost(): Flow<ExpendModel> = flow { delay(2000) emit(ExpendModel("网费", 30f, 2000)) }.flowOn(Dispatchers.IO) data class ExpendModel(val type: String, val cost: Float, val apiTime: Int) { fun info(): String { return "${type}: ${cost}, 接口请求耗时约$apiTime ms" }} //UI层 mBtnZip.setOnClickListener { lifecycleScope.launch { val electricFlow = mFlowModel.requestElectricCost() val waterFlow = mFlowModel.requestWaterCost() val internetFlow = mFlowModel.requestInternetCost() val builder = StringBuilder() var totalCost = 0f val startTime = System.currentTimeMillis() //NOTE:注意这里可以多个zip操作符来合并Flow,且多个Flow之间是并行关系 electricFlow.zip(waterFlow) { electric, water -> totalCost = electric.cost + water.cost builder.append("${electric.info()},\n").append("${water.info()},\n") }.zip(internetFlow) { two, internet -> totalCost += internet.cost two.append(internet.info()).append(",\n\n总花费:$totalCost") }.collect { mTvZipResult.text = it.append(",总耗时:${System.currentTimeMillis() - startTime} ms") } } }执行结果:电费: 10.0, 接口请求耗时约500 ms, 水费: 20.0, 接口请求耗时约1000 ms, 网费: 30.0, 接口请求耗时约2000 ms, 总花费:60.0,总耗时:2012 ms可以看到不但得到了所有接口的数据,而且总耗时基本等于耗时最长的接口的时间,说明zip操作符合并的多个Flow内部接口请求是并行的。
0
0
0
浏览量784
IT大鲨鱼

JNI 编程上手指南之描述符

1. 类描述符在 JNI 的 Native 方法中,我们要使用 Java 中的对象怎么办?即在 C/C++ 中怎么找到 Java 中的类,这就要使用到 JNI 开发中的类描述符了JNI 提供的函数中有个 FindClass() 就是用来查找 Java 类的,其参数必须放入一个类描述符字符串,类描述符一般是类的完整名称(包名+类名)一个 Java 类对应的描述符,就是类的全名,其中 . 要换成 / :完整类名:   java.lang.String 对应类描述符: java/lang/String jclass intArrCls = env->FindClass(“java/lang/String”) jclass clazz = FindClassOrDie(env, "android/view/Surface"); 2. 域描述符域描述符是 JNI 中对 Java 数据类型的一种表示方法。在 JVM 虚拟机中,存储数据类型的名称时,是使用指定的描述符来存储,而不是我们习惯的 int,float 等。虽然有类描述符,但是类描述符里并没有说明基本类型和数组类型如何表示,所以在 JNI 中就引入了域描述符的概念。接着我们通过一个表格来了解域描述符的定义:类型标识Java数据类型ZbooleanBbyteCcharSshortIintJlongFfloatDdoubleL包名/类名;各种引用类型Vvoid[数组方法(参数)返回值接着我们来看几个例子:Java类型:  java.lang.String JNI 域描述符:Ljava/lang/String; //注意结尾有分号 Java类型:   int[] JNI域描述符: [I Java类型:   float[] JNI域描述符: [F Java类型:   String[] JNI域描述符: [Ljava/lang/String; Java类型:   Object[ ] JNI域描述符: [[Ljava/lang/Object; Java类型:   int[][] JNI域描述符: [[I Java类型:   float[][] JNI域描述符: [[F 3. 方法描述符方法描述符是 JVM 中对函数(方法)的标记方式,看几个例子就能基本掌握其命名特点了:Java 方法 方法描述符 String fun() ()Ljava/lang/String; int fun(int i, Object object) (ILjava/lang/Object;)I void fun(byte[] bytes) ([B)V int fun(byte data1, byte data2) (BB)I void fun() ()V
0
0
0
浏览量2006
IT大鲨鱼

Kotlin | 高阶函数reduce()、fold()详解

.在 Kotlin 中,reduce() 和 fold() 是函数式编程中常用的高阶函数。它们都是对集合中的元素进行聚合操作的函数,将一个集合中的元素缩减成一个单独的值。它们的使用方式非常相似,但是返回值略有不同。下面是它们的区别:· reduce() 函数是对集合中的所有元素进行聚合处理,并返回最后一个合并处理值。· fold() 函数除了合并所有元素之外,还可以接受一个初始值,并将其与聚合结果一起返回。注:如果集合为空的话,只会返回初始值。reduce示例1、使用 reduce() 函数计算列表中所有数字的总和:fun reduceAdd() { val list = listOf(1, 2, 3, 4, 5) val sum = list.reduce { acc, i -> println("acc:$acc, i:$i") acc + i } println("sum is $sum") // 15 }执行结果:acc:1, i:2acc:3, i:3acc:6, i:4acc:10, i:5sum is 152、使用 reduce() 函数计算字符串列表中所有字符串的拼接结果:val strings = listOf("apple", "banana", "orange", "pear")val result = strings.reduce { acc, s -> "$acc, $s" }println(result) // apple, banana, orange, pear执行结果:apple, banana, orange, pearfold示例1、使用 fold() 函数计算列表中所有数字的总和,并在其基础上加上一个初始值:val numbers = listOf(1, 2, 3, 4, 5)val sum = numbers.fold(10) { acc, i -> acc + i }println(sum) // 25执行结果为:acc:10, i:1acc:11, i:2acc:13, i:3acc:16, i:4acc:20, i:5sum is 252、使用 fold() 函数将列表中的所有字符串连接起来,并在其基础上加上一个初始值:val strings = listOf("apple", "banana", "orange", "pear")val result = strings.fold("Fruits:") { acc, s -> "$acc $s" }println(result) // Fruits: apple banana orange pear执行结果:Fruits: apple banana orange pear源码解析· reduce() 在Kotlin标准库的实现如下:public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S { val iterator = this.iterator() if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.") var accumulator: S = iterator.next() while (iterator.hasNext()) { accumulator = operation(accumulator, iterator.next()) } return accumulator}从代码中可以看出,reduce函数接收一个operation参数,它是一个lambda表达式,用于聚合计算。reduce函数首先获取集合的迭代器,并判断集合是否为空,若为空则抛出异常。然后通过迭代器对集合中的每个元素进行遍历操作,对元素进行聚合计算,将计算结果作为累加器,传递给下一个元素,直至聚合所有元素。最后返回聚合计算的结果。· fold() 在Kotlin标准库的实现如下:public inline fun <T, R> Iterable<T>.fold( initial: R, operation: (acc: R, T) -> R): R { var accumulator: R = initial for (element in this) { accumulator = operation(accumulator, element) } return accumulator}从代码中可以看出,fold函数接收两个参数,initial参数是累加器的初始值,operation参数是一个lambda表达式,用于聚合计算。fold函数首先将初始值赋值给累加器,然后对集合中的每个元素进行遍历操作,对元素进行聚合计算,将计算结果作为累加器,传递给下一个元素,直至聚合所有元素。最后返回聚合计算的结果。总结· reduce()适用于不需要初始值的聚合操作,fold()适用于需要初始值的聚合操作。· reduce()操作可以直接返回聚合后的结果,而fold()操作需要通过lambda表达式的返回值来更新累加器的值。在使用时,需要根据具体场景来选择使用哪个函数。
0
0
0
浏览量644
IT大鲨鱼

JNI 编程上手指南之 JNI 调用性能优化

为什么要做性能优化Java 程序中,调用一个 Native 方法相比调用一个 Java 方法要耗时很多,我们应该减少 JNI 方法的调用,同时一次 JNI 调用尽量完成更多的事情。对于过于耗时的 JNI 调用,应该放到后台线程调用。Native 程序要访问 Java 对象的字段或调用它们的方法时,本机代码必须调用 FindClass()、GetFieldID()、GetStaticFieldID、GetMethodID() 和 GetStaticMethodID() 等方法,返回的 ID 不会在 JVM 进程的生存期内发生变化。但是,获取字段或方法的调用有时会需要在 JVM 中完成大量工作,因为字段和方法可能是从超类中继承而来的,这会让 JVM 向上遍历类层次结构来找到它们。为了提高性能,我们可以把这些 ID 缓存起来,用内存换性能。使用时缓存Java 层:public class TestJavaClass { //...... private void myMethod() { Log.i("JNI", "this is java myMethod"); } //...... } public native void cacheTest(); Natice 层extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_cacheTest(JNIEnv *env, jobject thiz) { jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass"); if (clazz == NULL) { return; } static jmethodID java_construct_method_id = NULL; static jmethodID java_method_id = NULL; //实现缓存的目的,下次调用不用再获取 methodid 了 if (java_construct_method_id == NULL) { //构造函数 id java_construct_method_id = env->GetMethodID(clazz, "<init>", "()V"); if (java_construct_method_id == NULL) { return; } } //调用构造函数,创建一个对象 jobject object_test = env->NewObject(clazz, java_construct_method_id); if (object_test == NULL) { return; } //相同的手法,缓存 methodid if (java_method_id == NULL) { java_method_id = env->GetMethodID(clazz, "myMethod", "()V"); if (java_method_id == NULL) { return; } } //调用 myMethod 方法 env->CallVoidMethod(object_test, java_method_id); env->DeleteLocalRef(clazz); env->DeleteLocalRef(object_test); } 手法还是比较简单的,主要是通过一个全局变量保存 methodid,这样只有第一次调用 native 函数时,才会调用 GetMethodID 去获取,后面的调用都使用缓存起来的值了。这样就避免了不必要的调用,提升了性能。静态初始化缓存Java 层: static { System.loadLibrary("myjnidemo"); initIDs(); } public static native void initIDs();C++ 层://定义用于缓存的全局变量 static jmethodID java_construct_method_id2 = NULL; static jmethodID java_method_id2 = NULL; extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_initIDs(JNIEnv *env, jclass clazz) { jclass clazz2 = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass"); if (clazz == NULL) { return; } //实现缓存的目的,下次调用不用再获取 methodid 了 if (java_construct_method_id2 == NULL) { //构造函数 id java_construct_method_id2 = env->GetMethodID(clazz2, "<init>", "()V"); if (java_construct_method_id2 == NULL) { return; } } if (java_method_id2 == NULL) { java_method_id2 = env->GetMethodID(clazz2, "myMethod", "()V"); if (java_method_id2 == NULL) { return; } } }手法和使用时缓存是一样的,只是缓存的时机变了。如果是动态注册的 JNI 还可以在 Onload 函数中来执行缓存操作。
0
0
0
浏览量2018
IT大鲨鱼

JNI 编程上手指南之 JavaVM 详解

JavaVM 是什么?JavaVM 是一个结构体,用于描述 Java 虚拟机。一个 JVM 中只有一个 JavaVM 对象。在 Android 平台上,一个 Java 进程只能有一个 ART 虚拟机,也就是说一个进程只有一个 JavaVM 对象。JavaVM 可以在进程中的各线程间共享接着我来看一下 JavaVM 在代码中是如何被定义的:struct JavaVM_; #ifdef __cplusplus typedef JavaVM_ JavaVM; //c++ 中,是 JavaVM_ #else typedef const struct JNIInvokeInterface_ *JavaVM; //c 中,是 JNIInvokeInterface_ #endif // JavaVM_ 主要是定义了几个成员函数 struct JavaVM_ { const struct JNIInvokeInterface_ *functions; #ifdef __cplusplus jint DestroyJavaVM() { return functions->DestroyJavaVM(this); } jint AttachCurrentThread(void **penv, void *args) { return functions->AttachCurrentThread(this, penv, args); } jint DetachCurrentThread() { return functions->DetachCurrentThread(this); } jint GetEnv(void **penv, jint version) { return functions->GetEnv(this, penv, version); } jint AttachCurrentThreadAsDaemon(void **penv, void *args) { return functions->AttachCurrentThreadAsDaemon(this, penv, args); } #endif }; //JNIInvokeInterface_ 主要定义了几个函数指针 struct JNIInvokeInterface_ { void *reserved0; void *reserved1; void *reserved2; jint (JNICALL *DestroyJavaVM)(JavaVM *vm); jint (JNICALL *AttachCurrentThread)(JavaVM *vm, void **penv, void *args); jint (JNICALL *DetachCurrentThread)(JavaVM *vm); jint (JNICALL *GetEnv)(JavaVM *vm, void **penv, jint version); jint (JNICALL *AttachCurrentThreadAsDaemon)(JavaVM *vm, void **penv, void *args); };如何获得 JavaVM动态注册时,可以在 JNI_OnLoad 的参数中获取到 JavaVM:JavaVM *gJavaVM; jint JNI_OnLoad(JavaVM * vm, void * reserved) { gJavaVM = vm //...... }也可以通过 JNIEnv 的函数获取到 JavaVM:JavaVM *gJavaVM; JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) { env->GetJavaVM(&gJavaVM); return (*env)->NewStringUTF(env,"Hello from JNI !"); }
0
0
0
浏览量2017
IT大鲨鱼

Kotlin | 利用扩展函数转换对象的一个小技巧

有这么一种场景:当我们从服务端请求到数据并转换为实体对象后,UI 界面层需要的却是另一个实体类,比如实际项目场景中:· case1: 前者实体类是用 Java 编写的,而后者由于某些原因必须使用Kotlin编写的一个实体类,那么就要进行一次类型转换了;· case2: 前者实体类也是Kotlin 编写的,但是只是最终实体类的一部分数据,最终数据是由多个实体类组合起来的,那么同样需要进行一次类型转换。· ... ...比如下面两个实体类:@Serializabledata class A( val id: String, val name: String = "", val shortDescription: String = "", val longDescription: String = "", val url: String = "", val imageUrl: String = "",)data class B( val id: String, val name: String, val shortDescription: String, val longDescription: String, val url: String, val imageUrl: String, val followed: Boolean = false,)其中A是从服务端获取数据后生成的,而UI界面层使用的是B。如果不想多考虑,那么直接在new B()的时候,将A实例中需要的数据赋值给B实例中的变量即可:val a: A = ......//假设从服务端生成了A实例//将A实例中的数据传到B中val b = B( id = a.id, name = a.name, shortDescription = a.shortDescription, longDescription = a.longDescription, url = a.url, imageUrl = a.imageUrl, followed = true, //followed从其他数据源获取 )//接下来就可以将b对象传到UI界面层进行使用了不知道有没有小伙伴像上面这样使用,反正我写的时候都是直接转(此时必须要上那个经典配图了): 直到我在学习谷歌的一个Demo项目时,看到了这种写法://顶级A扩展函数,将其转换成Bfun A.asEntity() = B( id = id, name = name, shortDescription = shortDescription, longDescription = longDescription, url = url, imageUrl = imageUrl, followed = true,)//使用的地方,如在ViewModel中suspend fun getA(ids: List<String>?): List<A>{ ......}//1、第1个地方使用val result1 : List<B> = network1.getA().map(A::asEntity)//2、第2个地方使用val result2 : List<B> = network2.getA().map(A::asEntity)不得不说这种写法给人眼前一亮的感觉(可能有的大佬早就这样写了���),A.asEntity()是一个顶级扩展函数,负责将A实例中的变量值赋给对应的B中,后续有修改可以统一在这里处理;另外顶级函数也意味着可以在任意有需要的地方调用它,比如上述示例的1、 2处都可以调用这个扩展函数,从而达到复用的目的。
0
0
0
浏览量2007
IT大鲨鱼

Kotlin | 使用vararg可变参数

背景一般在项目开发中,我们经常会在关键节点上埋点,而且埋点中会增加一些额外参数,这些参数通常是成对出现且参数个数是不固定的。如下://定义事件EVENT_IDconst val EVENT_ID = "event_xmkp"//注意:这里传入的是vararg可变参数fun String.log(vararg args: String) { if (args.size % 2 > 0) { throw RuntimeException("传入的参数必须是偶数") } if (args.isEmpty()) { buryPoint(this) } else { //注意这里:可变参数在作为数组传递时需要使用伸展(spread)操作符(在数组前面加 *) buryPoint(this, *args) } }private fun buryPoint(eventId: String, vararg args: String) { if (args.isNotEmpty()) { Log.e(TAG, "buryPoint: $eventId, args: ${args.toList()}") } else { Log.e(TAG, "buryPoint: $eventId") }}调用方式如下:EVENT_ID.log()EVENT_ID.log("name", "小马快跑")EVENT_ID.log("name", "小马快跑", "city", "北京")示例中可变参数可以是0个、2个、4个,执行结果:2022-11-22 19:00:54 E/TTT: eventID: event_xmkp2022-11-22 19:00:54 E/TTT: eventID: event_xmkp, args: [name, 小马快跑]2022-11-22 19:00:54 E/TTT: eventID: event_xmkp, args: [name, 小马快跑, city, 北京]可以看到通过定义可变参数,在调用方可以灵活地传入0个或多个参数,下面就分析下Kotlin方法中的可变参数。注意:可变参数在作为数组传递时需要使用伸展操作符(在数组前面加 *),如果去掉 *号,编译器会报如下错: Kotlin中使用可变参数Java中可变参数规则:· 使用...表示可变参数· 可变参数只能在参数列表的最后· 可变参数在方法体中最终是以数组的形式访问Kotlin中可变参数规则:· 不同于Java,在Kotlin中如果 vararg 可变参数不是列表中的最后一个参数, 可以使用具名参数语法传递其后的参数的值。· 和Java一样,在函数内,可以以数组的形式使用这个可变参数的形参变量,而如果需要传递可变参数,需要在前面加上伸展(spread)操作符(在数组前面加 *),第一节已给出示例。对Kotlin可变参数反编译对上一节中的String.log()代码反编译成Java代码://kt代码fun String.log(vararg args: String) { if (args.size % 2 > 0) { throw RuntimeException("传入的参数必须是偶数") } if (args.isEmpty()) { buryPoint(this) } else { //注意这里:可变参数在作为数组传递时需要使用伸展(spread)操作符(在数组前面加 *) buryPoint(this, *args) } }转换之后: // Java代码 public final void log(@NotNull String $this$log, @NotNull String... args) { ... if (args.length % 2 > 0) { throw (Throwable)(new RuntimeException("传入的参数必须是偶数")); } else { if (args.length == 0) { this.buryPoint($this$log); } else { this.buryPoint($this$log, (String[])Arrays.copyOf(args, args.length)); } } }· Kotlin的vararg args: String参数转换成Java的 @NotNull String... args· Kotlin的spread伸展操作符*args转换成Java的(String[])Arrays.copyOf(args, args.length),可见最终还是通过系统拷贝生成了数组。
0
0
0
浏览量934
IT大鲨鱼

Kotlin data数据类、copy()函数、sealed密封类

data数据类data class ModelA( val name: String = "", var age: Int = 10, var grade: Int = 6,)· 主构造函数需要至少有一个参数;· 主构造函数的所有参数需要标记为 val 或 var;· 数据类不能被abstract、open、sealed或者internal修饰;转换成Java类:public final class ModelA { @NotNull private final String name; private int age; private int grade; @NotNull public final String getName() { return this.name; } public final int getAge() { return this.age; } public final void setAge(int var1) { this.age = var1; } public final int getGrade() { return this.grade; } public final void setGrade(int var1) { this.grade = var1; } public ModelA(@NotNull String name, int age, int grade) { Intrinsics.checkNotNullParameter(name, "name"); super(); this.name = name; this.age = age; this.grade = grade; } // $FF: synthetic method public ModelA(String var1, int var2, int var3, int var4, DefaultConstructorMarker var5) { if ((var4 & 1) != 0) { var1 = ""; } if ((var4 & 2) != 0) { var2 = 10; } if ((var4 & 4) != 0) { var3 = 6; } this(var1, var2, var3); } public ModelA() { this((String)null, 0, 0, 7, (DefaultConstructorMarker)null); } @NotNull public final String component1() { return this.name; } public final int component2() { return this.age; } public final int component3() { return this.grade; } @NotNull public final ModelA copy(@NotNull String name, int age, int grade) { Intrinsics.checkNotNullParameter(name, "name"); return new ModelA(name, age, grade); } // $FF: synthetic method public static ModelA copy$default(ModelA var0, String var1, int var2, int var3, int var4, Object var5) { if ((var4 & 1) != 0) { var1 = var0.name; } if ((var4 & 2) != 0) { var2 = var0.age; } if ((var4 & 4) != 0) { var3 = var0.grade; } return var0.copy(var1, var2, var3); } @NotNull public String toString() { return "ModelA(name=" + this.name + ", age=" + this.age + ", grade=" + this.grade + ")"; } public int hashCode() { String var10000 = this.name; return ((var10000 != null ? var10000.hashCode() : 0) * 31 + Integer.hashCode(this.age)) * 31 + Integer.hashCode(this.grade); } public boolean equals(@Nullable Object var1) { if (this != var1) { if (var1 instanceof ModelA) { ModelA var2 = (ModelA)var1; if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age && this.grade == var2.grade) { return true; } } return false; } else { return true; } }}可以看到data数据类帮我们生成了equals()、hashCode()、toString()、copy()函数。copy()函数当需要复制一个对象,并需要改变部分属性值时,copy()函数为此而生。val modelA = ModelA(name = "A", age = 10, grade = 1)val modelB = modelA.copy(name = "B")log("modelA:$modelA,hashCode:${modelA.hashCode()}")log("modelB:$modelB,hashCode:${modelB.hashCode()}")//执行结果:modelA:ModelA(name=A, age=10, grade=1),hashCode:62776modelB:ModelA(name=B, age=10, grade=1),hashCode:63737如果copy()函数中改变了对象中的属性,会通过new重新生成一个新对象,示例中通过结果中不同的hashCode值即可看到;而如果只是调用copy()函数,并未改变对象的属性值时,通过实验发现两者的hashCode值并未改变。sealed密封类sealed密封类用来表示受限的类继承结构:当一个值为有限几种的类型、而不能有任何其他类型时。可以将密封类对比枚举类:枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。sealed class Async<out T> { object Loading : Async<Nothing>() data class Success<out T>(val data: T) : Async<T>()}转换成Java类://注意看,这里是abstract抽象类public abstract class Async { private Async() { } // $FF: synthetic method public Async(DefaultConstructorMarker $constructor_marker) { this(); } public static final class Loading extends Async { @NotNull public static final Async.Loading INSTANCE; private Loading() { super((DefaultConstructorMarker)null); } static { Async.Loading var0 = new Async.Loading(); INSTANCE = var0; } } public static final class Success extends Async { private final Object data; public final Object getData() { return this.data; } public Success(Object data) { super((DefaultConstructorMarker)null); this.data = data; } public final Object component1() { return this.data; } @NotNull public final Async.Success copy(Object data) { return new Async.Success(data); } // $FF: synthetic method public static Async.Success copy$default(Async.Success var0, Object var1, int var2, Object var3) { if ((var2 & 1) != 0) { var1 = var0.data; } return var0.copy(var1); } @NotNull public String toString() { return "Success(data=" + this.data + ")"; } public int hashCode() { Object var10000 = this.data; return var10000 != null ? var10000.hashCode() : 0; } public boolean equals(@Nullable Object var1) { if (this != var1) { if (var1 instanceof Async.Success) { Async.Success var2 = (Async.Success)var1; if (Intrinsics.areEqual(this.data, var2.data)) { return true; } } return false; } else { return true; } } }}可以看到转换成Java类之后,sealed密封类自身是abstract抽象类,不能直接进行实例化。 sealed密封类的典型用法即是在when(){}中,当传入的是密封类时,不用再写else分支了,如:fun processResult(result: Async<String>) { when (result) { is Async.Loading -> { // do something } is Async.Success -> { //do something } //这里不用再写else逻辑了 }}
0
0
0
浏览量1442
IT大鲨鱼

Kotlin中正确的使用Handler

Handler造成的内存泄漏Handler中的几个关键角色:· Handler:负责发送和处理Message消息;· Message:消息载体;· MessageQueue:消息队列,负责存储Message消息。· Looper:每个Thread中只有一个对应的Looper,负责不断循环从MessageQueue中获取Message,并且不断通过msg.target(Handler)将消息取出来并执行。Handler的详解参见:Android异步消息处理机制之Handler。如果Handler在Activity中是以非静态内部类的方式初始化的,那么Handler默认就会持有Activity的实例,因为在Java中:非静态内部类默认会持有外部类的实例,而静态内部类不会持有外部类的实例。在Handler中发送延迟消息,如使用sendMessageDelayed(msg, delayMillis)发送消息,并且在msg消息还在MessageQueue中没有得到处理时就关闭了当前页面(Activity调用了finish()),类持有关系是Looper -> MessageQueue -> Message -> Handler -> Activity,而在UI线程中的Looper.loop()是会一直执行的,即UI线程中Looper的生命周期跟Application一样长,从而导致Activity不能及时被回收导致内存泄漏。通过static内部类 + WeakReference弱引用的方式可以避免内存泄漏的产生。Kotlin中使用Handler在Kotlin中,并不能直接通过static关键字来声明静态类,那么如何声明一个静态内部类呢?其实在Kotlin中,直接在一个类中声明另一个类,经过Kotlin编译器之后自动就是static静态内部类了,如://Outer.ktclass Outer { private val bar: Int = 1 class Inner { //val value = bar //错误!静态内部类不能访问外部类的成员变量,所以这里访问不了外部类的bar }}反编译成Java文件之后public final class Outer { private final int bar = 1; public static final class Inner { }}可以看到编译之后Inner内部类已经是静态内部类了。如果想访问外部类的成员变量,可以将内部类声明为非静态内部类,只需要加上inner关键字就可以了,如下://Outer.ktclass Outer { private val bar: Int = 1 inner class Inner { val value = bar //非静态内部类能够直接访问外部类的成员变量 }}反编译成Java文件之后:public final class Outer { private final int bar = 1; public final class Inner { private final int value; public final int getValue() { return this.value; } public Inner() { this.value = Outer.this.bar; } }}通过inner关键字转换成非静态内部类,可以直接访问外部类的成员变量了。我们知道了如何在Kotlin里写静态内部类,那么就可以在Kotlin里以static内部类 + WeakReference弱引用的方式来使用Handler了。class HandlerActivity : AppCompatActivity() { companion object { const val WHAT_HINT_TEXT = 1000 //MSG_WHAT } private val mOutPut = "我输出了" //成员变量 private val weakHandler by lazy { WeakReferenceHandler(this) } //static + 弱引用 class WeakReferenceHandler(obj: HandlerActivity) : Handler(Looper.getMainLooper()) { private val mRef: WeakReference<HandlerActivity> = WeakReference(obj) override fun handleMessage(msg: Message) { mRef.get()?.run { when (msg.what) { WHAT_HINT_TEXT -> println(mOutPut) //可以直接访问Activity中的变量 } } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) weakHandler.sendEmptyMessageDelayed(WHAT_HINT_TEXT, 5000) } override fun onDestroy() { //退出页面时,置空所以的Message weakHandler.removeCallbacksAndMessages(null) super.onDestroy() }}上述代码即是在Kotlin的UI线程中使用Handler的一个例子,通过static + 弱引用 + onDestroy中remove Messages避免内存泄漏。
0
0
0
浏览量537
IT大鲨鱼

JNI 编程上手指南之 Native 访问 Java

本文接着介绍如何在 C/C++ 中访问 Java,主要从以下几个方面来讲述:访问 Java 的成员变量,包括了实例成员和静态成员访问 Java 的方法,包括了成员方法和静态方法1. Native 访问 Java 成员变量我们直接看 Demo:Java 层://定义一个被访问的类 public class TestJavaClass { private String mString = "Hello JNI, this is normal string !"; private static int mStaticInt = 0; } //定义两个 native 方法 public native void accessJavaFiled(TestJavaClass testJavaClass); public native void accessStaticField(TestJavaClass testJavaClass); c++ 层://访问成员变量 extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_accessJavaFiled(JNIEnv *env, jobject thiz, jobject test_java_class) { jclass clazz; jfieldID mString_fieldID; //获得 TestJavaClass 的 jclass 对象 // jclass 类型是一个局部引用 clazz = env->GetObjectClass(test_java_class); if (clazz == NULL) { return; } //获得 mString 的 fieldID mString_fieldID = env->GetFieldID(clazz, "mString", "Ljava/lang/String;"); if (mString_fieldID == NULL) { return; } //获得 mString 的值 jstring j_string = (jstring) env->GetObjectField(test_java_class, mString_fieldID); //GetStringUTFChars 分配了内存,需要使用 ReleaseStringUTFChars 释放 const char *buf = env->GetStringUTFChars(j_string, NULL); //修改 mString 的值 char *buf_out = "Hello Java, I am JNI!"; jstring temp = env->NewStringUTF(buf_out); env->SetObjectField(test_java_class, mString_fieldID, temp); //jfieldID 不是 JNI 引用类型,不用 DeleteLocalRef // jfieldID 是一个指针类型,其内存的分配与回收由 JVM 负责,不需要我们去 free //free(mString_fieldID); //释放内存 env->ReleaseStringUTFChars(j_string, buf); //释放局部引用表 env->DeleteLocalRef(j_string); env->DeleteLocalRef(clazz); } //访问静态成员变量 extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_accessStaticField(JNIEnv *env, jobject thiz, jobject test_java_class) { jclass clazz; jfieldID mStaticIntFiledID; clazz = env->GetObjectClass(test_java_class); if (clazz == NULL) { return; } mStaticIntFiledID = env->GetStaticFieldID(clazz, "mStaticInt", "I"); //获取静态成员 jint mInt = env->GetStaticIntField(clazz, mStaticIntFiledID); //修改静态成员 env->SetStaticIntField(clazz, mStaticIntFiledID, 10086); env->DeleteLocalRef(clazz); } 访问一个类成员基本分为三部:获取到类对应的 jclass 对象(对应于 Java 层的 Class 对象),jclss 是一个局部引用,使用完后记得使用 DeleteLocalRef 以避免局部引用表溢出。获取到需要访问的类成员的 jfieldID,jfieldID 不是一个 JNI 引用类型,是一个普通指针,指针指向的内存又 JVM 管理,我们无需在使用完后执行 free 清理操作根据被访问对象的类型,使用 GetxxxField 和 SetxxxField 来获得/设置成员变量的值2. Native 访问 Java 方法我们直接看 Demo:Java 层//等待被 native 层访问的 java 类 public class TestJavaClass { //...... private void myMethod() { Log.i("JNI", "this is java myMethod"); } private static void myStaticMethod() { Log.d("JNI", "this is Java myStaticMethod"); } } //本地方法 public native void accessJavaMethod(); public native void accessStaticMethod(); C++ 层:extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_accessJavaMethod(JNIEnv *env, jobject thiz) { //获取 TestJavaClass 对应的 jclass jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass"); if (clazz == NULL) { return; } //构造函数 id jmethodID java_construct_method_id = env->GetMethodID(clazz, "<init>", "()V"); if (java_construct_method_id == NULL) { return; } //创建一个对象 jobject object_test = env->NewObject(clazz, java_construct_method_id); if (object_test == NULL) { return; } //获得 methodid jmethodID java_method_id = env->GetMethodID(clazz, "myMethod", "()V"); if (java_method_id == NULL) { return; } //调用 myMethod 方法 env->CallVoidMethod(object_test,java_method_id); //清理临时引用吧 env->DeleteLocalRef(clazz); env->DeleteLocalRef(object_test); } extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_accessStaticMethod(JNIEnv *env, jobject thiz) { jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass"); if (clazz == NULL) { return; } jmethodID static_method_id = env->GetStaticMethodID(clazz, "myStaticMethod", "()V"); if(NULL == static_method_id) { return; } env->CallStaticVoidMethod(clazz, static_method_id); env->DeleteLocalRef(clazz); } Native 访问一个 Java 方法基本分为三部:获取到类对应的 jclass 对象(对应于 Java 层的 Class 对象),jclss 是一个局部引用,使用完后记得使用 DeleteLocalRef 以避免局部引用表溢出。获取到需要访问的方法的 jmethodID,jmethodID 不是一个 JNI 引用类型,是一个普通指针,指针指向的内存由 JVM 管理,我们无需在使用完后执行 free 清理操作接着就可以调用 CallxxxMethod/CallStaticxxxMethod 来调用对于的方法,xxx 是方法的返回类型。
0
0
0
浏览量2009
IT大鲨鱼

JUC系列学习(五):CountDownLatch、Semaphore、CyclicBarrier的

CountDownLatchCountDownLatch通过计数器实现一个线程等待其他若干线程执行完后,本线程再继续执行的功能。使用举例public static void main(String[] args) throws InterruptedException { //CountDownLatch传入数量n 主线程执行await()后 阻塞等待两个子线程执行 // 当子线程执行完毕时会调用countDown() 此时n会减一 当n减至0时主线程会重新执行 CountDownLatch latch = new CountDownLatch(2); MyRunnable runnable = new MyRunnable(latch); Thread thread1 = new Thread(runnable, ThreadConsts.THREAD_1); Thread thread2 = new Thread(runnable, ThreadConsts.THREAD_2); thread1.start(); thread2.start(); System.out.println("Main Thread: 开始等待其他线程执行"); latch.await(); System.out.println("Main Thread: 继续执行"); } static class MyRunnable implements Runnable { private CountDownLatch latch; MyRunnable(CountDownLatch latch) { this.latch = latch; } @Override public void run() { System.out.println(Thread.currentThread().getName() + ":开始执行"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":执行完毕"); latch.countDown(); } } 执行结果:Main Thread: 开始等待其他线程执行 线程1:开始执行 线程2:开始执行 线程1:执行完毕 线程2:执行完毕 Main Thread: 继续执行 源码解析public class CountDownLatch { private final Sync sync; public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); } public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } public void countDown() { sync.releaseShared(1); } public long getCount() { return sync.getCount(); } public String toString() { return super.toString() + "[Count = " + sync.getCount() + "]"; } //静态内部类Sync private static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 4982264981922014374L; Sync(int count) { setState(count); } int getCount() { return getState(); } protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { int c = getState(); if (c == 0) return false; int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } } } } CountDownLatch构造方法初始化了Sync内部类,本质上是通过AQS实现的共享锁SemaphoreSemaphore 翻译为信号量,是一种共享锁。可以同时允许一个或多个线程同时共享资源,Semaphore的构造参数如下:public Semaphore(int permits) { sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); } permits表示资源的最大访问量,表示最多有Permits个线程同时访问资源,fair表示是否是公平锁。Semaphore使用时的几个主要方法:public void acquire() //获取一个许可 成功返回true 失败返回false并进入队列等待 public void acquire(int permits) //获取permits个许可 public void release() //释放一个许可 public void release(int permits) //释放permits个许可 使用举例public static void main(String[] args) throws InterruptedException { //银行一共有2个柜台 true表示公平锁 false是非公平锁 Semaphore semaphore = new Semaphore(2, true); //一共有10个顾客来办理业务 for (int i = 0; i < 10; i++) { Client client = new Client(semaphore, "client" + i); client.start(); Thread.sleep(10); } } private static class Client extends Thread { private Semaphore semaphore; private String threadName; Client(Semaphore semaphore, String threadName) { this.semaphore = semaphore; this.threadName = threadName; } @Override public void run() { try { //acquire()获取一次使用权限 semaphore.acquire(); System.out.println(threadName + "开始办理业务,当前可使用许可数(空闲柜台数):" + semaphore.availablePermits()); Thread.sleep(1000); //release释放一次使用权限 semaphore.release(); System.out.println("==>" + threadName + "结束办理业务,当前可使用许可数(空闲柜台数):" + semaphore.availablePermits()); } catch (InterruptedException e) { e.printStackTrace(); } } } 执行结果:client0开始办理业务,当前可使用许可数(空闲柜台数):1 client1开始办理业务,当前可使用许可数(空闲柜台数):0 ==>client0结束办理业务,当前可使用许可数(空闲柜台数):1 client2开始办理业务,当前可使用许可数(空闲柜台数):0 ==>client1结束办理业务,当前可使用许可数(空闲柜台数):1 client3开始办理业务,当前可使用许可数(空闲柜台数):0 ==>client2结束办理业务,当前可使用许可数(空闲柜台数):1 client4开始办理业务,当前可使用许可数(空闲柜台数):0 ==>client3结束办理业务,当前可使用许可数(空闲柜台数):1 client5开始办理业务,当前可使用许可数(空闲柜台数):0 ==>client4结束办理业务,当前可使用许可数(空闲柜台数):1 client6开始办理业务,当前可使用许可数(空闲柜台数):0 ==>client5结束办理业务,当前可使用许可数(空闲柜台数):1 client7开始办理业务,当前可使用许可数(空闲柜台数):0 ==>client6结束办理业务,当前可使用许可数(空闲柜台数):0 client8开始办理业务,当前可使用许可数(空闲柜台数):0 ==>client7结束办理业务,当前可使用许可数(空闲柜台数):1 client9开始办理业务,当前可使用许可数(空闲柜台数):0 ==>client8结束办理业务,当前可使用许可数(空闲柜台数):1 ==>client9结束办理业务,当前可使用许可数(空闲柜台数):2 源码解析public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public void acquire(int permits) throws InterruptedException { if (permits < 0) throw new IllegalArgumentException(); sync.acquireSharedInterruptibly(permits); } public void release(int permits) { if (permits < 0) throw new IllegalArgumentException(); sync.releaseShared(permits); } public void release() { sync.releaseShared(1); } //tryAcquire跟上面的acquire方法一样回去尝试获取锁,不同的是tryAcquire可以立即获取执行结果 public boolean tryAcquire() { return sync.nonfairTryAcquireShared(1) >= 0; } public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } public boolean tryAcquire(int permits) { if (permits < 0) throw new IllegalArgumentException(); return sync.nonfairTryAcquireShared(permits) >= 0; } public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { if (permits < 0) throw new IllegalArgumentException(); return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout)); } Semaphore构造方法初始化了Sync内部类,本质上也是通过AQS实现的共享锁。CyclicBarrierCyclicBarrier意为循环栅栏,可以实现一组线程等待至某个状态之后再全部继续执行。当所有线程执行完毕后,CyclicBarrier还可以继续被重用。使用举例public static void main(String[] args) { System.out.println("CyclicBarrier例子:"); //构造方法中传入的Runnable是由最后通过栅栏的线程去执行,如本例中栅栏数声明为3,当最后一个线程执行await()时,最后这个线程会再去执行这里声明的Runnable任务 //这里Runnable是非必须的,不声明的话不会执行,同时CyclicBarrier将允许所有线程继续执行。 CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "是最后一个通过栅栏的线程,它继续执行CyclicBarrier构造方法中的Runnable任务(如有)"); } }); for (int i = 0; i < 3; i++) { new Thread(new MyTask(barrier), ThreadConsts.THREAD + i).start(); } } static class MyTask implements Runnable { CyclicBarrier barrier; MyTask(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { try { System.out.println(Thread.currentThread().getName() + ":开始执行"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + ":执行完毕,等待其他线程执行"); barrier.await(); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName() + ":通过栅栏,所有任务执行完毕"); } } } 执行结果:CyclicBarrier例子: 线程1:开始执行 线程0:开始执行 线程2:开始执行 线程2:执行完毕,等待其他线程执行 线程0:执行完毕,等待其他线程执行 线程1:执行完毕,等待其他线程执行 线程1是最后一个通过栅栏的线程,它继续执行CyclicBarrier构造方法中的Runnable任务(如有) 线程1:通过栅栏,所有任务执行完毕 线程2:通过栅栏,所有任务执行完毕 线程0:通过栅栏,所有任务执行完毕 源码解析public CyclicBarrier(int parties) { this(parties, null); } public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction; } public int await() throws InterruptedException, BrokenBarrierException { try { return dowait(false, 0L); } catch (TimeoutException toe) { throw new Error(toe); // cannot happen } } public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException { return dowait(true, unit.toNanos(timeout)); } private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock; lock.lock(); try { final Generation g = generation; int index = --count; //1、当index==0时表示所有线程都执行了await操作 if (index == 0) { // tripped boolean ranAction = false; try { //2、如果barrierCommand不为空且index==0,则由最后通过栅栏的线程去执行该barrierCommand final Runnable command = barrierCommand; if (command != null) command.run(); ranAction = true; nextGeneration(); return 0; } finally { if (!ranAction) breakBarrier(); } } // loop until tripped, broken, interrupted, or timed out for (;;) { try { if (!timed) //3、阻塞等待其他线程去执行 trip.await(); else if (nanos > 0L) nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) { } } } finally { lock.unlock(); } } //唤醒所有线程继续执行 private void nextGeneration() { // signal completion of last generation trip.signalAll(); // set up next generation count = parties; generation = new Generation(); } 总结CountDownLatch 和 CyclicBarrier都可以实现线程之间的等待,但是两者的侧重点不同:· CountDownLatch侧重于一个线程等待其他若干个线程执行完之后,这个线程会在其他线程执行完毕后继续执行该线程。· CyclicBarrier侧重于多个线程互相等待至某个状态,然后这一组线程就会继续同时执行;· CountDownLatch不可复用,CyclicBarrier可以复用
0
0
0
浏览量827
IT大鲨鱼

JUC系列学习(二):AbstractQueuedSynchronizer同步器框架及相关实现类

一 同步锁在并发编程中,我们经常用到的是synchronized和ReentrantLock。其中,synchronized是jvm内置锁,而ReentrantLock位于java.util.concurrent包下(以下简称JUC),ReentrantLock是基于AbstractQueuedSynchronizer(以下简称AQS)同步器框架实现的,本文主要来介绍AQS的内部实现及在JUC中基于AQS实现的相关类。二 AQS内部实现AQS是一个抽象类,内部维护一个state变量(代表共享资源)、一个FIFO等待队列(用来获取共享资源、线程排队管理等)。AQS定义了两种访问共享资源的方式:Exclusive(独占方式,每次只有一个线程访问资源,如:ReentrantLock)、Share(共享方式,多个线程可以同时访问资源,如:Semaphore、CountDownLatch等)。不管是独占方式还是共享方式,其具体实现只需要实现共享资源state的获取和释放即可,等待队列的相关操作(如获取锁失败入队、唤醒出队等),AQS已经实现好了。注:虽然AQS提供了独占和共享两种方式访问共享资源,但是两者并不一定是互斥的,还可以是独占和共享共存的方式访问共享资源,如ReentrantReadWriteLock(后面单独分析)。1、等待队列Node是AQS中的一个静态内部类,上面说到AQS中维护了一个FIFO的双端等待队列,当获取锁失败时,AQS会将当前线程及等待状态等信息构造成一个节点Node,加入到等待队列的队尾,并且当前线程变成阻塞状态,等待唤醒。//等待队列的Head节点 private transient volatile Node head; //等待队列的Tail节点 private transient volatile Node tail; static final class Node { //共享锁对应节点 static final Node SHARED = new Node(); //独占锁对应节点 static final Node EXCLUSIVE = null; //等待状态 volatile int waitStatus; Node nextWaiter; static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; //前驱节点 volatile Node prev; //后继节点 volatile Node next; //当前线程 volatile Thread thread; final boolean isShared() { return nextWaiter == SHARED; } //找到当前节点的前驱节点 final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } Node() {} //在addWaiter()时调用 Node(Thread thread, Node mode) { this.nextWaiter = mode; this.thread = thread; } //在Condition中调用 Node(Thread thread, int waitStatus) { this.waitStatus = waitStatus; this.thread = thread; } } · waitStatus:当前线程在等待队列中的状态waitStatus值功能SIGNAL-1当前节点的后继节点被阻塞(通过park)时,设置其前驱节点的状态为SIGNAL。标记为SIGNAL节点的线程释放锁时就会通知后继节点,使得后继节点所在的线程被唤醒。这个状态一般是后继节点来设置前驱节点的。CANCELLED1由于超时(timeout)或中断(interrupt),队列中此节点被取消。节点进入此状态后不会再变化。CONDITION-2此节点当前位于条件队列(condition queue)中。当其他线程对这个Condition调用signal方法后,它会被转移同步队列(sync queue)中。PROPAGATE-3已发布的节点应该传播到其他节点。这在doReleaseShared中设置(仅针对head节点),以确保传播继续进行,即使其他操作已经介入。00以上的都不是,默认初始无锁状态· prev :等待队列(Sync Queue)中前驱节点· next :等待队列(Sync Queue)中后继节点· thread : 当前线程· nextWaiter:条件队列(Condition Queue)的后继节点注:等待队列(Sync Queue)在独占模式、共享模式中都会使用到,条件队列(Condition Queue)只会在独占锁中使用。如:ReentrantLock lock = new ReentrantLock();Condition condition = lock.newCondition();condition.await();condition.signal();通过ReentrantLock可以构造Condition,调用condition的await/signal方法时会把当前线程在Condition Queue中执行入队/出队操作。当调用condition.await()时,Node节点就会从Sync Queue中进入到Condition Queue中,且对应的pre、next都会被置为null,waitStatus变为CONDITION,并通过nextWaiter形成链表;而当调用condition.signal()时,Node又会从Condition Queue进入到Sync Queue中,pre、next重新设置,waitStatus根据当前状态进行设置,nextWaiter会被置为null。注:当调用多个lock.newCondition()时,就会产生多个Condition队列,即一个ReentrantLock可以对应多个Condition Queue。2、状态指示器statestate表示当前锁的状态,state=0是初始状态,即无锁状态; 当state>0 表示已经有线程获得了锁,当同一个线程多次获得同步锁时(可重入性),state会递增,而在释放锁的时候,state会进行递减直到state=0时,其他线程才有资格获取锁。独占模式下只有一个线程能够获取同步锁,而共享模式下可以有多个线程可以获取同步锁。private volatile int state; protected final int getState() { return state; } protected final void setState(int newState) { state = newState; } //通过CAS方式更新state值 protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } 继承AQS的同步器主要实现:独占锁需要实现:· isHeldExclusively():是否是独占资源,当用到condition时需要实现它。· tryAcquire(int arg):独占锁方式,尝试获取资源,成功返回true,否则返回false。· tryRelease(int arg):独占锁方式,释放资源,如果释放操作使得所有获取同步器时被阻塞的线程恢复执行,那么返回的是true,否则返回false。共享锁需要实现:· tryAcquireShared(int arg):共享方式获取资源,负数表示获取操作失败;0代表成功,但无剩余资源;正数代表成功,且有剩余资源。· tryReleaseShared(int arg):共享方式尝试释放资源,如果释放后允许唤醒后续节点返回true,否则返回false。我们来看下上述方法在AQS中的实现:protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); } protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); } protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); } protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); } 无论是独占锁还是共享锁,获取锁和释放锁在AQS中都是没有实现的,需要在自定义同步器中实现独占锁相关:· acquire(int arg): 独占模式下尝试获取共享资源,忽略中断操作。获取成功返回true,否则线程进入等待队列中,等待被唤醒后继续尝试获取共享资源直到成功为止。本方法可以在实现Lock.lock()中使用。acquire()在AQS中的源码://tryAcquire尝试获取资源(在子类中实现),如成功直接返回,否则通过addWaiter及acquireQueued将该线程加入到等待队列的队尾,标记为独占模式,如果该线程在等待过程中中断过,acquireQueued方法会返回true,否则返回false。 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //如果tail为空(即队列为空),或通过CAS设置node到队尾失败,继续通过enq()循环设置node到队尾,直至成功为止 enq(node); return node; } //将node设置到队列尾部 private final boolean compareAndSetTail(Node expect, Node update) { return unsafe.compareAndSwapObject(this, tailOffset, expect, update); } private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } /** *1、当前node节点的前驱节点是SIGNAL状态,返回true,意味着当前线程会被挂起,阻塞等待。 *2、如果前驱节点是CANCEL状态(waitStatus>0),跳过此节点并删除,继续往上找,直到找到不是CANCEL状态的节点作为其前驱节点为止;如果前驱节点是0或者PROPAGATE状态,则将前驱节点设置为SIGNAL状态,则在下一次执行循环时shouldParkAfterFailedAcquire可以直接返回true,挂起线程。 **/ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) return true; if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); } static void selfInterrupt() { Thread.currentThread().interrupt(); } · acquireInterruptibly(int arg) : 同acquire方法,区别在于如果收到中断通知会直接中断。本方法可以在Lock.lockInterruptibly()中使用。public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); } private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } } 三 JUC中基于AQS的相关实现基于AQS构建的同步器类中,最基本的操作就是各种形式的获取操作和释放操作。获取操作是一种依赖状态的操作,通常会阻塞;释放操作时被阻塞的线程会重新开始执行。前面也说到了,AQS通过管理state变量来进行相关操作,跟state相关的方法有getState、setState、compareAndSetState,并且state在不同实现AQS的子类中代表的意思也各不相同。如:state 在 ReentrantLock中代表的是锁所有者线程重复获取该锁的次数;Semaphore 中 state代表剩余的许可数量;FutureTask中state用来表示任务的状态(未开始、正在运行、已完成、已取消)。· ReentrantLock:只支持独占方式获取锁,所以内部只实现了tryAcquire、tryRelease、isHeldExclusively。ReentrantLock利用AQS对多个条件变量、多个等待线程集内置支持。Lock.newCondition返回一个新的ConditionObject实例。· CountDownLatch:用于保存当前许可的数量。获取操作意味着“等待并直到闭锁到达结束状态”· Semaphore:Semaphore翻译为信号量,是一种共享锁,可以同时允许一个或多个线程同时共享资源。· CyclicBarrier:CyclicBarrier意为循环栅栏,可以实现一组线程等待至某个状态之后再全部继续执行。当所有线程执行完毕后,CyclicBarrier还可以继续被重用。· ReentrantReadWriteLock:ReentrantReadWriteLock是独占锁(写锁)、共享锁(读锁)可以同时存在的一种读写锁,在读操作远大于写操作的场景中,能实现更好的并发性。· ThreadPoolExecutor:线程池,内部实现使用的ReentrantLock。另外Android中AsyncTask内部实现即是ThreadPoolExecutor。四 总结获取锁的流程:· AQS 的模板方法 acquire 通过调用子类自定义实现的 tryAcquire 获取锁;· 如果获取锁失败,通过 addWaiter 方法将线程构造成 Node 节点插入到同步队列队尾;· 在 acquirQueued 方法中以自旋的方法尝试获取锁,如果失败则判断是否需要将当前线程阻塞,如果需要阻塞则最终执行 LockSupport(Unsafe) 中的 native API 来实现线程阻塞。释放锁的流程:· 首先获取当前节点(实际上传入的是 head 节点)的状态,如果 head 节点的下一个节点是 null,或者下一个节点的状态为 CANCEL(不用再唤醒了),则直接从等待队列的尾部开始遍历,一直遍历到最前面的 waitStatus 小于等于 0 的节点(大于0的状态是CANCEL状态)。· 如果最终遍历到的节点不为 null,再调用 LockSupport.unpark 方法,调用底层方法唤醒线程。
0
0
0
浏览量2009
IT大鲨鱼

深入理解Kotlin协程

Kotlin协程协程由程序自己创建和调度,不需要操作系统调度,所以协程比线程更加轻量。相比于线程的切换,协程切换开销更小,速度更快。我们知道线程是CPU调度的基本单位,而协程不能独立存在,必须依赖于线程。在Kotlin中,Dispatcher(内部是线程池或者Android主线程)是调度器,可以调度协程运行在一个或多个线程之中。一个线程中可以有多个协程,同一个协程可以运行在一个线程的不同时刻;多个协程可以运行在一个或多个线程的不同时刻。进程、线程、协程三者的关系: · 进程:线程 = 1:N· 线程:协程 = 1:N协程优势:· 更安全的代码:kotlin中提供了许多语言功能,避免Java中最常见的null空指针等异常· 语法简洁、富有表现力:相比于Java,kotlin可以使用更少的代码实现更多的功能。· 可互操作:与Java语言无缝互通。即可以在 kotlin 代码中调用Java代码,同时也可以在Java代码中调用kotlin代码。kotlin代码本质上也是通过kotlin编译器编译后生成VM能识别的字节码。· 结构化并发:使用看似阻塞式的写法来实现异步功能。即可以同步的方式去写异步代码,相比于回调方式大幅简化了后台任务管理,例如网络请求、数据库访问等任务的管理。Suspend 非阻塞式挂起函数协程在常规函数的基础上添加了两项操作,用于处理长时间运行的任务。在 invoke(或call)和return之外,协程添加了 suspend 和 resume:· suspend 用于暂停执行当前协程,并保存所有局部变量。· resume 用于让已挂起的协程从挂起处继续执行。suspend 函数不能在普通函数中调用,否则会报Suspend function 'xxx' should be called only from a coroutine or another suspend function 的提示;如需调用 suspend 函数,只能从其他 suspend 函数进行调用,或通过使用协程构建器(例如 launch)来启动新的协程。suspend 函数相比于普通函数内部多了一个 Continuation 续体实例(Kotlin转成Java代码之后即可看到),suspend函数中可以调用普通函数,但普通函数却不能调用suspend函数。 suspend fun fetchDocs() { // Dispatchers.Main val result = get("https://developer.android.com") // Dispatchers.IO for `get` show(result) // Dispatchers.Main}suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }suspend 修饰的函数为非阻塞式挂起函数,何为非阻塞式挂起呢?不同于Java 中的Thread.sleep() 会阻塞当前线程,suspend 修饰的函数当遇到启动子线程操作时,会在切线程时将协程挂起并记录当前挂起点,接着主线程暂停了当前协程,但可以去执行协程外逻辑而不会被阻塞;此时协程中启动的子线程也可以继续执行(相当于兵分两路,互不影响),当子线程中的逻辑执行完毕后,会自动从协程的挂起点恢复,这样就可以继续在主线程往下执行了。整体流程如下:执行suspend函数 -> 启动子线程 -> 函数挂起并记录挂起点 -> 协程暂停 -> 子线程执行完毕 -> 协程从挂起点恢复如在上面的示例中,get() 在主线程中调用,内部在它启动网络请求之前挂起协程。虽然协程被挂起,但主线程并没有被阻塞,此时如果主线程中收到其他消息事件依然可以去处理(如点击事件等)。当网络请求完成时,get 会恢复已挂起的协程,继续执行show(result) 方法。这里注意一下,挂起函数并不一定会导致协程挂起,只有在发生异步调用时才会挂起。Kotlin 使用 堆栈帧 管理要运行哪个函数以及所有局部变量。挂起协程时,系统会复制并保存当前的堆栈帧以供稍后使用。恢复时,会将堆栈帧从其保存位置复制回来,然后函数再次开始运行。即使代码可能看起来像普通的顺序阻塞请求,协程也能确保网络请求避免阻塞主线程。CPS变换 + Continuation续体 + 状态机CPS变换 (Continuation-Passing-Style Transformation) 是一种编程风格, 用来将内部要执行的逻辑封装到一个闭包里面, 然后再返回给调用者。上一节的例子中,协程就是通过传递 Continuation 来控制异步调用流程的:将函数挂起之后执行的逻辑包装起成一个Continuation, 里面包含了挂起点信息,这样当协程恢复时就可以在挂起点继续执行。Continuation意为续体,只存在于挂起函数中。Kotlin 中当一个普通函数加上suspend 关键字之后,就成为了挂起函数,如:private suspend fun suspendFuc() {}我们对此函数进行反编译,查看对应的Java代码为:private final Object suspendFuc(Continuation $completion) { return Unit.INSTANCE;}可以看到Java代码中系统帮我们多生成了一个Continuation 类型的参数。Continuation 是一个接口类型,表示在返回T类型值的挂起点之后的延续,其中类型T代表的是原来函数的返回值类型。@SinceKotlin("1.3")public interface Continuation<in T> { /** * The context of the coroutine that corresponds to this continuation. */ public val context: CoroutineContext /** * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the * return value of the last suspension point. */ public fun resumeWith(result: Result<T>)} resumeWith 恢复执行相应协程,并传递一个结果作为最后一个挂起点的返回值。· 状态机上述函数中加入一个delay 函数,delay 函数本身也是一个挂起函数: private suspend fun suspendFuc(): String { delay(1000) return "value" }经过Tools -> Kotlin -> Show Kotlin Bytecode 反编译查看: private final Object suspendFuc(Continuation var1) { Object $continuation; label20: { if (var1 instanceof <undefinedtype>) { $continuation = (<undefinedtype>)var1; if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) { ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE; break label20; } } //将要执行的Continuation逻辑传入ContinuationImpl中 $continuation = new ContinuationImpl(var1) { // $FF: synthetic field Object result; int label; //invokeSuspend()会在恢复协程挂起点时调用 @Nullable public final Object invokeSuspend(@NotNull Object $result) { this.result = $result; this.label |= Integer.MIN_VALUE; return CoroutineBaseFragment.this.suspendFuc(this); } }; } Object $result = ((<undefinedtype>)$continuation).result; //返回此状态意味着函数要被挂起 Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); // 状态机逻辑,通过label进行分块执行 switch(((<undefinedtype>)$continuation).label) { case 0: ResultKt.throwOnFailure($result); ((<undefinedtype>)$continuation).label = 1; if (DelayKt.delay(1000L, (Continuation)$continuation) == var4) { return var4; } break; case 1: ResultKt.throwOnFailure($result); break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } return "value"; }suspend挂起函数经过Kotlin编译器编译之后会进行CPS变换,并且函数里的逻辑会进行分块执行:分块逻辑数量 = 挂起函数数量 + 1,上述函数逻辑中,通过switch(label){} 状态机将逻辑分块执行,首先label = 0,此时会先将label置为1,接着调用了delay挂起函数,返回Intrinsics.COROUTINE_SUSPENDED意味着函数要被挂起;当函数从协程挂起点恢复执行时,会调用$continuation#invokeSuspend(Object $result) 方法恢复执行,$result是上一次函数执行的结果,以参数形式带入之后,就可以从挂起点继续执行了。此时label变为1,ResultKt.throwOnFailure($result)中通过 if (value is Result.Failure) throw value.exception判断上面的分块结果是否有异常,如果有直接抛异常;否则直接break,执行到最后的return "value" 逻辑,这样整个方法的流程就运行完毕了。总结:suspend挂起函数的执行流程就是通过CPS变换 + Continuation + 状态机来运转的。CoroutineContext我们知道 Android 中的 Context(子类有Application、Activity、Service)可以获取应用资源,可以启动Activity、Service等。CoroutineContext意为协程上下文,其作用可以类比 Context,通过它可以控制协程在哪个线程中执行、捕获协程异常、设置协程名称等。public interface CoroutineContext { //从该上下文中返回具有给定[key]的元素 或 null public operator fun <E : Element> get(key: Key<E>): E? //从initial开始累加上下文中的条目,并从左到右对当前累加器值和上下文中的每个元素应用operation public fun <R> fold(initial: R, operation: (R, Element) -> R): R //返回一个上下文,其中包含来自此上下文的元素和来自其他上下文context的元素。具有相同键的元素会被覆盖。 public operator fun plus(context: CoroutineContext): CoroutineContext = if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation context.fold(this) { acc, element -> val removed = acc.minusKey(element.key) if (removed === EmptyCoroutineContext) element else { // make sure interceptor is always last in the context (and thus is fast to get when present) val interceptor = removed[ContinuationInterceptor] if (interceptor == null) CombinedContext(removed, element) else { val left = removed.minusKey(ContinuationInterceptor) if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else CombinedContext(CombinedContext(left, element), interceptor) } } } //删除带有指定[key]的元素。 public fun minusKey(key: Key<*>): CoroutineContext /** * Key for the elements of [CoroutineContext]. [E] is a type of element with this key. */ public interface Key<E : Element> //CoroutineContext的一个元素。协程上下文的一个元素本身就是一个单例上下文。 public interface Element : CoroutineContext { /** * A key of this coroutine context element. */ public val key: Key<*> public override operator fun <E : Element> get(key: Key<E>): E? = @Suppress("UNCHECKED_CAST") if (this.key == key) this as E else null public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = operation(initial, this) public override fun minusKey(key: Key<*>): CoroutineContext = if (this.key == key) EmptyCoroutineContext else this }}协程总是会运行在以 CoroutineContext 类型的上下文中,其中plus操作符被重写,多个CoroutineContext相加时,会生成一个CombinedContext( 内部可以理解成一个Element都不一样的链表);同时 CombinedContext拥有 map索引能力,集合中的每个元素都有一个唯一的 Key。继承关系 Element 定义在CoroutineContext内部,是它的内部接口。CoroutineContext 使用以下元素集定义协程的行为:· Job:控制协程的生命周期。· CoroutineDispatcher:将工作分派到适当的线程。· CoroutineName:协程的名称,可用于调试。· CoroutineExceptionHandler:处理未捕获的异常。下面就来详细看看几种元素的使用。CoroutineContext几种具体实现1、Job & SupervisorJobJob是协程的句柄。使用launch或async创建的协程都会返回一个Job实例,该实例是对应协程的唯一标识并可以管理协程的生命周期。如:val job1 = GlobalScope.launch { }val job2 = MainScope().launch { }override fun onDestroy() { super.onDestroy() job1.cancel() job2.cancel()}注:上面的协程启动方式并不推荐在项目中直接使用,因为生命周期比较长,如果没有主动关闭可能就会产生内存泄漏。推荐在ViewModel中使用viewModelScope,LifecycleOwner中使用lifecycleScope,可以在各自的生命周期中自动关闭协程。 val job = GlobalScope.launch(context = Job() + Dispatchers.Main, start = CoroutineStart.LAZY) { // 逻辑部分 } job常用的API: - job.start() // 对应 start = CoroutineStart.LAZY - job.cancelAndJoin() //cancel() + join() - job.cancel() // 取消 - job.isActive // 协程是否存活 - job.isCancelled // 协程是否被取消 - job.isCompleted //协程是否已经执行完毕Job 有以下状态:StateisActiveisCompletedisCancelledNew (optional initial state)falsefalsefalseActive (default initial state)truefalsefalseCompleting (transient state)truefalsefalseCancelling (transient state)falsefalsetrueCancelled (final state)falsetruetrueCompleted (final state)falsetruefalse状态流转: wait children +-----+ start +--------+ complete +-------------+ finish +-----------+ | New | -----> | Active | ---------> | Completing | -------> | Completed | +-----+ +--------+ +-------------+ +-----------+ | cancel / fail | | +----------------+ | | V V +------------+ finish +-----------+ | Cancelling | --------------------------------> | Cancelled | +------------+ +-----------+协程通过状态机方式实现自动回调。SupervisorJob的使用val exceptionHandler = CoroutineExceptionHandler { context, throwable -> log("throwable:$throwable") }GlobalScope.launch(exceptionHandler) { //子Job1 launch(SupervisorJob()) { delay(200) throw NullPointerException() } //子Job2 launch { delay(300) log("child2 execute successfully") } //子Job3 launch { delay(400) log("child3 execute successfully") } log("parent execute successfully")}注意子Job1传入的是 SupervisorJob,当发生异常时,兄弟协程(job2/job3)不会被取消;如果是默认配置,那么兄弟协程也会被取消,上述代码执行结果:2022-08-30 23:31:30.142 E/TTT: parent execute successfully2022-08-30 23:31:30.346 E/TTT: throwable:java.lang.NullPointerException2022-08-30 23:31:30.446 E/TTT: child2 execute successfully2022-08-30 23:31:30.545 E/TTT: child3 execute successfully如果将job1中的更换为默认时,执行结果为:2022-08-30 23:31:30.142 E/TTT: parent execute successfully2022-08-30 23:31:30.346 E/TTT: throwable:java.lang.NullPointerException可以看到job1中使用默认设置时,当job1发生了异常,兄弟协程也会被取消。注意这里job2/job3可以被取消是因为delay()中有对取消状态的判断,通知job2/job3取消可以类比Thread.interrupt(),interrupt只是通知线程要中断并设置一个中断状态,最终要不要中断还是线程自己说了算。所以如果改成以下代码,即使job1使用默认配置,job3也不会被取消://子Job3 launch(Dispatchers.IO) { Thread.sleep(400) log("child3 execute successfully") }job3中没有了对协程状态的判断,所以即使job3被通知要取消协程了,依然会继续执行直到结束。SuperviorJob源码浅析public fun Job(parent: Job? = null): CompletableJob = JobImpl(parent)public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) { override fun childCancelled(cause: Throwable): Boolean = false}当子协程 job1/job2/job3 启动时,会和协程本身的 Job 形成父子关系。而当子协程抛异常时,父Job 的 childCancelled() 方法会被执行,且默认返回的是 true,表示取消 父Job 及其所有 子Job;如果 子Job中使用的是 SuperviorJob,childCancelled() 返回的是 false,表示 父Job及其未发生异常的 子Job都不会受影响。2、CoroutineDispatcherpublic actual object Dispatchers { @JvmStatic public actual val Default: CoroutineDispatcher = createDefaultDispatcher() @JvmStatic public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher @JvmStatic public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined @JvmStatic public val IO: CoroutineDispatcher = DefaultScheduler.IO}将工作分派到适当的线程。· Dispatchers.Main:运行在UI线程中。Dispatchers.Main.immediate: 如果在UI线程加载,不会做特殊处理;如果是在子线程,会通过Handler转发到主线程· Dispatchers.IO: 执行IO密集型任务,最多提交max(64, CPU核心数)个任务执行。· Dispatchers.DEFAULT :执行CPU密集型任务, CoroutineScheduler最多有corePoolSize个线程被创建,corePoolSize的取值为max(2, CPU核心数),即它会尽量的等于CPU核心数· Dispatchers.Unconfined:不给协程指定运行的线程,由启动协程的线程决定;但当被挂起后, 会由恢复协程的线程继续执行。内部通过ThreadLocal保存执行协程时对应的线程,用于恢复协程时在取出对应线程并在其继续执行协程。withContextwithContext() 可以在不引入回调的情况下控制任何代码行的线程池,因此可以将其应用于非常小的函数,例如从数据库中读取数据或执行网络请求。一种不错的做法是使用 withContext() 来确保每个函数都是主线程安全的,这意味着,您可以从主线程调用每个函数。这样,调用方就从不需要考虑应该使用哪个线程来执行函数了。3、CoroutineName协程的名称,可用于调试。4、CoroutineExceptionHandlerpublic interface CoroutineExceptionHandler : CoroutineContext.Element { public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler> //当有未处理的异常发生时,该方法就会执行 public fun handleException(context: CoroutineContext, exception: Throwable)}CoroutineExceptionHandler当有未捕获的异常时就会触发执行。如果不在顶级协程中设置,那么当有异常发生时会导致程序的crash,可以通过自定义CoroutineExceptionHandler来拦截异常,如下:val exceptionHandler = CoroutineExceptionHandler { context, throwable -> log("throwable:$throwable") }lifecycleScope.launch(exceptionHandler) { // do something } 自定义CoroutineExceptionHandler调用的是如下方法:public inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler = object : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler { override fun handleException(context: CoroutineContext, exception: Throwable) = handler.invoke(context, exception) }所以当发生异常时,会在handleException()中执行我们传入的handler(CoroutineContext, Throwable)方法。CoroutineExceptionHandler使用示例: val exceptionHandler = CoroutineExceptionHandler { context, throwable -> log("parent throwable:$throwable") } lifecycleScope.launch(exceptionHandler) { log("parent execute start") val childExHandler = CoroutineExceptionHandler { context, throwable -> log("child throwable:$throwable") } //子协程中如果使用SupervisorJob()、Job(),则异常不会往上传播;否则异常会在顶层协程中处理 val childJob = launch(childExHandler) { delay(1000) log("child execute") throw IllegalArgumentException("error occur") } childJob.join() log("parent execute end") }执行结果:2022-09-04 00:38:51.792 15415-15415/ E/TTT: parent execute start2022-09-04 00:38:52.796 15415-15415/ E/TTT: child execute2022-09-04 00:38:52.805 15415-15415/ E/TTT: parent throwable:java.lang.IllegalArgumentException: error occur可见虽然在子协程中也自定义了CoroutineExceptionHandler,但是最终子协程中的异常还是在顶层的父协程种处理的,如果就想在子协程中处理异常呢?可以在子协程中加上SupervisorJob()、Job(),则异常会在子协程中处理而不会往上传播: //其他内容不变 val childJob = launch(childExHandler + SupervisorJob()) { //其他内容不变 }执行结果:2022-09-04 00:42:48.465 15755-15755/ E/TTT: parent execute start2022-09-04 00:42:49.468 15755-15755/ E/TTT: child execute2022-09-04 00:42:49.473 15755-15755/ E/TTT: child throwable:java.lang.IllegalArgumentException: error occur2022-09-04 00:42:49.474 15755-15755/ E/TTT: parent execute end可见异常最终是在子协程中处理的,且虽然子协程中发生了异常,父协程依然能执行完毕。注:如果启动协程的是async,则CoroutineExceptionHandler中的handler方法并不会马上执行,必须调用deffered.await()时才会执行。CoroutineScope 协程作用域CoroutineScope 会跟踪它使用 launch 或 async 创建的所有协程。CoroutineScope 本身并不运行协程,但是通过CoroutineScope可以保证对协程进行统一管理,避免发生内存泄漏等,常见的CoroutineScope:· GlobalScope: 全局协程作用域,如果不主动通过job.cancel()关闭,其生命周期与Application一致。· MainScope: MainScope的内部:public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main),可以看到MainScope其实是通过SupervisorJob() + Dispatchers.Main自定义的协程作用域,其内部运行在UI线程且内部异常不会往上传播。· runBlocking: 顶层函数,和 GlobalScope 不一样,它会阻塞当前线程直到其内部所有子协程执行结束;· ktx扩展库中的自定义GlobalScope:典型应用就是在LifecycleOwner(androidX中的FragmentActivity、Fragment都实现了该接口)使用的lifecycleScope,以及ViewModel中使用的ViewModelScope,其内部可以在合适的时机自动关闭协程,从而避免内存泄露的发生。
0
0
0
浏览量1476
IT大鲨鱼

JUC系列学习(三):ReentrantLock的使用、源码解析及与Synchronized的异同

ReentrantLock介绍及使用ReentrantLock同Synchronized一样可以实现线程锁的功能,同样具有可重入性,除此之外还可以实现公平锁&非公平锁,其底层是基于AQS框架实现的。主要方法:· lock(): 加锁· lockInterruptibly(): 加锁,支持中断· tryLock():· tryLock(long timeout, TimeUnit unit):· unlock(): 解锁· newCondition(): 初始化Condition条件,如在A线程中使用condition.await()中断执行并释放锁,B线程中可以通过condition.signal/condition.signalAll来通知A线程恢复执行。· getHoldCount(): 返回当前state的值,默认是0· isHeldByCurrentThread(): 当前线程是否是锁的持有者· isLocked(): 是否有锁· isFair(): 是否是公平锁· getOwner(): 获取持有当前锁的线程,如果没有返回null· hasQueuedThreads():· getQueueLength():· getQueuedThreads():· hasWaiters(): 在Condition Queue中是否有Node,只能在互斥锁中调用,否则会抛异常。· getWaitQueueLength():· getWaitingThreads(Condition condition):通过一个例子来看下ReentrantLock的基本用法:· lock加锁 unlock解锁://初始化 ReentrantLock ReentrantLock reentrantLock = new ReentrantLock(); //lock加锁 unlock解锁 MyRunnable runnable = new MyRunnable(reentrantLock); Thread threadA = new Thread(runnable,"threadA"); Thread threadB = new Thread(runnable,"threadB"); threadA.start(); threadB.start(); static class MyRunnable implements Runnable { private ReentrantLock reentrantLock; MyRunnable(ReentrantLock reentrantLock) { this.reentrantLock = reentrantLock; } @Override public void run() { try { reentrantLock.lock(); for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "," + i); } } catch (Exception e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } } } 输出结果:threadA,0 threadA,1 threadA,2 threadA,3 threadA,4 threadB,0 threadB,1 threadB,2 threadB,3 threadB,4通过结果可以说明ReentrantLock可以保证多线程访问共享资源的顺序性。· condition:wait、signal/siagnalAll 通知//condition:await、signal/signalAll通知 Condition condition = reentrantLock.newCondition(); ThreadC threadC = new ThreadC(reentrantLock, condition); threadC.start(); Thread.sleep(2000); ThreadD threadD = new ThreadD(reentrantLock, condition); threadD.start(); static class ThreadC extends Thread { private ReentrantLock reentrantLock; private Condition condition; ThreadC(ReentrantLock reentrantLock, Condition condition) { this.reentrantLock = reentrantLock; this.condition = condition; setName("ThreadC"); } @Override public void run() { System.out.println(Thread.currentThread().getName() + ": 开始运行"); try { reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + ": 通过condition.await中断运行并放弃锁"); condition.await(); System.out.println(Thread.currentThread().getName() + ": 重新获取锁,恢复执行"); } catch (Exception e) { e.printStackTrace(); } finally { reentrantLock.unlock(); System.out.println(Thread.currentThread().getName() + ": 释放锁"); } } } static class ThreadD extends Thread { private ReentrantLock reentrantLock; private Condition condition; ThreadD(ReentrantLock reentrantLock, Condition condition) { this.reentrantLock = reentrantLock; this.condition = condition; setName("ThreadD"); } @Override public void run() { System.out.println(Thread.currentThread().getName() + ": 开始运行"); try { reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + ": 通过condition.signal去唤醒ThreadC"); condition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { reentrantLock.unlock(); System.out.println(Thread.currentThread().getName() + ": 释放锁"); } } } 执行结果:ThreadC: 开始运行 ThreadC: 通过condition.await中断运行并放弃锁 ThreadD: 开始运行 ThreadD: 通过condition.signal去唤醒 ThreadD: 释放锁 ThreadC: 重新获取锁,恢复执行 ThreadC: 释放锁ReentrantLock源码分析public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); } Lock 提供了一种无条件的、可轮询的、定时的、可中断的锁获取操作。所有加锁和解锁方法都是显式的。ReentrantLock实现了Lock接口,并提供了与Synchronized相同的互斥性与可见性。同时当锁不可用时ReentrantLock提供了更高的灵活性。公平锁及非公平锁ReentrantLock中有一个静态内部类Sync并且继承了AbstractQueuedSynchronizer(AQS),所以ReentrantLock的底层是基于AQS同步器框架实现的,默认使用的是非公平锁,如果需要使用公平锁,初始化时需要传入参数true,即ReentrantLock reentrantLock = new ReentrantLock(true), 对应的源码://默认实现是非公平锁 public ReentrantLock() { sync = new NonfairSync(); } //传入true 初始化的是FairSync公平锁,否则是NonfairSync非公平锁 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } 那么公平锁FairSync及非公平锁NonfairSync内部又是怎么实现的呢?继续往下看://抽象类Sync,继承自AQS,默认实现的是非公平锁方法 abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; //抽象方法 在子类中实现 abstract void lock(); //非公平锁 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //state为0 表示还没有加锁 if (c == 0) { //非公平锁直接通过CAS再次获取锁 如果成功 直接设置当前线程为独占锁 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { //如果当前state不为0 说明已经有线程持有锁 如果持有锁的是当前线程,那么直接对state进行加1 所以ReentrantLock也有可重入性 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } //释放锁 protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } //是否是互斥锁 protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } final ConditionObject newCondition() { return new ConditionObject(); } final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } final boolean isLocked() { return getState() != 0; } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state } } //1、非公平锁实现,继承自Sync static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; final void lock() { //非公平锁直接通过CAS尝试获取锁 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else //调用AQS中的acquire()方法,acquire()方法又会调用到tryAcquire()方法 acquire(1); } protected final boolean tryAcquire(int acquires) { //调用抽象父类Sync中的方法,见上面Sync类中nonfairTryAcquire方法注释 return nonfairTryAcquire(acquires); } } //2、公平锁实现,继承自Sync static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { //不同于非公平锁,这里并不会通过CAS去尝试获取锁 而是直接调用FairSync类中重写的tryAcquire方法 acquire(1); } protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //不同于非公平锁,除非没有等待节点Node或者在队列中的第一个,否则不会尝试获取锁而是加入到等待队列中(在父类AQS中实现) if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } } 1、在调用lock()方法时,公平锁不会通过CAS直接尝试获取锁,而非公平锁会直接通过CAS尝试获取锁,如果成功,那么会直接获取锁返回(比等待队列中的线程优先获取到锁,从而导致非公平性)。2、当非公平锁获取锁失败后,和公平锁一样都会进入tryAcquire方法中,如果此时之前的锁释放了(state==0),非公平锁会再次执行一次CAS尝试获取锁,而公平锁会判断队列中是否有线程在等待,没有的话才会尝试去获取锁,否则直接加入到等待队列中。如果非公平锁/公平锁尝试获取锁失败,那么都会进入阻塞队列中等待唤醒。此外,如果当前state不为0(已经有线程持有锁),判断持有锁的是不是当前线程,如果是,那么直接对state进行加1 所以ReentrantLock具有可重入性。性能对比:非公平锁有更好的性能,吞吐量比较大,同时,非公平锁可能会导致在阻塞队列中的线程一直处于饥饿状态。Synchronized浅析 & ReentrantLock对比Synchronized浅析:Synchronized是基于Jvm层面的互斥锁,底层通过monitor指令来实现加锁和解锁,可以把monitor当成一把锁,锁里面包含了计数器和线程指针,当计数器为0时,表示锁没有被任何线程持有,当计数器大于0时表示锁已经被某个线程持有,线程指针指的即使持有当前锁的线程,当同一线程多次申请该锁时,计数器会进行叠加(可重入性),这里跟AQS中的state概念是类似的。 PS:Synchronized在1.5之前属于重量级锁,线程的阻塞和唤醒需要CPU从用户态转为内核态,频繁的阻塞和唤醒对CPU来说是一件耗性能的工作,从而影响到并发性能。JDK1.6后对其做了一系列的优化,主要包括:自旋锁、偏向锁、轻量级锁、重量级锁、锁消除、锁粗化等,通过一系列锁优化技术在特定场景下可以大大提高并发性能。自旋锁:自旋锁是指当一个线程获取锁时,如果已经被其他线程获取锁,当前线程将循环等待(执行无实际意义的循环),不断判断锁是否能够成功获取,直到成功才会退出循环。优点:旋锁可以减少不必要用户态与内核态之间的来回切换,提高了性能。线程状态一直处于用户态。缺点:· 自旋锁是不公平的,若多个线程自旋,无法满足等待最长时间的线程最先获取锁,即存在线程饥饿的问题· 如果某个线程持有锁的时间过长,会导致其他自旋等待的线程循环等待,过度消耗CPU做无用功。更多锁优化相关知识点,可以参见:· 深入理解自旋锁· 锁优化技术· 深入浅出Java锁优化(偏向锁,轻量级锁,锁消除,锁粗化,自旋锁)Synchronized、ReentrantLock对比:对比SynchronizedReentrantLock实现原理Jvm层面的内置互斥锁,底层通过monitorenter和monitorexit两个字节码指令来实现加锁解锁操作的应用层互斥锁是否需要手动释放锁否(自动释放)是(需要手动释放锁)其他修饰普通方法、成员变量为对象锁,不同对象的锁互相不影响;修饰static静态方法为类锁。不可中断,不支持定时Lock锁可以中断,支持定时通过对比我们看到,ReentrantLock的功能相比于Synchronized来说,功能更强大,如:可以实现公平锁、可以中断、支持定时等功能,但是是否能认为ReentrantLock可以完全替代Synchronized呢?答案是否认的,可以看到ReentrantLock在使用时需要手动释放锁,这里就好像一颗定时炸弹,一旦开发者忘记了释放锁,就会导致后续的所有线程都不能再获取锁。ReentrantLock在某种程度上可以认为是Synchronized的一种补充,两者各有优缺点。
0
0
0
浏览量708
IT大鲨鱼

Android Jetpack系列之DataStore

一 、DataStore介绍Jetpack DataStore 是一种改进的新数据存储解决方案,允许使用协议缓冲区存储键值对或类型化对象。DataStore 以异步、一致的事务方式存储数据,克服了 SharedPreferences(以下统称为SP)的一些缺点。DataStore基于Kotlin协程和Flow实现,并且可以对SP数据进行迁移,旨在取代SP。DataStore提供了两种不同的实现:Preferences DataStore与Proto DataStore,其中Preferences DataStore用于存储键值对;Proto DataStore用于存储类型化对象,后面会分别给出对应的使用例子。二、SharedPreferences缺点DataStore出现之前,我们用的最多的存储方式毫无疑问是SP,其使用方式简单、易用,广受好评。然而google对SP的定义为轻量级存储,如果存储的数据少,使用起来没有任何问题,当需要存储数据比较多时,SP可能会导致以下问题:· SP第一次加载数据时需要全量加载,当数据量大时可能会阻塞UI线程造成卡顿· SP读写文件不是类型安全的,且没有发出错误信号的机制,缺少事务性API· commit() / apply()操作可能会造成ANR问题: commit()是同步提交,会在UI主线程中直接执行IO操作,当写入操作耗时比较长时就会导致UI线程被阻塞,进而产生ANR;apply()虽然是异步提交,但异步写入磁盘时,如果执行了Activity / Service中的onStop()方法,那么一样会同步等待SP写入完毕,等待时间过长时也会引起ANR问题。针对apply()我们展开来看一下:SharedPreferencesImpl#EditorImpl.java中最终执行了apply()函数: public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); public void apply() { final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { public void run() { try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } } }; //8.0之前 QueuedWork.add(awaitCommit); //8.0之后 QueuedWork.addFinisher(awaitCommit); //异步执行磁盘写入操作 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); //......其他......}构造一个名为awaitCommit的Runnable任务并将其加入到QueuedWork中,该任务内部直接调用了CountDownLatch.await()方法,即直接在UI线程执行等待操作,那么需要看QueuedWork中何时执行这个任务。QueuedWork类在Android8.0以上和8.0以下的版本实现方式有区别:8.0之前QueuedWork.java:public class QueuedWork { private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers = new ConcurrentLinkedQueue<Runnable>(); public static void add(Runnable finisher) { sPendingWorkFinishers.add(finisher); } public static void waitToFinish() { Runnable toFinish; // 从队列中取出任务:如果任务为空,则跳出循环,UI线程可以继续往下执行; //反之任务不为空,取出任务并执行,实际执行的CountDownLatch.await(),即UI线程会阻塞等待 while ((toFinish = sPendingWorkFinishers.poll()) != null) { toFinish.run(); } } //......其他......}8.0之后QueuedWork.java:public class QueuedWork { private static final LinkedList<Runnable> sFinishers = new LinkedList<>(); public static void waitToFinish() { Handler handler = getHandler(); StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { //8.0之后优化,会主动尝试执行写磁盘任务 processPendingWork(); } finally { StrictMode.setThreadPolicy(oldPolicy); } try { while (true) { Runnable finisher; synchronized (sLock) { //从队列中取出任务 finisher = sFinishers.poll(); } //如果任务为空,则跳出循环,UI线程可以继续往下执行 if (finisher == null) { break; } //任务不为空,执行CountDownLatch.await(),即UI线程会阻塞等待 finisher.run(); } } finally { sCanDelay = true; } } }可以看到不管8.0之前还是之后,waitToFinish()都会尝试从Runnable任务队列中取任务,如果有的话直接取出并执行,直接看哪里调用了waitToFinish():ActivityThread.javaprivate void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) { //......其他...... QueuedWork.waitToFinish();}private void handleStopService(IBinder token) { //......其他...... QueuedWork.waitToFinish();}省略了一些代码细节,可以看到在ActivityThread中handleStopActivity、handleStopService方法中都会调用waitToFinish()方法,即在Activity的onStop()中、Service的onStop()中都会先同步等待写入任务完成才会继续执行。所以apply()虽然是异步写入磁盘,但是如果此时执行到Activity/Service的onStop(),依然可能会阻塞UI线程导致ANR。画外音:SP使用过程中导致的ANR问题,可以通过一些Hook手段进行优化,如字节发布的 今日头条 ANR 优化实践系列 - 告别 SharedPreference 等待。我司项目里使用的SP也是按此优化,优化后效果还是比较显著的,所以目前项目也还没有对SP进行迁移(如迁移到MMKV或DataStore),但并不影响我们学习新的存储姿势。三、DataStore使用DataStore优势:· DataStore基于事务方式处理数据更新。· DataStore基于Kotlin Flow存取数据,默认在Dispatchers.IO里异步操作,避免阻塞UI线程,且在读取数据时能对发生的Exception进行处理。· 不提供apply()、commit()存留数据的方法。· 支持SP一次性自动迁移至DataStore中。3.1 Preferences DataStore添加依赖项implementation 'androidx.datastore:datastore-preferences:1.0.0'构建Preferences DataStoreval Context.bookDataStorePf: DataStore<Preferences> by preferencesDataStore( // 文件名称 name = "pf_datastore")通过上面的代码,我们就成功创建了Preferences DataStore,其中preferencesDataStore()是一个顶层函数,包含以下几个参数:· name:创建Preferences DataStore的文件名称。· corruptionHandler:如果DataStore在试图读取数据时,数据无法反序列化,会抛出androidx.datastore.core.CorruptionException,此时会执行corruptionHandler。· produceMigrations:SP产生迁移到Preferences DataStore。ApplicationContext作为参数传递给这些回调,迁移在对数据进行任何访问之前运行。· scope:协程作用域,默认IO操作在Dispatchers.IO线程中执行。上述代码执行后,会在/data/data/项目包名/files/下创建名为pf_datastore的文件如下: 可以看到后缀名并不是xml,而是.preferences_pb。这里需要注意一点:不能将上面的初始化代码写到Activity里面去,否则重复进入Actvity并使用Preferences DataStore时,会尝试去创建一个同名的.preferences_pb文件,因为之前已经创建过一次,当检测到尝试创建同名文件时,会直接抛异常:java.lang.IllegalStateException: There are multiple DataStores active for the same file:xxx. You should either maintain your DataStore as a singleton or confirm that there is no two DataStore's active on the same file (by confirming that the scope is cancelled).报错类在androidx.datastore:datastore-core:1.0.0的androidx/datastore/core/SingleProcessDataStore下:internal val activeFiles = mutableSetOf<String>()file.absolutePath.let { synchronized(activeFilesLock) { check(!activeFiles.contains(it)) { "There are multiple DataStores active for the same file: $file. You should " + "either maintain your DataStore as a singleton or confirm that there is " + "no two DataStore's active on the same file (by confirming that the scope" + " is cancelled)." } activeFiles.add(it) } }其中file是通过File(applicationContext.filesDir, "datastore/$fileName")生成的文件,即Preferences DataStore最终要在磁盘中操作的文件地址,activeFiles是在内存中保存生成的文件路径的,如果判断到activeFiles里已经有该文件,直接抛异常,即不允许重复创建。存数据首先声明一个实体类BookModel:data class BookModel( var name: String = "", var price: Float = 0f, var type: Type = Type.ENGLISH)enum class Type { MATH, CHINESE, ENGLISH}BookRepo.kt中执行存储操作:const val KEY_BOOK_NAME = "key_book_name"const val KEY_BOOK_PRICE = "key_book_price"const val KEY_BOOK_TYPE = "key_book_type"//Preferences.Key<T>类型object PreferenceKeys { val P_KEY_BOOK_NAME = stringPreferencesKey(KEY_BOOK_NAME) val P_KEY_BOOK_PRICE = floatPreferencesKey(KEY_BOOK_PRICE) val P_KEY_BOOK_TYPE = stringPreferencesKey(KEY_BOOK_TYPE)}/** * Preferences DataStore存数据 */suspend fun saveBookPf(book: BookModel) { context.bookDataStorePf.edit { preferences -> preferences[PreferenceKeys.P_KEY_BOOK_NAME] = book.name preferences[PreferenceKeys.P_KEY_BOOK_PRICE] = book.price preferences[PreferenceKeys.P_KEY_BOOK_TYPE] = book.type.name }}Activity中:lifecycleScope.launch { val book = BookModel( name = "Hello Preferences DataStore", price = (1..10).random().toFloat(), //这里价格每次点击都会变化,为了展示UI层能随时监听数据变化 type = Type.MATH ) mBookRepo.savePfData(book)}通过 bookDataStorePf.edit(transform: suspend (MutablePreferences) -> Unit) 挂起函数进行存储,该函数接受 transform 块,能够以事务方式更新DataStore中的状态。取数据/** * Preferences DataStore取数据 取数据时可以对Flow数据进行一系列处理 */val bookPfFlow: Flow<BookModel> = context.bookDataStorePf.data.catch { exception -> // dataStore.data throws an IOException when an error is encountered when reading data if (exception is IOException) { emit(emptyPreferences()) } else { throw exception }}.map { preferences -> //对应的Key是 Preferences.Key<T> val bookName = preferences[PreferenceKeys.P_KEY_BOOK_NAME] ?: "" val bookPrice = preferences[PreferenceKeys.P_KEY_BOOK_PRICE] ?: 0f val bookType = Type.valueOf(preferences[PreferenceKeys.P_KEY_BOOK_TYPE] ?: Type.MATH.name) return@map BookModel(bookName, bookPrice, bookType)}Activity中:lifecycleScope.launch { mBookViewModel.bookPfFlow.collect { mTvContentPf.text = it.toString() }}通过bookDataStorePf.data 返回的是Flow<BookModel>,那么后续就可以通过Flow对数据进行一系列处理。从文件读取数据时,如果出现错误,系统会抛出IOExceptions。可以在 map() 之前使用 catch() 运算符,并且在抛出的异常是 IOException 时发出 emptyPreferences()。如果出现其他类型的异常,重新抛出该异常。注意:Preferences DataStore存取数据时的Key是Preferences.Key< T>类型,且其中的T只能存 Int、Long、Float、Double、Boolean、String、Set< String>类型,此限制在androidx/datastore/preferences/core/PreferencesSerializer类参与序列化的getValueProto()方法中: private fun getValueProto(value: Any): Value { return when (value) { is Boolean -> Value.newBuilder().setBoolean(value).build() is Float -> Value.newBuilder().setFloat(value).build() is Double -> Value.newBuilder().setDouble(value).build() is Int -> Value.newBuilder().setInteger(value).build() is Long -> Value.newBuilder().setLong(value).build() is String -> Value.newBuilder().setString(value).build() is Set<*> -> @Suppress("UNCHECKED_CAST") Value.newBuilder().setStringSet( StringSet.newBuilder().addAllStrings(value as Set<String>) ).build() //如果不是上面的类型,会直接抛异常 else -> throw IllegalStateException( "PreferencesSerializer does not support type: ${value.javaClass.name}" ) } }可以看到最后一个else逻辑中,如果不是上面的类型,会直接抛异常。因为Key是Preferences.Key< T>类型,系统默认帮我们包了一层,位于androidx.datastore.preferences.core.PreferencesKeys.kt:public fun intPreferencesKey(name: String): Preferences.Key<Int> = Preferences.Key(name)public fun doublePreferencesKey(name: String): Preferences.Key<Double> = Preferences.Key(name)public fun stringPreferencesKey(name: String): Preferences.Key<String> = Preferences.Key(name)public fun booleanPreferencesKey(name: String): Preferences.Key<Boolean> = Preferences.Key(name)public fun floatPreferencesKey(name: String): Preferences.Key<Float> = Preferences.Key(name)public fun longPreferencesKey(name: String): Preferences.Key<Long> = Preferences.Key(name)public fun stringSetPreferencesKey(name: String): Preferences.Key<Set<String>> = Preferences.Key(name)因为上述的声明都在顶层函数中,所以可以直接使用。比如我们想声明一个String类型的Preferences.Key< T>,可以直接如下进行声明:val P_KEY_NAME: Preferences.Key<String> = stringPreferencesKey("key")SP迁移至Preferences DataStore如果想对SP进行迁移,只需在Preferences DataStore构建环节添加produceMigrations参数(该参数含义创建环节已介绍)如下://SharedPreference文件名const val BOOK_PREFERENCES_NAME = "book_preferences"val Context.bookDataStorePf: DataStore<Preferences> by preferencesDataStore( name = "pf_datastore", //DataStore文件名称 //将SP迁移到Preference DataStore中 produceMigrations = { context -> listOf(SharedPreferencesMigration(context, BOOK_PREFERENCES_NAME)) })这样构建完成时,SP中的内容也会迁移到Preferences DataStore中了,注意迁移是一次性的,即执行迁移后,SP文件会被删除,如下:迁移之前(SP文件已存在): 创建Preferences DataStore并执行迁移后(SP文件已经被删除): 3.2 Proto DataStoreSP 和 Preferences DataStore 的一个缺点是无法定义架构,保证不了存取键时使用了正确的数据类型。Proto DataStore 可利用 Protocol Buffers协议缓冲区 定义架构来解决此问题。Protobuf协议缓冲区是一种对结构化数据进行序列化的机制。通过使用协议,Proto DataStore 可以知道存储的类型,无需使用键便能提供类型。添加依赖项1、添加协议缓冲区插件及 Proto DataStore 依赖项为了使用Proto DataStore,让协议缓冲区为我们的架构生成代码,需要在build.gradle 中引入protobuf插件:plugins { ... id "com.google.protobuf" version "0.8.17"}android { //.............其他配置.................. sourceSets { main { java.srcDirs = ['src/main/java'] proto { //指定proto源文件地址 srcDir 'src/main/protobuf' include '**/*.protobuf' } } } //proto buffer 协议缓冲区相关配置 用于DataStore protobuf { protoc { //protoc版本参见:https://repo1.maven.org/maven2/com/google/protobuf/protoc/ artifact = "com.google.protobuf:protoc:3.18.0" } // Generates the java Protobuf-lite code for the Protobufs in this project. See // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation // for more information. generateProtoTasks { all().each { task -> task.builtins { java { option 'lite' } } } } //修改生成java类的位置 默认是 $buildDir/generated/source/proto generatedFilesBaseDir = "$projectDir/src/main/generated" }}dependencies { api 'androidx.datastore:datastore:1.0.0' api "com.google.protobuf:protobuf-javalite:3.18.0" ...}需要配置或引入的库看上去还挺多,可以考虑将这些配置单独放到一个module中去。2、定义和使用protobuf对象只需对数据结构化的方式进行一次定义,编译器便会生成源代码,轻松写入和读取结构化数据。我们是配置依赖项的sourceSets{}中声明了proto源码地址路径在src/main/protobuf,所有的proto文件都要在该声明的路径下: Book.proto文件内容://指定protobuf版本,没有指定默认使用proto2,必须在第一行进行指定syntax = "proto3";//option:可选字段//java_package:指定proto文件生成的java类所在的包名option java_package = "org.ninetripods.mq.study";//java_outer_classname:指定该proto文件生成的java类的名称option java_outer_classname = "BookProto";enum Type { MATH = 0; CHINESE = 1; ENGLISH = 2;}message Book { string name = 1; //书名 float price = 2; //价格 Type type = 3; //类型}上述代码编写完后,执行Build -> ReBuild Project,就会在generatedFilesBaseDir配置的路径下生成对应Java代码,如下: 3、创建序列化器序列化器定义了如何存取我们在 proto 文件中定义的数据类型。如果磁盘上没有数据,序列化器还会定义默认返回值。如下我们创建一个名为BookSerializer的序列化器:object BookSerializer : Serializer<BookProto.Book> { override val defaultValue: BookProto.Book = BookProto.Book.getDefaultInstance() override suspend fun readFrom(input: InputStream): BookProto.Book { try { return BookProto.Book.parseFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException("Cannot read proto.", exception) } } override suspend fun writeTo(t: BookProto.Book, output: OutputStream) { t.writeTo(output) }}其中,BookProto.Book是通过协议缓冲区生成的代码,如果找不到 BookProto.Book 对象或相关方法,可以清理并Rebuild项目,以确保协议缓冲区生成对象。构建Proto DataStore//构建Proto DataStoreval Context.bookDataStorePt: DataStore<BookProto.Book> by dataStore( fileName = "BookProto.pb", serializer = BookSerializer)dataStore为顶层函数,可以传入的参数如下:· fileName: 创建Proto DataStore的文件名称。· serializer: Serializer序列化器定义了如何存取格式化数据。· corruptionHandler:如果DataStore在试图读取数据时,数据无法反序列化,抛出androidx.datastore.core.CorruptionException,则调用corruptionHandler。· produceMigrations:SP迁移到Proto DataStore时执行。ApplicationContext作为参数传递给这些回调,迁移在对数据进行任何访问之前运行· scope:协程作用域,默认IO操作在Dispatchers.IO线程中执行。上述代码执行后,会在/data/data/项目包名/files/下创建名为BookProto.pb的文件如下: 存数据lifecycleScope.launch { //构建BookProto.Book val bookInfo = BookProto.Book.getDefaultInstance().toBuilder() .setName("Hello Proto DataStore") .setPrice(20f) .setType(BookProto.Type.ENGLISH) .build() bookDataStorePt.updateData { bookInfo } }Proto DataStore 提供了一个挂起函数DataStore.updateData() 来存数据,当存储完成时,协程也执行完毕。取数据 /** * Proto DataStore取数据 */ val bookProtoFlow: Flow<BookProto.Book> = context.bookDataStorePt.data .catch { exception -> if (exception is IOException) { emit(BookProto.Book.getDefaultInstance()) } else { throw exception } }//Activity中lifecycleScope.launch { mBookViewModel.bookProtoFlow.collect { mTvContentPt.text = it.toString() }}Proto DataStore取数据方式跟Preferences DataStore一样,不再赘述。SP迁移至Proto DataStore//构建Proto DataStoreval Context.bookDataStorePt: DataStore<BookProto.Book> by dataStore( fileName = "BookProto.pb", serializer = BookSerializer, //将SP迁移到Proto DataStore中 produceMigrations = { context -> listOf( androidx.datastore.migrations.SharedPreferencesMigration( context, BOOK_PREFERENCES_NAME ) { sharedPrefs: SharedPreferencesView, currentData: BookProto.Book -> //从SP中取出数据 val bookName: String = sharedPrefs.getString(KEY_BOOK_NAME, "") ?: "" val bookPrice: Float = sharedPrefs.getFloat(KEY_BOOK_PRICE, 0f) val typeStr = sharedPrefs.getString(KEY_BOOK_TYPE, BookProto.Type.MATH.name) val bookType: BookProto.Type = BookProto.Type.valueOf(typeStr ?: BookProto.Type.MATH.name) //将SP中的数据存入Proto DataStore中 currentData.toBuilder() .setName(bookName) .setPrice(bookPrice) .setType(bookType) .build() } ) })Proto DataStore 定义了 SharedPreferencesMigration 类。migrate里指定了下面两个参数:· SharedPreferencesView: 可以用于从 SharedPreferences 中检索数据· BookProto.Book:当前数据同样地在创建时如果传入了produceMigrations,那么SP文件会迁移至Proto DataStore,迁移完后SP文件被删除。这里还需要注意一点,Preferences DataStore 、Proto DataStore在执行迁移时都会用到SharedPreferencesMigration类,但是这两个地方用到该类对应的包名是不一样的,如Proto DataStore的包名路径是androidx.datastore.migrations.SharedPreferencesMigration,当把他们写在一个文件里时,注意其中一个要使用完整路径。四、总结直接上官网给出的SP与DataStore对比:
0
0
0
浏览量494
IT大鲨鱼

Java&Android | 多线程之ThreadLocal的使用及源码解析

ThreadLocal是什么ThreadLocal是一个能创建线程局部变量的类。通过ThreadLocal提供的get和set方法,可以为每一个使用该变量的线程保存一份数据副本,且线程之间是不能相互访问的,从而达到变量在线程间隔离、封闭的效果。使用例子public static void main(String[] args) throws InterruptedException { final ThreadLocal<String> threadLocal = new ThreadLocal<>(); threadLocal.set("AAA"); new Thread(new Runnable() { @Override public void run() { threadLocal.set("BBB"); System.out.println("get in " + Thread.currentThread().getName() + " " + threadLocal.get()); } }).start(); Thread.sleep(1000); System.out.println("get in main thread " + threadLocal.get()); } 执行结果:get in Thread-0 BBB get in main thread AAA首先,在主线程中初始化了ThreadLocal,并且操作的变量是String类型,在主线程中设置该变量为"AAA",主线程等待1秒钟,同时启动了一个子线程也调用ThreadLocal设置该变量为"BBB"并输出,1秒之后通过get输出主线程的结果,发现子线程设置的值并没有影响主线程中设置的值,即通过ThreadLocal修饰的变量可以实现在各个线程之间互不干扰,相互隔离的效果。源码解析初始化//1 final ThreadLocal<String> threadLocal = new ThreadLocal<>(); threadLocal.set("AAA"); //2 final ThreadLocal<String> threadLocal = new ThreadLocal<String>() { @Override protected String initialValue() { return "AAA"; } }; 对应的源码:protected T initialValue() { return null; } public ThreadLocal() { } ThreadLocal的初始化可以有上面1、2两种方式,一种是先初始化然后通过set设置值,一种直接重写initialValue并设置值。既然ThreadLocal可以做到变量的线程封闭,我们有理由猜想是不是ThreadLocal是通过Map<Thread,T>来实现的呢?其中key是当前Thread,value是通过set或者initialValue设置的,看似是这样,但ThreadLocal内部并不是这么实现的,接着往下分析。set值public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //根据当前线程获取ThreadLocalMap,注:ThreadLocalMap内部并不是通过map来存储value,而是通过数组存储的 ThreadLocalMap map = getMap(t); if (map != null) //不为空,内部直接通过数组设置Entry元素(Entry中包装了ThreadLocal及value,其中key=ThreadLocal,value=传入值value) map.set(this, value); else //为空,则初始化一个ThreadLocalMap,并将ThreadLocal及value包装成Entry放入数组中。 createMap(t, value); } ThreadLocalMap getMap(Thread t) { //threadLocals是Thread类中的成员变量 return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } Thread类:public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; } 所以set方法首先根据当前线程获取线程中的threadLocals变量(ThreadLocalMap类型),并将ThreadLocal及value包装成Entry放入数组中,因为threadLocals是Thread中的局部变量(存放在栈空间中),所以只有当前线程能访问,其他线程无法访问。这里有个问题:为什么还需要将ThreadLocal作为key传入到ThreadLocalMap呢?因为一个线程中可以初始化多个ThreadLocal,是一对多的关系,所以需要传入ThreadLocal,如果初始化了多个ThreadLocal,根据不同的ThreadLocal可以获得对应的value。那么ThreadLocalMap内部到底是怎么存储的呢?ThreadLocal静态内部类ThreadLocalMap:static class ThreadLocalMap { //内部类Entry,继承了弱引用WeakReference,使用ThreadLocal作为键值 static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } //初始容量 必须是2个倍数 private static final int INITIAL_CAPACITY = 16; //Entry数组,必要时可以扩容, private Entry[] table; //数组大小 private int size = 0; //初始化ThreadLocalMap,并将ThreadLocal、firstValue封装成Entry并放入Entry数组中 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } //根据key(ThreadLocal类型)的hash获取Entry在数组中的位置,有数据的话直接返回该数据 private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; } //根据key(ThreadLocal类型)设置value值 private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; //获取数组中存取位置 int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //如果key值在Entry中存在,那么直接覆盖之前的值 if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } //移除key对应的value private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } } } get值public T get() { //获取当前thread Thread t = Thread.currentThread(); //根据当前线程获取ThreadLocalMap,注:ThreadLocalMap内部并不是通过map来存储value,而是通过数组存储的 ThreadLocalMap map = getMap(t); if (map != null) { //根据this(ThreeadLocal)获取数组中对应的Entry,不为空直接取出value ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { T result = (T)e.value; return result; } } //如果线程中的ThreadLocalMap为空,则进行初始化 return setInitialValue(); } private T setInitialValue() { //初始化值 默认是null T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } ThreadLocal在Handler中的使用Handler机制: Handler构造函数:public Handler(Callback callback, boolean async) { ......其他代码...... mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } Looper.prepare初始化:static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } Android中Handler机制在项目中使用的很频繁,Handler底层通过MessageQueue和Looper来实现消息的线程间通信。其中Handler来发送及接收并处理消息,MessageQueue接收Handler发来的消息,并在Looper循环中根据msg.target(handler)来分发消息。一个线程只对应一个Lopper,一个Looper对应一个MessageQueue,但是一个线程中可以有多个Handler。因为一个线程只能对应一个Looper,且Looper跟线程是一一绑定关系,此时用ThreadLocal再合适不过。Looper中使用ThreadLocal关联Looper,使得Looper只能在各自线程使用,并且不管handler从哪个线程传来消息,ThreadLocal保证了最终消息在Looper初始化时所在的线程处理。总结· ThreadLocal存储变量副本实际是保存在每个线程的threadLocals(ThreadLocal.ThreadLocalMap类型)变量中。· ThreadLocal包含的对象(指的是ThreadLocal中的T对象)在不同的线程中有不同的副本(实际上也是不同的实例)· ThreadLocalMap中的Entry弱引用于ThreadLocal,同时也会回收key为null的Entry,从而避免了Entry无法释放导致内存泄漏画一个简易图:
0
0
0
浏览量2018
IT大鲨鱼

Android Jetpack系列之MVI架构

写在前面在之前介绍MVVM的文章中,介绍了常用的MVC、MVP、MVVM架构及其对MVVM的封装使用,其中MVVM的主旨可以理解为数据驱动:Repository提供数据,ViewModel中发送数据,UI层使用的LiveData订阅数据,当有数据变化时会主动通知UI层进行刷新。有兴趣的可以去看一下:1、 Android Jetpack系列之MVVM使用及封装2、Android Jetpack系列之MVVM使用及封装(续)那么MVI又是什么呢?看了一些关于MVI的文章,大家都称MVI是(Model-View-Intent),其中Intent称为意图(注意这里的Intent并不是页面跳转时使用的Intent),MVI本质上是在MVVM的基础上将View与ViewModel之间的数据传递做了统一整合。google官方文档中并没有MVI的说法,而是在之前的MVVM架构基础上进行了升级,其主旨意思与MVI很相近,为了保持一致,后续介绍的MVVM升级版架构统一称之为MVI架构。MVI vs  MVVM新旧架构对比旧版MVVM架构:新版MVVM或者称之为MVI:差异1、LiveData < T>  改为Flow< UIState>关于LiveData的缺点:LiveData的接收只能在主线程;LiveData发送数据是一次性买卖,不能多次发送;LiveData发送数据的线程是固定的,不能切换线程,setValue/postValue本质上都是在主线程上发送的。当需要来回切换线程时,LiveData就显得无能为力了。Flow可以完美解决LiveData遇到的问题,既可以多次从上游发送数据,也可以灵活地切换线程,所以如果涉及到来回切线程,那么使用Flow是更优解。关于Flow的详细用法,感兴趣的同学可以参见:Android Kotlin之Flow数据流注:如果项目中还没有切换到Kotlin,依然可以使用LiveData来发送数据;如果已经切换到Kotlin,那么更推荐使用Flow来发送数据。还有一点区别,LiveData在旧版架构中传递的是单个实体数据,即每个数据都会对应一个LiveData,很显然,如果页面逻辑很复杂的话,会导致ViewModel中的LiveData膨胀;新版架构中通过Flow发送的统一为UIState了,UIState本质上也是一个data类,不同的是UIState会把View层相关的实体状态统一管控,这样在ViewModel中只需要一个Flow来统一交互即可。差异2、交互规范新版架构中,提出了单向数据流来管理页面状态的概念:即数据的流向是固定的,整个数据流向是View -> ViewModel -> Model数据层 -> ViewModel获得数据 -> 根据UiState刷新View层。其中,事件 Events 向上流动、状态 UiState 向下流动的。整体流程如下:ViewModel 会存储并公开界面要使用的状态。界面状态是经过 ViewModel 转换的应用数据。界面会向 ViewModel 发送用户事件通知。ViewModel 会处理用户操作并更新状态。更新后的状态将反馈给界面以进行呈现。系统会对导致状态更改的所有事件重复上述操作。官方给了一个点击书签的示例:上面是UI界面中添加书签的操作,点击之后成功添加书签,那么整个数据的流转过程如下:单向数据流提高了代码的可读性及修改的便利性。单向数据流有以下好处:数据一致性。界面只有一个可信来源。可测试性。状态来源是独立的,因此可独立于界面进行测试。可维护性。状态的更改遵循明确定义的模式,即状态更改是用户事件及其数据拉取来源共同作用的结果。MVI实战示例图定义UIState & 编写ViewModelclass MViewModel : BaseViewModel<MviState, MviSingleUiState>() { //Repository中间层 管理所有数据来源 包括本地的及网络的 private val mWanRepo = WanRepository() override fun initUiState(): MviState { return MviState(BannerUiState.INIT, DetailUiState.INIT) } //请求Banner数据 fun loadBannerData() { requestDataWithFlow( showLoading = true, request = { mWanRepo.requestWanData("") }, successCallback = { data -> sendUiState { copy(bannerUiState = BannerUiState.SUCCESS(data)) } }, failCallback = {} ) } //请求List数据 fun loadDetailData() { requestDataWithFlow( showLoading = false, request = { mWanRepo.requestRankData() }, successCallback = { data -> sendUiState { copy(detailUiState = DetailUiState.SUCCESS(data)) } }, ) } fun showToast() { sendSingleUiState(MviSingleUiState("触发了一次性消费事件!")) } } /** * 定义UiState 将View层所有实体类相关的都包括在这里,可以有效避免模板代码(StateFlow只需要定义一个即可) */ data class MviState(val bannerUiState: BannerUiState, val detailUiState: DetailUiState?) : IUiState data class MviSingleUiState(val message: String) : ISingleUiState sealed class BannerUiState { object INIT : BannerUiState() data class SUCCESS(val models: List<WanModel>) : BannerUiState() } sealed class DetailUiState { object INIT : DetailUiState() data class SUCCESS(val detail: RankModel) : DetailUiState() } 其中MviState中定义的UIState即是View层相关的数据类,而MviSingleUiState中定义的是一次性消费事件,如Toast、跳转页面等,所以使用的Channel来交互,在前面的文章中已经讲到了,这里不再重复。相关接口:interface IUiState //重复性事件 可以多次消费 interface ISingleUiState //一次性事件,不支持多次消费 object EmptySingleState : ISingleUiState //一次性事件,不支持多次消费 sealed class LoadUiState { data class Loading(var isShow: Boolean) : LoadUiState() object ShowMainView : LoadUiState() data class Error(val msg: String) : LoadUiState() } LoadUiState定义了页面加载的几种状态:正在加载Loading、加载成功ShowMainView、加载失败Error,几种状态的使用与切换在BaseViewModel中数据请求中进行了封装,具体使用可参考示例代码。如果页面请求中没有一次性消费事件,ViewModel初始化时可以直接传入EmptySingleState。基类BaseViewModel/** * ViewModel基类 * * @param UiState 重复性事件,View层可以多次接收并刷新 * @param SingleUiState 一次性事件,View层不支持多次消费 如弹Toast,导航Activity等 */ abstract class BaseViewModel<UiState : IUiState, SingleUiState : ISingleUiState> : ViewModel() { /** * 可以重复消费的事件 */ private val _uiStateFlow = MutableStateFlow(initUiState()) val uiStateFlow: StateFlow<UiState> = _uiStateFlow /** * 一次性事件 且 一对一的订阅关系 * 例如:弹Toast、导航Fragment等 * Channel特点 * 1.每个消息只有一个订阅者可以收到,用于一对一的通信 * 2.第一个订阅者可以收到 collect 之前的事件 */ private val _sUiStateFlow: Channel<SingleUiState> = Channel() val sUiStateFlow: Flow<SingleUiState> = _sUiStateFlow.receiveAsFlow() private val _loadUiStateFlow: Channel<LoadUiState> = Channel() val loadUiStateFlow: Flow<LoadUiState> = _loadUiStateFlow.receiveAsFlow() protected abstract fun initUiState(): UiState protected fun sendUiState(copy: UiState.() -> UiState) { _uiStateFlow.update { _uiStateFlow.value.copy() } } protected fun sendSingleUiState(sUiState: SingleUiState) { viewModelScope.launch { _sUiStateFlow.send(sUiState) } } /** * 发送当前加载状态: Loading、Error、Normal */ private fun sendLoadUiState(loadState: LoadUiState) { viewModelScope.launch { _loadUiStateFlow.send(loadState) } } /** * @param showLoading 是否展示Loading * @param request 请求数据 * @param successCallback 请求成功 * @param failCallback 请求失败,处理异常逻辑 */ protected fun <T : Any> requestDataWithFlow( showLoading: Boolean = true, request: suspend () -> BaseData<T>, successCallback: (T) -> Unit, failCallback: suspend (String) -> Unit = { errMsg -> //默认异常处理,子类可以进行覆写 sendLoadUiState(LoadUiState.Error(errMsg)) }, ) { viewModelScope.launch { //是否展示Loading if (showLoading) { sendLoadUiState(LoadUiState.Loading(true)) } val baseData: BaseData<T> try { baseData = request() when (baseData.state) { ReqState.Success -> { sendLoadUiState(LoadUiState.ShowMainView) baseData.data?.let { successCallback(it) } } ReqState.Error -> baseData.msg?.let { error(it) } } } catch (e: Exception) { e.message?.let { failCallback(it) } } finally { if (showLoading) { sendLoadUiState(LoadUiState.Loading(false)) } } } } } 基类中StateFlow的默认值是通过initUiState()来定义的,并强制需要子类实现: override fun initUiState(): MviState { return MviState(BannerUiState.INIT, DetailUiState.INIT) } 这样当一进入页面时就会在监听到这些初始化事件,并作出反应,如果不需要处理,可以直接略过。requestDataWithFlow里封装了整个请求逻辑,Repository数据支持定义数据BaseData类:class BaseData<T> { @SerializedName("errorCode") var code = -1 @SerializedName("errorMsg") var msg: String? = null var data: T? = null var state: ReqState = ReqState.Error } enum class ReqState { Success, Error } 基类BaseRepository:open class BaseRepository { suspend fun <T : Any> executeRequest( block: suspend () -> BaseData<T> ): BaseData<T> { val baseData = block.invoke() if (baseData.code == 0) { //正确 baseData.state = ReqState.Success } else { //错误 baseData.state = ReqState.Error } return baseData } } 基类中定义请求逻辑,子类中直接使用:class WanRepository : BaseRepository() { val service = RetrofitUtil.getService(DrinkService::class.java) suspend fun requestWanData(drinkId: String): BaseData<List<WanModel>> { return executeRequest { service.getBanner() } } suspend fun requestRankData(): BaseData<RankModel> { return executeRequest { service.getRankList() } } } View层/** * MVI示例 */ class MviExampleActivity : BaseMviActivity() { private val mBtnQuest: Button by id(R.id.btn_request) private val mToolBar: Toolbar by id(R.id.toolbar) private val mContentView: ViewGroup by id(R.id.cl_content_view) private val mViewPager2: MVPager2 by id(R.id.mvp_pager2) private val mRvRank: RecyclerView by id(R.id.rv_view) private val mViewModel: MViewModel by viewModels() override fun getLayoutId(): Int { return R.layout.activity_wan_android_mvi } override fun initViews() { initToolBar(mToolBar, "Jetpack MVI", true, true, BaseActivity.TYPE_BLOG) mRvRank.layoutManager = GridLayoutManager(this, 2) } override fun initEvents() { registerEvent() mBtnQuest.setOnClickListener { mViewModel.showToast() //一次性消费 mViewModel.loadBannerData() mViewModel.loadDetailData() } } private fun registerEvent() { /** * Load加载事件 Loading、Error、ShowMainView */ mViewModel.loadUiStateFlow.flowWithLifecycle2(this) { state -> when (state) { is LoadUiState.Error -> mStatusViewUtil.showErrorView(state.msg) is LoadUiState.ShowMainView -> mStatusViewUtil.showMainView() is LoadUiState.Loading -> mStatusViewUtil.showLoadingView(state.isShow) } } /** * 一次性消费事件 */ mViewModel.sUiStateFlow.flowWithLifecycle2(this) { data -> showToast(data.message) } mViewModel.uiStateFlow.flowWithLifecycle2(this, prop1 = MviState::bannerUiState) { state -> when (state) { is BannerUiState.INIT -> {} is BannerUiState.SUCCESS -> { mViewPager2.visibility = View.VISIBLE mBtnQuest.visibility = View.GONE val imgs = mutableListOf<String>() for (model in state.models) { imgs.add(model.imagePath) } mViewPager2.setIndicatorShow(true).setModels(imgs).start() } } } mViewModel.uiStateFlow.flowWithLifecycle2(this, Lifecycle.State.STARTED, prop1 = MviState::detailUiState) { state -> when (state) { is DetailUiState.INIT -> {} is DetailUiState.SUCCESS -> { mRvRank.visibility = View.VISIBLE val list = state.detail.datas mRvRank.adapter = RankAdapter().apply { setModels(list) } } } } } override fun retryRequest() { //点击屏幕重试 mViewModel.showToast() //一次性消费 mViewModel.loadBannerData() mViewModel.loadDetailData() } /** * 展示Loading、Empty、Error视图等 */ override fun getStatusOwnerView(): View? { return mContentView } } 先回看下新版架构图,View->ViewModel请求数据时通过events来进行传递,可以如在ViewModel中进行封装:sealed class EVENT : IEvent { object Banner : EVENT() object Detail : EVENT() } override fun dispatchEvent(event: EVENT) { when (event) { EVENT.Banner -> { loadBannerData() } EVENT.Detail -> {loadDetailData() } } 那么View层中可以如下调用:mViewModel.dispatchEvent(EVENT.Banner) mViewModel.dispatchEvent(EVENT.Detail) 而在示例中在View层发送数据请求时,并没有在ViewModel中将请求进行封装,而是直接通过mViewModel.loadBannerData()进行的请求,个人认为封装Event的做法有点多余了。总结升级版的MVI架构相比于旧版MVVM架构,规范性更好,约束性也更强。具体来说:Flow相比于LiveData来说,能力更强,尤其当遇到来回切线程时;定义了UIState来集中管理页面的数据状态,从而ViewModel中只需定义一个StateFlow来管理即可,减少模板代码。同时定义UIState也会带来副作用,即View层没有diff能力,会对每一次的事件进行全量更新,不过可以在View层将UIState里的内容细化监听来达到增量更新UI的目的。但是并不是说新版的架构就一定适合你的项目,架构毕竟是一种规范,具体使用还需要见仁见智。
0
0
0
浏览量999
IT大鲨鱼

JNI 编程上手指南之 JNI 数据类型

1. 数据类型JNI 程序中涉及了三种数据类型,分别是:Java 类型JNI 类型C/C++ 类型在 Java 程序中我们使用的是 Java 类型,C/C++ 程序中拿到的是 JNI 类型,我们需要将其转换为 C/C++ 类型,使用 C/C++ 类型再去调用 C/C++ 层函数完成计算或 IO 操作等任务后,将结果再转换为 JNI 类型返回后,在 java 代码中,我们就能收到对应的 Java 类型。我们可以在 $JAVA_HOME/inlcude/jni.h 文件中查看到 jni 中基本类型的定义:typedef unsigned char jboolean; typedef unsigned short jchar; typedef short jshort; typedef float jfloat; typedef double jdouble; typedef jint jsize; $JAVA_HOME/include/linux/jni_md.h 中定义了 jbyte, jint and jlong 和 CPU 平台相关的类型:typedef int jint; #ifdef _LP64 typedef long jlong; #else typedef long long jlong; #endif typedef signed char jbyte; 以上这些类型我们称之为基本数据类型,其关系梳理如下:Java 类型JNI 类型C/C++ 类型booleanjbooleanunsigned charbytejbytesigned charcharjcharunsigned shortshortjshortsigned shortintjintintlongjlonglongfloatjfloatfloatdoublejdoubledouble这些类型不需要进行转换,可以直接在 JNI 中使用:jbyte result=0xff; jint size; jbyte* timeBytes; 引用类型也定义在 jni.h 中:#ifdef __cplusplus class _jobject {}; class _jclass : public _jobject {}; class _jthrowable : public _jobject {}; class _jstring : public _jobject {}; class _jarray : public _jobject {}; class _jbooleanArray : public _jarray {}; class _jbyteArray : public _jarray {}; class _jcharArray : public _jarray {}; class _jshortArray : public _jarray {}; class _jintArray : public _jarray {}; class _jlongArray : public _jarray {}; class _jfloatArray : public _jarray {}; class _jdoubleArray : public _jarray {}; class _jobjectArray : public _jarray {}; typedef _jobject *jobject; typedef _jclass *jclass; typedef _jthrowable *jthrowable; typedef _jstring *jstring; typedef _jarray *jarray; typedef _jbooleanArray *jbooleanArray; typedef _jbyteArray *jbyteArray; typedef _jcharArray *jcharArray; typedef _jshortArray *jshortArray; typedef _jintArray *jintArray; typedef _jlongArray *jlongArray; typedef _jfloatArray *jfloatArray; typedef _jdoubleArray *jdoubleArray; typedef _jobjectArray *jobjectArray; #else struct _jobject; typedef struct _jobject *jobject; typedef jobject jclass; typedef jobject jthrowable; typedef jobject jstring; typedef jobject jarray; typedef jarray jbooleanArray; typedef jarray jbyteArray; typedef jarray jcharArray; typedef jarray jshortArray; typedef jarray jintArray; typedef jarray jlongArray; typedef jarray jfloatArray; typedef jarray jdoubleArray; typedef jarray jobjectArray; #endif 总结如下:java 类型JNI 引用类型类型描述java.lang.Objectjobject表示任何Java的对象java.lang.StringjstringJava的String字符串类型的对象java.lang.ClassjclassJava的Class类型对象java.lang.ThrowablejthrowableJava的Throwable类型byte[]jbyteArrayJava byte型数组Object[]jobjectArrayJava任何对象的数组boolean[]jbooleanArrayJava boolean型数组char[]jcharArrayJava char型数组short[]jshortArrayJava short型数组int[]jintArrayJava int型数组long[]jlongArrayJava long型数组float[]jfloatArrayJava float型数组double[]jdoubleArrayJava double型数组2. 数据类型转换native 程序主要做了这么几件事:接收 JNI 类型的参数参数类型转换,JNI 类型转换为 Native 类型执行 Native 代码创建一个 JNI 类型的返回对象,将结果拷贝到这个对象并返回结果其中很多代码都是在做类型转换的操作,下面我们来看看类型转换的示例。2.1 基本类型基本类型无需做转换,直接使用:java 层:private native double average(int n1, int n2); c/c++ 层:JNIEXPORT jdouble JNICALL Java_HelloJNI_average(JNIEnv *env, jobject jobj, jint n1, jint n2) { //基本类型不用做转换,直接使用 cout << "n1 = " << n1 << ", n2 = " << n2 << endl; return jdouble(n1 + n2)/2.0; } 2.2 字符串为了在 C/C++ 中使用 Java 字符串,需要先将 Java 字符串转换成 C 字符串。用 GetStringChars 函数可以将 Unicode 格式的 Java 字符串转换成 C 字符串,用 GetStringUTFChars 函数可以将 UTF-8 格式的 Java 字符串转换成 C 字符串。这些函数的第三个参数均为 isCopy,它让调用者确定返回的 C 字符串地址指向副本还是指向堆中的固定对象。java 层:private native String sayHello(String msg); c/c++ 层:jJNIEXPORT jstring JNICALL Java_HelloJNI_sayHello__Ljava_lang_String_2(JNIEnv *env, jobject jobj, jstring str) { //jstring -> char* jboolean isCopy; //GetStringChars 用于 unicode 编码 //GetStringUTFChars 用于 utf-8 编码 const char* cStr = env->GetStringUTFChars(str, &isCopy); if (nullptr == cStr) { return nullptr; } if (JNI_TRUE == isCopy) { cout << "C 字符串是 java 字符串的一份拷贝" << endl; } else { cout << "C 字符串指向 java 层的字符串" << endl; } cout << "C/C++ 层接收到的字符串是 " << cStr << endl; //通过JNI GetStringChars 函数和 GetStringUTFChars 函数获得的C字符串在原生代码中 //使用完之后需要正确地释放,否则将会引起内存泄露。 env->ReleaseStringUTFChars(str, cStr); string outString = "Hello, JNI"; // char* 转换为 jstring return env->NewStringUTF(outString.c_str()); } 2.3 数组数组的操作与字符串类似:java 层:private native double[] sumAndAverage(int[] numbers); c++ 层:JNIEXPORT jdoubleArray JNICALL Java_HelloJNI_sumAndAverage(JNIEnv *env, jobject obj, jintArray inJNIArray) { //类型转换 jintArray -> jint* jboolean isCopy; jint* inArray = env->GetIntArrayElements(inJNIArray, &isCopy); if (JNI_TRUE == isCopy) { cout << "C 层的数组是 java 层数组的一份拷贝" << endl; } else { cout << "C 层的数组指向 java 层的数组" << endl; } if(nullptr == inArray) return nullptr; //获取到数组长度 jsize length = env->GetArrayLength(inJNIArray); jint sum = 0; for(int i = 0; i < length; ++i) { sum += inArray[i]; } jdouble average = (jdouble)sum / length; //释放数组 env->ReleaseIntArrayElements(inJNIArray, inArray, 0); // release resource //构造返回数据,outArray 是指针类型,需要 free 或者 delete 吗?要的 jdouble outArray[] = {sum, average}; jdoubleArray outJNIArray = env->NewDoubleArray(2); if(NULL == outJNIArray) return NULL; //向 jdoubleArray 写入数据 env->SetDoubleArrayRegion(outJNIArray, 0, 2, outArray); return outJNIArray; } 其他类型的装换都大体类似,大家可以举一反三。3. 引用类型我们先回顾一下 Native 层和 Java 层里对象的创建和销毁的过程以 C++ 为例,Native 层中要创建一个对象的话需使用 new 操作符先分配内存,然后构造对象。如果不再使用这个对象,则需要通过 作符先析构这个对象,然后回收该对象所占的内存。Java 层中也通过 new 操作来构造一个对象。如果后续不再使用它,则可以显式地设置持有这个对象的变量的值为 null(也可以不做这一步,而交由垃圾回收来扫描和标记该对象是否有被引用)。该对象所占的内存则在垃圾回收过程中被收回。JNI 层作为 Java 层和 Native 层之间相交互的中间层,它兼具 Native 层和 Java 层的某些特性,尤其在对引用对象的创建和回收上。和 C++ 里的 new 操作符可以创建一个对象类似,JNI 层可以利用 JNI NewObject 等函数创建一个 Java 意义的对象(引用型对象)。这个被 New 出来的对象是局部(Local) 型的引用对象。JNI 层可通过 DeleteLocalRef 释放 Local 型的引用对象(等同于Java 层中设置持有这个对象的变量的值为 null)。如果不调用 DeleteLocalRef 的话,根据 JNI 规范,Local 型对象在 JNI 函数返回后,也会由虚拟机根据垃圾回收的逻辑进行标记和回收。除了 Local 型对象外,JNI 层借助JNI Global 相关函数可以将一个 Local 型引用对象转换成一个全局(Global) 型对象。而 Global 型对象的回收只能先由程序显式地调用 Global 相关函数进行删除,然后,虚拟机才能借助垃圾回收机制回收它们引用类型针对的是除开基本类型的 JNI 类型,比如 jstring, jclass ,jobject 等。JNI 类型是 java 层与 c 层的中间类型,java 层与 c 层都需要管理他。我们可以将 JNI 引用类型理解为 Java 意义的对象。JNI 类型根据使用的方式可分为:局部引用全部引用弱全部引用3.1 局部引用3.1.1 局部引用与概念什么是局部引用?通过 JNI 接口从 Java 传递下来或者通过 NewLocalRef 和各种 JNI 接口(FindClass、NewObject、GetObjectClass和NewCharArray等)创建的引用称为局部引用。局部引用的特点?在函数为执行完毕前,局部引用会阻止 GC 回收所引用的对象局部引用不在本地函数中跨函数使用,不能跨线前使用,当然也不能缓存起来使用函数返回后(未返回局部引用的情况下),局部引用所引用的对象会被 JVM 自动释放,也可在函数结束前通过 DeleteLocalRef 函数手动释放如果 c 函数返回了一个局部引用数据,在 java 层,该类型会转换为对应的 java 类型。当 java 层不存在该对象的引用时,gc 就会回收该对象一个常见的错误是使用静态变量保存局部引用,试图缓存变量提高性能:JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) { static jclass cls_string = NULL; if (cls_string == NULL) { cls_string = (*env)->FindClass(env, "java/lang/String"); if (cls_string == NULL) { return NULL; } } return (*env)->NewStringUTF(env,"Hello from JNI !"); } cls_string 是一个局部引用,当 native 函数执行完成后,gc 可能会回收掉 cls_string 指向的内存。下次调用该函数时,cls_string 存储的就是一个被释放后的内存地址,成了一个野指针。严重的,造成非法地址的访问,程序崩溃。3.1.2 释放局部变量释放一个局部引用有两种方式:本地方法执行完毕后 JVM 自动释放,自己调用 DeleteLocalRef 手动释放既然 JVM 会在函数返回后会自动释放所有局部引用,为什么还需要手动释放呢? 以下几种情况下,为了避免内存溢出,我们应该手动释放局部引用:JNI 会将创建的局部引用都存储在一个局部引用表中,如果这个表超过了最大容量限制,就会造成局部引用表溢出,使程序崩溃。经测试,Android上的 JNI 局部引用表最大数量是 512 个。当我们在实现一个本地方法时,可能需要创建大量的局部引用,如果没有及时释放,就有可能导致 JNI 局部引用表的溢出,所以,在不需要局部引用时就立即调用 DeleteLocalRef 手动删除。for (i = 0; i < len; i++) { jstring jstr = (*env)->GetObjectArrayElement(env, arr, i); ... /* 使用jstr */ (*env)->DeleteLocalRef(env, jstr); // 使用完成之后马上释放 }在编写 JNI 工具函数时,工具函数在程序当中是公用的,被谁调用你是不知道的。其内部的局部引用在使用完成后应该立即释放,避免过多的内存占用。如果你的本地函数不会返回。比如一个接收消息的函数,里面有一个死循环,用于等待别人发送消息过来 while(true) { if (有新的消息) { 处理之。。。。} else { 等待新的消息。。。}}。如果在消息循环当中创建的引用你不显示删除,很快将会造成JVM局部引用表溢出。局部引用使用完了就删除,而不是要等到函数结尾才释放,局部引用会阻止所引用的对象被 GC 回收。比如你写的一个本地函数中刚开始需要访问一个大对象,因此一开始就创建了一个对这个对象的引用,但在函数返回前会有一个大量的非常复杂的计算过程,而在这个计算过程当中是不需要前面创建的那个大对象的引用的。但是,在计算的过程当中,如果这个大对象的引用还没有被释放的话,会阻止 GC 回收这个对象,内存一直占用者,造成资源的浪费。所以这种情况下,在进行复杂计算之前就应该把引用给释放了,以免不必要的资源浪费。言而总之,当一个局部引用不在使用后,立即将其释放,以避免不必要的内存浪费。3.1.3 局部引用的管理JNI 的规范指出,JVM 要确保每个 Native 方法至少可以创建 16 个局部引用,经验表明,16 个局部引用已经足够平常的使用了。但是,如果要与 JVM 的中对象进行复杂的交互计算,就需要创建更多的局部引用了,这时就需要使用 EnsureLocalCapacity 来确保可以创建指定数量的局部引用,如果创建成功返回 0 ,返回返回小于 0 ,如下代码示例: // Use EnsureLocalCapacity int len = 20; if (env->EnsureLocalCapacity(len) < 0) { // 创建失败,out of memory } for (int i = 0; i < len; ++i) { jstring jstr = env->GetObjectArrayElement(arr,i); // 处理 字符串 // 创建了足够多的局部引用,这里就不用删除了,显然占用更多的内存 } 确保可以创建了足够的局部引用数量,所以在循环处理局部引用时可以不进行删除了,但是显然会消耗更多的内存空间了。PushLocalFrame 与 PopLocalFrame 是两个配套使用的函数对。它们可以为局部引用创建一个指定数量内嵌的空间,在这个函数对之间的局部引用都会在这个空间内,直到释放后,所有的局部引用都会被释放掉,不用再担心每一个局部引用的释放问题了。常见的使用场景就是在循环中: // Use PushLocalFrame & PopLocalFrame for (int i = 0; i < len; ++i) { if (env->PushLocalFrame(len)) { // 创建指定数据的局部引用空间 //out ot memory } jstring jstr = env->GetObjectArrayElement(arr, i); // 处理字符串 // 期间创建的局部引用,都会在 PushLocalFrame 创建的局部引用空间中 // 调用 PopLocalFrame 直接释放这个空间内的所有局部引用 env->PopLocalFrame(NULL); } 使用 PushLocalFrame & PopLocalFrame 函数对,就可以在期间放心地处理局部引用,最后统一释放掉。3.2 全局引用全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效。同局部引用一样,也会阻止它所引用的对象被 GC 回收。与局部引用不一样的是,函数执行完后,GC 也不会回收全局引用指向的对象。与局部引用创建方式不同的是,只能通过 NewGlobalRef 函数创建。 static jclass cls_string = NULL; if (cls_string == NULL) { jclass local_cls_string = (*env)->FindClass(env, "java/lang/String"); if (cls_string == NULL) { return NULL; } // 将java.lang.String类的Class引用缓存到全局引用当中 cls_string = (*env)->NewGlobalRef(env, local_cls_string); // 删除局部引用 (*env)->DeleteLocalRef(env, local_cls_string); // 再次验证全局引用是否创建成功 if (cls_string == NULL) { return NULL; } } 当我们的本地代码不再需要一个全局引用时,应该马上调用 DeleteGlobalRef 来释放它。如果不手动调用这个函数,即使这个对象已经没用了,JVM 也不会回收这个全局引用所指向的对象。3.3 弱全局引用弱全局引用使用 NewGlobalWeakRef 创建,使用 DeleteGlobalWeakRef 释放。下面简称弱引用。与全局引用类似,弱引用可以跨方法、线程使用。但与全局引用很重要不同的一点是,弱引用不会阻止 GC 回收它引用的对象。 static jclass myCls2 = NULL; if (myCls2 == NULL) { jclass myCls2Local = (*env)->FindClass(env, "mypkg/MyCls2"); if (myCls2Local == NULL) { return; /* 没有找到mypkg/MyCls2这个类 */ } myCls2 = NewWeakGlobalRef(env, myCls2Local); if (myCls2 == NULL) { return; /* 内存溢出 */ } } ... /* 使用myCls2的引用 */ 3.4 引用比较IsSameObject 用来判断两个引用是否指向相同的对象。还可以用 isSameObject 来比较弱全局引用所引用的对象是否被 GC 了,返回 JNI_TRUE 则表示回收了,JNI_FALSE 则表示未被回收。env->IsSameObject(obj1, obj2) // 比较两个引用是否指向相同的对象 env->IsSameObject(obj, NULL) // 比较局部引用或者全局引用是否为 NULL env->IsSameObject(wobj, NULL) // 比较弱全局引用所引用对象是否被 GC 回收 一些疑问如果 C 层返回给 java 层一个全局引用,这个全局引用何时可以被 GC 回收?我认为不会被 GC 回收,造成内存泄漏。所以 JNI 函数如果要返回一个对象,我们应该使用局部引用作为返回值。
0
0
0
浏览量951
IT大鲨鱼

Android Jetpack系列之ViewModel

ViewModel介绍ViewModel的定义:ViewModel旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel本质上是视图(View)与数据(Model)之间的桥梁,想想以前的MVC模式,视图和数据都会写在Activity/Fragment中,导致Activity/Fragment过重,后续难以维护,而ViewModel将视图和数据进行了分离解耦,为视图层提供数据。ViewModel的特点:ViewModel生命周期比Activity长数据可在屏幕发生旋转等配置更改后继续留存。下面是ViewModel生命周期图:可以看到即使是发生屏幕旋转,旋转之后拿到的ViewModel跟之前的是同一个实例,即发生屏幕旋转时,ViewModel并不会消失重建;而如果Activity是正常finish(),ViewModel则会调用onClear()销毁。ViewModel中不能持有Activity引用不要将Activity传入ViewModel中,因为ViewModel的生命周期比Activity长,所以如果ViewModel持有了Activity引用,很容易造成内存泄漏。如果想在ViewModel中使用Application,可以使用ViewModel的子类AndroidViewModel,其在构造方法中需要传入了Application实例:public class AndroidViewModel extends ViewModel { private Application mApplication; public AndroidViewModel(@NonNull Application application) { mApplication = application; } public <T extends Application> T getApplication() { return (T) mApplication; } } ViewModel使用举例引入ViewModel,在介绍Jetpack系列文章Lifecycle时已经提过一次,这里再写一下:implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" 先看运行效果图:页面中有两个Fragment,左侧为列表,右侧为详情,当点击左侧某一个Item时,右侧会展示相应的数据,即两个Fragment可以通过ViewModel进行通信;同时可以看到,当屏幕发生旋转的时候,右侧详情页的数据并没有丢失,而是直接进行了展示。//ViewModelActivity.kt class ViewModelActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.e(JConsts.VIEW_MODEL, "onCreate") setContentView(R.layout.activity_view_model) } } 其中的XML文件://activity_view_model.xml <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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=".viewmodel.ViewModelActivity"> <fragment android:id="@+id/fragment_item" android:name="com.example.jetpackstudy.viewmodel.ItemFragment" android:layout_width="150dp" android:layout_height="match_parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/fragment_detail" app:layout_constraintTop_toTopOf="parent" /> <fragment android:id="@+id/fragment_detail" android:name="com.example.jetpackstudy.viewmodel.DetailFragment" android:layout_width="0dp" android:layout_height="match_parent" app:layout_constraintLeft_toRightOf="@+id/fragment_item" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> 直接将Fragment以布局方式写入我们的Activity中,继续看两个Fragment://左侧列表Fragment class ItemFragment : Fragment() { lateinit var mRvView: RecyclerView //Fragment之间通过传入同一个Activity来共享ViewModel private val mShareModel by lazy { ViewModelProvider(activity!!).get(ShareViewModel::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = LayoutInflater.from(activity) .inflate(R.layout.layout_fragment_item, container, false) mRvView = view.findViewById(R.id.rv_view) mRvView.layoutManager = LinearLayoutManager(activity) mRvView.adapter = MyAdapter(mShareModel) return view } //构造RecyclerView的Adapter class MyAdapter(private val sViewModel: ShareViewModel) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() { class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val mTvText: TextView = itemView.findViewById(R.id.tv_text) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val itemView = LayoutInflater.from(parent.context) .inflate(R.layout.item_fragment_left, parent, false) return MyViewHolder(itemView) } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val itemStr = "item pos:$position" holder.mTvText.text = itemStr //点击发送数据 holder.itemView.setOnClickListener { sViewModel.clickItem(itemStr) } } override fun getItemCount(): Int { return 50 } } } //右侧详情页Fragment class DetailFragment : Fragment() { lateinit var mTvDetail: TextView //Fragment之间通过传入同一个Activity来共享ViewModel private val mShareModel by lazy { ViewModelProvider(activity!!).get(ShareViewModel::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return LayoutInflater.from(context) .inflate(R.layout.layout_fragment_detail, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { mTvDetail = view.findViewById(R.id.tv_detail) //注册Observer并监听数据变化 mShareModel.itemLiveData.observe(activity!!, { itemStr -> mTvDetail.text = itemStr }) } } 最后贴一下我们的ViewModel://ShareViewModel.kt class ShareViewModel : ViewModel() { val itemLiveData: MutableLiveData<String> by lazy { MutableLiveData<String>() } //点击左侧Fragment中的Item发送数据 fun clickItem(infoStr: String) { itemLiveData.value = infoStr } } 这里再强调一下,两个Fragment中ViewModelProvider(activity).get()传入的是同一个Activity,从而得到的ViewModel是同一个实例,进而可以进行互相通信。上面使用ViewModel的写法有两个好处:屏幕切换时保存数据屏幕发生变化时,不需要重新请求数据,直接从ViewModel中再次拿数据即可。Fragment之间共享数据:Activity 不需要执行任何操作,也不需要对此通信有任何了解。除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。ViewModel与onSaveInstance(Bundle)对比ViewModel是将数据存到内存中,而onSaveInstance()是通过Bundle将序列化数据存在磁盘中ViewModel可以存储任何形式的数据,且大小不限制(不超过App分配的内存即可),onSaveInstance()中只能存储可序列化的数据,且大小一般不超过1M(IPC通信数据限制)源码解析ViewModel的存取我们在获取ViewModel实例时,并不是直接new出来的,而是通过ViewModelProvider.get()获取的,顾名思义,ViewModelProvider意为ViewModel提供者,那么先来看它的构造方法://ViewModelProvider.java public ViewModelProvider(@NonNull ViewModelStoreOwner owner) { this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance()); } public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) { this(owner.getViewModelStore(), factory); } public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { mFactory = factory; mViewModelStore = store; } ViewModelProvider构造函数中的几个参数:ViewModelStoreOwner:ViewModel存储器拥有者,用来提供ViewModelStoreViewModelStore:ViewModel存储器,用来存储ViewModelFactory:创建ViewModel的工厂private final ViewModelStore mViewModelStore; private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"; @MainThread public <T extends ViewModel> T get(@NonNull Class<T> modelClass) { String canonicalName = modelClass.getCanonicalName(); if (canonicalName == null) { throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels"); } //首先构造了一个key,直接调用下面的get(key,modelClass) return get(DEFAULT_KEY + ":" + canonicalName, modelClass); } @MainThread public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { //尝试从ViewModelStore中获取ViewModel ViewModel viewModel = mViewModelStore.get(key); if (modelClass.isInstance(viewModel)) { if (mFactory instanceof OnRequeryFactory) { ((OnRequeryFactory) mFactory).onRequery(viewModel); } //viewModel不为空直接返回该实例 return (T) viewModel; } else { //noinspection StatementWithEmptyBody if (viewModel != null) { // TODO: log a warning. } } if (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) mFactory).create(key, modelClass); } else { //viewModel为空,通过Factory创建 viewModel = mFactory.create(modelClass); } //将ViewModel保存到ViewModelStore中 mViewModelStore.put(key, viewModel); return (T) viewModel; } 逻辑很简单,首先尝试通过ViewModelStore.get(key)获取ViewModel,如果不为空直接返回该实例;如果为空,通过Factory.create创建ViewModel并保存到ViewModelStore中。先来看Factory是如何创建ViewModel的,ViewModelProvider构造函数中,如果没有传入Factory,那么会使用NewInstanceFactory://接口Factory public interface Factory { <T extends ViewModel> T create(@NonNull Class<T> modelClass); } //默认Factory的实现类NewInstanceFactory public static class NewInstanceFactory implements Factory { private static NewInstanceFactory sInstance; //获取单例 static NewInstanceFactory getInstance() { if (sInstance == null) { sInstance = new NewInstanceFactory(); } return sInstance; } @Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { try { //反射创建 return modelClass.newInstance(); } catch (InstantiationException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } catch (IllegalAccessException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } } } 可以看到NewInstanceFactory的实现很简单,直接通过传入的class反射创建实例,泛型中限制必须是ViewModel的子类,所以最终创建的是ViewModel实例。看完Factory,接着来看ViewModelStore:public class ViewModelStore { private final HashMap<String, ViewModel> mMap = new HashMap<>(); final void put(String key, ViewModel viewModel) { ViewModel oldViewModel = mMap.put(key, viewModel); //如果oldViewModel不为空,调用oldViewModel的onCleared释放资源 if (oldViewModel != null) { oldViewModel.onCleared(); } } final ViewModel get(String key) { return mMap.get(key); } Set<String> keys() { return new HashSet<>(mMap.keySet()); } //遍历存储的ViewModel并调用其clear()方法,然后清除所有ViewModel public final void clear() { for (ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); } } ViewModelStore类也很简单,内部就是通过Map进行存储ViewModel的。到这里,我们基本看完了ViewModel的存取,流程如下:ViewModelStore的存取上面聊了ViewModel的存取,有一个重要的点没有说到,既然ViewModel的生命周期比Activity长,而ViewModel又是通过ViewModelStore存取的,那么ViewModelStore又是如何存取的呢?在上面的流程图中我们知道ViewModelStore是通过ViewModelStoreOwner提供的://接口ViewModelStoreOwner.java public interface ViewModelStoreOwner { ViewModelStore getViewModelStore(); } ViewModelStoreOwner中接口方法getViewModelStore()返回的既是ViewModelStore。我们上面例子获取ViewModel时是ViewModelProvider(activity).get(ShareViewModel::class.java),其中的activity其实就是ViewModelStoreOwner,也就是Activity中实现了这个接口://ComponentActivity.java public class ComponentActivity extends androidx.core.app.ComponentActivity implements LifecycleOwner, ViewModelStoreOwner,.... { @Override public ViewModelStore getViewModelStore() { //Activity还未关联到Application时,会抛异常,此时不能使用ViewModel if (getApplication() == null) { throw new IllegalStateException("Your activity is not yet attached to the " + "Application instance. You can't request ViewModel before onCreate call."); } ensureViewModelStore(); return mViewModelStore; } void ensureViewModelStore() { if (mViewModelStore == null) { NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null) { //从NonConfigurationInstances中恢复ViewModelStore mViewModelStore = nc.viewModelStore; } if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); } } } @Override //覆写了Activity的onRetainNonConfigurationInstance方法,在Activity#retainNonConfigurationInstances()方法中被调用,即配置发生变化时调用。 public final Object onRetainNonConfigurationInstance() { Object custom = onRetainCustomNonConfigurationInstance(); ViewModelStore viewModelStore = mViewModelStore; if (viewModelStore == null) { //尝试从之前的存储中获取NonConfigurationInstance NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null) { //从NonConfigurationInstances中恢复ViewModelStore viewModelStore = nc.viewModelStore; } } if (viewModelStore == null && custom == null) { return null; } //如果viewModelStore不为空,当配置变化时将ViewModelStore保存到NonConfigurationInstances中 NonConfigurationInstances nci = new NonConfigurationInstances(); nci.custom = custom; nci.viewModelStore = viewModelStore; return nci; } //内部类NonConfigurationInstances static final class NonConfigurationInstances { Object custom; ViewModelStore viewModelStore; } } NonConfigurationInstances是ComponentActivity的静态内部类,里面包含了ViewModelStore。getViewModelStore()内部首先尝试通过getLastNonConfigurationInstance()来获取NonConfigurationInstances,不为空直接能拿到对应的ViewModelStore;否则直接new一个新的ViewModelStore跟进去getLastNonConfigurationInstance()这个方法://Activity.java public class Activity extends ContextThemeWrapper { NonConfigurationInstances mLastNonConfigurationInstances; public Object getLastNonConfigurationInstance() { return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null; } NonConfigurationInstances retainNonConfigurationInstances() { //onRetainNonConfigurationInstance实现在子类ComponentActivity中实现 Object activity = onRetainNonConfigurationInstance(); ....... if (activity == null && children == null && fragments == null && loaders == null && mVoiceInteractor == null) { return null; } NonConfigurationInstances nci = new NonConfigurationInstances(); nci.activity = activity; ...... return nci; } final void attach(Context context, ......., NonConfigurationInstances lastNonConfigurationInstances) { mLastNonConfigurationInstances = lastNonConfigurationInstances; } static final class NonConfigurationInstances { Object activity; ...... } } 可以看到getLastNonConfigurationInstance()中是通过mLastNonConfigurationInstances是否为空来判断的,搜索一下该变量赋值的地方,就找到了Activity#attach()方法。我们知道Activity#attach()是在创建Activity的时候调用的,顺藤摸瓜就可以找到了ActivityThread://ActivityThread.java final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); Activity.NonConfigurationInstances lastNonConfigurationInstances; //1、将NonConfigurationInstances存储到ActivityClientRecord ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); Class<? extends Activity> activityClass = null; if (r != null) { ...... if (getNonConfigInstance) { try { //retainNonConfigurationInstances r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); } catch (Exception e) { ...... } } } synchronized (mResourcesManager) { mActivities.remove(token); } return r; } //2、从ActivityClientRecord中获取NonConfigurationInstances private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ...... activity.attach(...., r.lastNonConfigurationInstances,....); } 在执行performDestroyActivity()的时候,会调用Activity#retainNonConfigurationInstances()方法将生成的NonConfigurationInstances赋值给lastNonConfigurationInstances。在performLaunchActivity()中又会通过Activity#attach()将lastNonConfigurationInstances赋值给Activity.mLastNonConfigurationInstances,进而取到ViewModelStore。ViewModelStore的存取都是间接在ActivityThread中进行并保存在ActivityClientRecord中。在Activity配置变化时,ViewModelStore可以在Activity销毁时得以保存并在重建时重新从lastNonConfigurationInstances中获取,又因为ViewModelStore提供了ViewModel,所以ViewModel也可以在Activity配置变化时得以保存,这也是为什么ViewModel的生命周期比Activity生命周期长的原因了。
0
0
0
浏览量387
IT大鲨鱼

JNI 编程上手指南之 JNIEnv 详解

1. JNIEnv 是什么JNIEnv 即 Java Native Interface Environment,Java 本地编程接口环境。JNIEnv 内部定义了很多函数用于简化我们的 JNI 编程。JNI 把 Java 中的所有对象或者对象数组当作一个 C 指针传递到本地方法中,这个指针指向 JVM 中的内部数据结构(对象用jobject来表示,而对象数组用jobjectArray或者具体是基本类型数组),而内部的数据结构在内存中的存储方式是不可见的。只能从 JNIEnv 指针指向的函数表中选择合适的 JNI 函数来操作JVM 中的数据结构。在 C 语言中,JNIEnv 是一个指向 JNINativeInterface_ 结构体的指针:#ifdef __cplusplus typedef JNIEnv_ JNIEnv; #else typedef const struct JNINativeInterface_ *JNIEnv; // C 语言 #endif struct JNINativeInterface_ { void *reserved0; void *reserved1; void *reserved2; void *reserved3; jint (JNICALL *GetVersion)(JNIEnv *env); jclass (JNICALL *DefineClass) (JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len); jstring (JNICALL *NewStringUTF) (JNIEnv *env, const char *utf); //省略其他函数指针 //...... } JNINativeInterface_ 结构体中定义了非常多的函数指针,这些函数用于简化我们的 JNI 编程。C 语言中,JNIEnv 中函数的使用方式如下://JNIEnv * env // env 的实际类型是 JNINativeInterface_** (*env)->NewStringUTF(env,"Hello from JNI !"); 在 C++ 代码中,JNIEnv 是一个 JNIEnv_ 结构体:#ifdef __cplusplus typedef JNIEnv_ JNIEnv; #else typedef const struct JNINativeInterface_ *JNIEnv; #endif struct JNIEnv_ { const struct JNINativeInterface_ *functions; #ifdef __cplusplus jint GetVersion() { return functions->GetVersion(this); } jclass DefineClass(const char *name, jobject loader, const jbyte *buf, jsize len) { return functions->DefineClass(this, name, loader, buf, len); } jclass FindClass(const char *name) { return functions->FindClass(this, name); } jmethodID FromReflectedMethod(jobject method) { return functions->FromReflectedMethod(this,method); } jfieldID FromReflectedField(jobject field) { return functions->FromReflectedField(this,field); } jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) { return functions->ToReflectedMethod(this, cls, methodID, isStatic); } jclass GetSuperclass(jclass sub) { return functions->GetSuperclass(this, sub); } //省略其他函数 //...... } JNIEnv_ 结构体中同样定义了非常多的成员函数,这些函数用于简化我们的 JNI 编程。C++ 语言中,JNIEnv 中函数的使用方式如下://JNIEnv * env // env 的实际类型是 JNIEnv_* env->NewstringUTF ( "Hello from JNI ! "); 2. 如何获取到 JNIEnv对于单线程的情况,我们可以直接通过 JNI 方法传入的参数获取到 JNIEnv// 第一个参数就是 JNIEnv JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) { return (*env)->NewStringUTF(env,"Hello from JNI !"); } 对于多线程的情况,首先我们要知道,JNIEnv 是一个线程作用域的变量,不能跨线程传递,不同线程的 JNIEnv 彼此独立。那如何在不同的线程中获取到 JNIEnv 呢://定义全局变量 //JavaVM 是一个结构体,用于描述 Java 虚拟机,后面会讲 JavaVM* gJavaVM; JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) { //线程不允许共用env环境变量,但是JavaVM指针是整个jvm共用的,所以可以通过下面的方法保存JavaVM指针,在线程中使用 env->GetJavaVM(&gJavaVM); return (*env)->NewStringUTF(env,"Hello from JNI !"); } //假设这是一个工具函数,可能被多个线程调用 void util_xxx() { JNIEnv *env; //从全局的JavaVM中获取到环境变量 gJavaVM->AttachCurrentThread(&env,NULL); //就可以使用 JNIEnv 了 //最后需要做清理操作 gJavaVM->DetachCurrentThread(); } 3. JNIEnv 内部函数分类JNIEnv 中定义的函数可以分为以下几类:函数名功能FindClass用于获取类GetObjectClass通过对象获取这个类NewGlobalRef创建 obj 参数所引用对象的新全局引用NewObject构造新 Java 对象NewString利用 Unicode 字符数组构造新的 java.lang.String 对象NewStringUTF利用 UTF-8 字符数组构造新的 java.lang.String 对象New<Type>Array创建类型为Type的数组对象Get<Type>Field获取类型为Type的字段Set<Type>Field设置类型为Type的字段的值GetStatic<Type>Field获取类型为Type的static的字段SetStatic<Type>Field设置类型为Type的static的字段的值Call<Type>Method调用返回类型为Type的方法CallStatic<Type>Method调用返回值类型为Type的static方法
0
0
0
浏览量673
IT大鲨鱼

Android中Callable、Future、FutureTask的概念以及几种线程池的使用

线程池必备知识在开始介绍线程池之前,先来介绍下Callable和Future的概念,众所周知,Android中实现多线程的方式有两种,实现Runnable接口或者继承一个Thread,但是这两种方式都有一个缺点:在任务执行完成之后没有返回结果,所以在Java 1.5之后,出现了Callable和Future,通过他们构建的线程,可以在线程执行完成之后得到返回结果。先来对比Runnable 和Callable:Runnablepublic interface Runnable { public abstract void run(); } Callablepublic interface Callable<V> { V call() throws Exception; }Runnable 接口中的run()方法的返回值是void,所以Runnable无返回值;而Callable接口中的call()方法的返回值是V,也可以抛出异常,所以Callable是可以有返回值的。接着来看下Future:Futurepublic interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }Future<V>用来获取异步计算的结果,即是获取Callable<V>任务的结果Future备注cancel(boolean)尝试取消异步任务的执行。如果任务已经执行完成、已经被取消、因为某种原因不能被取消,则返回false;如果任务正在执行,并且mayInterruptIfRunning为true,那么会调用interrupt()尝试打断任务。该方法返回结果后,isDone()总会返回trueisCancelled()如果在任务完成前被取消,返回trueisDone()如果任务完成则返回true。任务完成包括正常结束、任务被取消、任务发生异常,都返回trueget()获取异步任务执行结果,如果没有返回,则阻塞等待get(long timeout, TimeUnit unit)在给定的时间内等待获取异步任务结果,如果超时还未获取到结果,则会抛出TimeoutExceptionFuture<V>只是一个接口,还需要看Future的具体实现类:以FutureTask为例,具体分析下FutureTask:public class FutureTask<V> implements RunnableFuture<V> { ................其他..................... }咦?FutureTask并没有实现Future接口,而是实现了一个叫RunnableFuture的接口,这货从哪里蹦出来的?我们点进去看一下:public interface RunnableFuture<V> extends Runnable, Future<V> { /** * Sets this Future to the result of its computation * unless it has been cancelled. */ void run();}吆西!原来这货不仅继承了Future接口,还继承了Runnable接口(PS:接口可以多继承),那么我们的FutureTask也就实现了Runnable和Future接口FutureTask备注boolean isCancelled()同Futureboolean isDone()同Futureboolean cancel(boolean mayInterruptIfRunning)同FutureV get()同FutureV get(long timeout, TimeUnit unit)同Futurevoid run()如果这个任务没有被取消,将直接在当前线程内执行任务FutureTask有两个构造方法:public FutureTask(Callable<V> callable)public FutureTask(Runnable runnable, V result)我们看到FutureTask初始化时不仅可以传入Callable,还可以传入一个Runnable和一个"返回结果result",这里为什么返回结果要加引号呢,来看下源码就知道了:public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable}上面FutureTask()的初始化构造参数中调用了Executors.callable():public static <T> Callable<T> callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); return new RunnableAdapter<T>(task, result);}private static final class RunnableAdapter<T> implements Callable<T> { private final Runnable task; private final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); //把传入的值直接返回 return result; }}通过源码我们看到,在最后的call()方法中,直接把传入的值返回了,所以FutureTask(Runnable runnable, V result)得到的只是预设的结果,Runnable并不会得到执行的结果。如果不需要预设的返回结果,可以像下面这样初始化:Future<?> f = new FutureTask<Void>(runnable, null)FutureTask的使用: //初始化一个线程池 ExecutorService executor = = Executors.newSingleThreadExecutor(); //new 一个Callable并传入FutureTask FutureTask<String> future =new FutureTask<>(new Callable<String>() { public String call() { //do something return result; }}); executor.execute(future); //在异步任务执行期间可以做一些其他的事情 displayOtherThings(); //通过future.get()得到异步任务执行结果 String result=future.get();ExecutorServiceExecutorService继承自Executor接口,并提供了管理线程以及创建可以追踪一个或多个异步任务的进展的Future的方法。public interface ExecutorService extends Executor { //无法提交新任务,但是已经提交的任务将继续执行,当执行完成后关闭线程池 void shutdown(); //尝试停止所有正在执行的任务,暂停等待任务的处理,并返回等待执行的任务列表。 List<Runnable> shutdownNow(); //如果线程池已经关闭则返回true boolean isShutdown(); //如果所有任务都在线程池关闭后完成,则返回true。注意,除非首先调用 shutdown 或 shutdownNow,否则 isTerminated 永不为 true。 boolean isTerminated(); //阻塞等待,直到所有任务在关闭请求后完成执行,或者超时发生,或者当前线程被中断 // 如果此执行程序终止,则返回 true;如果终止前超时了,则返回 false boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; <T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); //提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功 完成时将会返回 null Future<?> submit(Runnable task); <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; //执行给定的任务,当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的 Future 列表。 //返回列表的所有元素的 Future.isDone() 为 true。一旦返回后,即取消尚未完成的任务。 //注意,可以正常地或通过抛出异常来终止已完成 任务。如果此操作正在进行时修改了给定的 // collection,则此方法的结果是不确定的。 <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; //执行给定的任务,如果在给定的超时期满前某个任务已成功完成(也就是未抛出异常),则返回其结果。 //一旦正常或异常返回后,则取消尚未完成的任务。如果此操作正在进行时修改了给定的 collection, //则此方法的结果是不确定的。 <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }线程池的使用使用线程池的好处:· 当执行大量异步任务时,线程池能提供更好的性能体验,因为线程池能减少每个任务的调用开销,重用存在的线程,减少对象创建、消亡的开销· 还可以提供绑定和管理资源 (包括执行任务时使用的线程) 的方法。有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞· 提供定时执行、定期执行、单线程、并发数控制等功能。Android中提供了四种线程池,四种线程池内部实现都是直接或者间接用ThreadPoolExecutor。Executors.newCachedThreadPool()只有Integer.MAX_VALUE个非核心线程,当有任务来时,如果线程池中的线程都处于活动状态,那么会新建线程来执行,否则就会利用空闲线程去执行,空闲线程都会有一个超时机制,超过60秒的空闲线程会被回收。任务队列为空集合,所以所有任务都会被立即执行,CachedThreadPool适合执行大量的耗时较少的操作。效果图: 因为非核心线程数有无限个,所以不管有多少任务都可以并行执行,可以看到上述5个任务一起执行,核心代码:ini复制代码//初始化线程池ExecutorService threadPool= Executors.newCachedThreadPool();//初始化CallableCallable<Integer> callable = new Callable<Integer>() {    int num = 0;    @Override    public Integer call() throws Exception {        while (num < 100) {            num++;            sendMsg(num, what);            Thread.sleep(50);        }        return 100;    } };//执行线程任务threadPool.submit(callable);Executors.newFixedThreadPool(int nThreads)只有核心线程并且不会被回收,任务队列没有大小限制。效果图: 因为核心线程数参数我们传入的是4,可所以看到先执行其中的4个任务,等待有任务执行完成后接着去执行第5个任务,核心代码://初始化线程池ExecutorService threadPool= Executors.newFixedThreadPool(4);//初始化CallableCallable<Integer> callable = new Callable<Integer>() { int num = 0; @Override public Integer call() throws Exception { while (num < 100) { num++; sendMsg(num, what); Thread.sleep(50); } return 100; } };//执行线程任务threadPool.submit(callable);Executors.newSingleThreadExecutor()内部只有一个核心线程,所有任务按顺序执行 统一所有任务到一个线程中,使得这些任务不用处理线程同步问题。效果图: 可以看到每次只能执行一个任务,也就是所有任务都是串行执行的,核心代码://初始化线程池ExecutorService threadPool= Executors.newSingleThreadExecutor();//初始化CallableCallable<Integer> callable = new Callable<Integer>() { int num = 0; @Override public Integer call() throws Exception { while (num < 100) { num++; sendMsg(num, what); Thread.sleep(50); } return 100; } };//执行线程任务threadPool.submit(callable);Executors.newScheduledThreadPool(int corePoolSize)核心线程是固定的,非核心线程是不固定的,非核心线程闲置时会被立即回收,主要用于执行定时任务和具有周期性的重复任务。效果图: ScheduledExecutorService传入的核心线程数是4,并且是在延迟2秒之后执行的,核心代码://初始化线程池,核心线程数为4ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(4);//初始化CallableCallable<Integer> callable = new Callable<Integer>() { int num = 0; @Override public Integer call() throws Exception { while (num < 100) { num++; sendMsg(num, what); Thread.sleep(50); } return 100; }};//延迟2秒后执行scheduledPool.schedule(callable, 2, TimeUnit.SECONDS);除上述延迟执行的方法外,ScheduledExecutorService中还有下列方法:public interface ScheduledExecutorService extends ExecutorService { //延迟delay(TimeUnit 为单位)之后执行Runnable public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit); //延迟delay(TimeUnit 为单位)之后执行Callable public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit); //延迟initialDelay之后每period时间执行一次Callable,循环执行 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); //第一次延迟initialDelay之后执行,之后在每次完成后延迟delay时间执行下一次操作 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit); }
0
0
0
浏览量2008
IT大鲨鱼

JNI 编程上手指南之异常处理

JNI 程序中的异常分为以下几种:Native 程序原生异常,一般通过函数返回值和 linux 信号处理, C++ 中也有 try catch 机制解决异常,不是本文重点JNIEnv 内部函数抛出的异常,一般通过返回值判断,发现异常直接 return, jvm 会给将异常传递给 Java 层Native 回调 Java 层方法,被回调的方法抛出异常,JNI 提供了特定的 API 来处理这类异常1. JNIEnv 内部函数抛出的异常很多 JNIEnv 中的函数都会抛出异常,处理方法大体上是一致的:返回值与特殊值(一般是 NULL)比较,知晓函数是否发生异常如果发生异常立即 returnjvm 会将异常抛给 java 层,我们可以在 java 层通过 try catch 机制捕获异常接着我们来看一个例子:Java 层:public native void exceptionTest(); //调用 try { exceptionTest(); } catch (Exception e) { e.printStackTrace(); } Native 层:extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_exceptionTest(JNIEnv *env, jobject thiz) { //查找的类不存在,返回 NULL; jclass clazz = env->FindClass("com/yuandaima/myjnidemo/xxx"); if (clazz == NULL) { return; //return 后,jvm 会向 java 层抛出 ClassNotFoundException } } 执行后的 log:java.lang.ClassNotFoundException: Didn't find class "com.yuandaima.myjnidemo.xxx"说明,java 层捕获到了异常2. Native 回调 Java 层方法,被回调的方法抛出异常Native 回调 Java 层方法,被回调的方法抛出异常。这样情况下一般有两种解决办法:Java 层 Try catch 本地方法,这是比较推荐的办法。Native 层处理异常,异常处理如果和 native 层相关,可以采用这种方式2.1 Java 层 Try catch 本地方法Java 层://执行这个方法会抛出异常 private static int exceptionMethod() { return 20 / 0; } //native 方法,在 native 中,会调用到 exceptionMethod() 方法 public native void exceptionTest(); //Java 层调用 try { exceptionTest(); } catch (Exception e) { //这里处理异常 //一般是打 log 和弹 toast 通知用户 e.printStackTrace(); } Native 层:extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_exceptionTest(JNIEnv *env, jobject thiz) { jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass"); if (clazz == NULL) { return; } //调用 java 层会抛出异常的方法 jmethodID static_method_id = env->GetStaticMethodID(clazz, "exceptionMethod", "()I"); if (NULL == static_method_id) { return; } //直接调用,发生 ArithmeticException 异常,传回 Java 层 env->CallStaticIntMethod(clazz, static_method_id); env->DeleteLocalRef(clazz); }2.2 Native 层处理异常有的异常需要在 Native 处理,这里又分为两类:异常在 Native 层就处理完了异常在 Native 层处理了,还需要返回给 Java 层,Java 层继续处理接着我们看下示例:Java 层://执行这个方法会抛出异常 private static int exceptionMethod() { return 20 / 0; } //native 方法,在 native 中,会调用到 exceptionMethod() 方法 public native void exceptionTest(); //Java 层调用 try { exceptionTest(); } catch (Exception e) { //这里处理异常 //一般是打 log 和弹 toast 通知用户 e.printStackTrace(); }Native 层:extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_exceptionTest(JNIEnv *env, jobject thiz) { jthrowable mThrowable; jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass"); if (clazz == NULL) { return; } jmethodID static_method_id = env->GetStaticMethodID(clazz, "exceptionMethod", "()I"); if (NULL == static_method_id) { return; } env->CallStaticIntMethod(clazz, static_method_id); //检测是否有异常发生 if (env->ExceptionCheck()) { //获取到异常对象 mThrowable = env->ExceptionOccurred(); //这里就可以根据实际情况处理异常了 //....... //打印异常信息堆栈 env->ExceptionDescribe(); //清除异常信息 //如果,异常还需要 Java 层处理,可以不调用 ExceptionClear,让异常传递给 Java 层 env->ExceptionClear(); //如果调用了 ExceptionClear 后,异常还需要 Java 层处理,我们可以抛出一个新的异常给 Java 层 jclass clazz_exception = env->FindClass("java/lang/Exception"); env->ThrowNew(clazz_exception, "JNI抛出的异常!"); env->DeleteLocalRef(clazz_exception); } env->DeleteLocalRef(clazz); env->DeleteLocalRef(mThrowable); }
0
0
0
浏览量2012
IT大鲨鱼

Android Jetpack系列之LiveData

LiveData介绍LiveData是一种可观察的数据存储类。LiveData 具有生命周期感知能力,遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的Observer,非活跃状态下的Observer不会受到通知。生命周期状态可以通过Lifecycle提供,包括DESTROYED、INITIALIZED、CREATED、STARTED、RESUMED,当且仅当生命周期处于STARTED、RESUMED时为活跃状态,其他状态是非活跃状态。LiveData优点确保界面符合数据状态LiveData 遵循观察者模式。当数据发生变化时,LiveData 会通知 Observer 对象,那么Observer回调的方法中就可以进行UI更新,即数据驱动。不会发生内存泄漏观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁(如Activity进入ONDESTROY状态)后进行自我清理。不会因 Activity 停止而导致崩溃如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。不再需要手动处理生命周期界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。数据始终保持最新状态如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。配置更改时自动保存数据如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。共享资源使用单例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象。LiveData使用举例基础用法先上效果图:在Activity中动态添加了一个Fragment,点击按钮产生一个1000以内的随机值,并通过LiveData.setValue发送出去,在Fragment中通过LiveData.observe进行数据观察与接收,可以看到即使Activity中先发送的数据,Fragment中滞后注册观察者依然能收到数据,即LiveData发送的是粘性事件。首先需要保证Activity和Fragment中的LiveData是同一个实例://LiveDataInstance.kt 使用object来声明单例模式 object LiveDataInstance { //MutableLiveData是抽象类LiveData的具体实现类 val INSTANCE = MutableLiveData<String>() } Activity中随机生成一个数并通过LiveData.setValue进行发送://LiveDataActivity.kt class LiveDataActivity : AppCompatActivity() { lateinit var mTextView: TextView var mFragment: LiveDataFragment? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_live_data) mTextView = findViewById(R.id.tv_text) addLiveDataFragment() } fun updateValue(view: View) { sendData(produceData()) } //随机更新一个整数 private fun produceData(): String { val randomValue = (0..1000).random().toString() mTextView.text = "Activity中发送:$randomValue" return randomValue } //通过setValue发送更新 private fun sendData(randomValue: String) { LiveDataInstance.INSTANCE.value = randomValue } //添加Fragment fun addFragment(view: View) { addLiveDataFragment() } //移除Fragment fun removeFragment(view: View) { delLiveDataFragment() } private fun addLiveDataFragment() { val fragment = supportFragmentManager.findFragmentById(R.id.fl_content) if (fragment != null) { Toast.makeText(this, "请勿重复添加", Toast.LENGTH_SHORT).show() return } if (mFragment == null) { mFragment = LiveDataFragment.newInstance() } supportFragmentManager .beginTransaction() .add(R.id.fl_content, mFragment!!) .commitAllowingStateLoss() } private fun delLiveDataFragment() { val fragment = supportFragmentManager.findFragmentById(R.id.fl_content) if (fragment == null) { Toast.makeText(this, "没有Fragment", Toast.LENGTH_SHORT).show() return } supportFragmentManager.beginTransaction().remove(fragment).commitAllowingStateLoss() } } Fragment动态添加到Activity中,并通过LiveData.observe注册观察者并监听数据变化://LiveDataFragment.kt class LiveDataFragment : Fragment() { lateinit var mTvObserveView: TextView //数据观察者 数据改变时在onChange()中进行刷新 private val changeObserver = Observer<String> { value -> value?.let { Log.e(JConsts.LIVE_DATA, "observer:$value") mTvObserveView.text = value } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.live_data_fragment, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) mTvObserveView = view.findViewById(R.id.tv_observe) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) //通过LiveData.observe注册观察者,监听数据变化 LiveDataInstance.INSTANCE.observe(this, changeObserver) } companion object { fun newInstance() = LiveDataFragment() } } 上面就是一个LiveData的基本用法了,很简单,Activity/Fragment中共用LiveData实例,在Activity中通过点击按钮生成一个随机数并通过LiveData.setValue发送数据(如果在子线程中发送,需要使用postValue),然后Fragment中通过LiveData.observe注册观察者并监听数据变化。改一下代码: //LiveDataActivity.kt override fun onStop() { super.onStop() val data = produceData() Log.e(JConsts.LIVE_DATA, "onStop():$data") sendData(data) } //LiveDataFragment.kt private val changeObserver = Observer<String> { value -> value?.let { Log.e(JConsts.LIVE_DATA, "observer:$value") mTvObserveView.text = value } } 点击Home键,Activity的onStop会触发,并通过LiveData.setValue发送数据,看下打印日志:2021-07-13 17:07:08.662 1459-1459/com.example.jetpackstudy E/LIVEDATA: onStop():742 Activity中在onStop中重新生成了一个随机值并发送了出去,但是在Fragment中的Observer中并没有收到数据,这是为什么呢?还记得LiveData的能力吗,它是能感知生命周期的,并且只会更新处于活跃状态下的Observer(STARTED、RESUMED状态),所以在onStop中发送的事件,Fragment作为观察者已经不在活跃状态下了,并不会收到通知,当我们App切回前台时,Observer重新回到活跃状态,所以会收到Activity之前发送的事件:2021-07-13 17:12:47.433 5850-5850/com.example.jetpackstudy E/LIVEDATA: observer:742 如果想让Observer不管在什么状态下都能马上收到数据变化的通知,可以使用LiveData.observeForever来注册并监听数据变化: //LiveDataFragment.kt private val changeObserver = Observer<String> { value -> value?.let { Log.e(JConsts.LIVE_DATA, "observer:$value") mTvObserveView.text = value } } //observeForever不管Observer是否处于活跃状态都会立马相应数据变化 //注意这里只需要传一个Observer即可,不需要传入LifecycleOwner,因为不需要考虑Observer是否处于活跃状态 LiveDataInstance.INSTANCE.observeForever(changeObserver) override fun onDestroy() { super.onDestroy() //需要手动移除观察者 LiveDataInstance.INSTANCE.observeForever(changeObserver) } 上述代码重新实验,点击Home键将App切到后台:2021-07-13 17:29:56.848 15679-15679/com.example.jetpackstudy E/LIVEDATA: onStop():878 2021-07-13 17:29:56.849 15679-15679/com.example.jetpackstudy E/LIVEDATA: observer:878 可以看到通过LiveData.observeForever注册的Observer即使不在活跃状态也是会立马相应数据变化的,这里要注意一点,LiveData.observeForever注册的Observer并不会自动解除注册,需要我们手动处理。进阶用法Transformations.map()修改数据源先上效果图://LiveDataFragment.kt //数据观察者 数据改变时在onChange()中进行刷新 private val changeObserver = Observer<String> { value -> value?.let { Log.e(JConsts.LIVE_DATA, "transform observer:$value") mTvObserveView.text = value } } //Transformations.map()改变接收的data val transformLiveData = Transformations.map(LiveDataInstance.INSTANCE) { "Transform:$it" } //观察者监听的时候传入了LifecycleOwner 用以监听生命周期变化 transformLiveData.observe(this, changeObserver) 可以看到在Activity中发送的数据源是“xxx”,Fragment中经过Transformations.map变换后变成"Transform:xxx",通过Transformations.map()可以对接收的数据源进行修改。Transformations.switchMap()切换数据源//LiveDataInstance.kt object LiveDataInstance { val INSTANCE = MutableLiveData<String>() val INSTANCE2 = MutableLiveData<String>() val SWITCH = MutableLiveData<Boolean>() } 注:一般LiveData都是与ViewModel结合使用的,本文主要介绍LiveData,所以使用了单例//LiveDataActivity.kt mBtnSwitch = findViewById(R.id.btn_switch) mBtnSwitch.setOnCheckedChangeListener { _, isChecked -> //发送开关状态 用以在Transformations.switchMap中切换数据源 LiveDataInstance.SWITCH.value = isChecked } //通过setValue发送更新 private fun sendData(randomValue: String, isLeft: Boolean) { if (isLeft) { LiveDataInstance.INSTANCE.value = randomValue } else { LiveDataInstance.INSTANCE2.value = randomValue } } //LiveDataFragment.kt //数据观察者 数据改变时在onChange()中进行刷新 private val changeObserver = Observer<String> { value -> value?.let { Log.e(JConsts.LIVE_DATA, "transform observer:$value") mTvObserveView.text = value } } //Transformations.switchMap()切换数据源 val switchMapLiveData = Transformations.switchMap(LiveDataInstance.SWITCH) { switchRight -> if (switchRight) { LiveDataInstance.INSTANCE2 } else { LiveDataInstance.INSTANCE } } switchMapLiveData.observe(this, changeObserverTransform) 例子中有两个数据源:LiveDataInstance.INSTANCE、LiveDataInstance.INSTANCE2,当Switch开关切换时,通过Transformations.switchMap()可以来回切换数据源,Observer中也会更新对应的数据。源码解析发送数据setValue/postValue//LiveData.java //setValue发送数据,只能在主线程中使用 protected void setValue(T value) { assertMainThread("setValue"); mVersion++; mData = value; dispatchingValue(null); } //postValue发送数据,可以在子线程中使用 protected void postValue(T value) { boolean postTask; synchronized (mDataLock) { postTask = mPendingData == NOT_SET; mPendingData = value; } if (!postTask) { return; } ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); } private final Runnable mPostValueRunnable = new Runnable() { @SuppressWarnings("unchecked") @Override public void run() { Object newValue; synchronized (mDataLock) { newValue = mPendingData; mPendingData = NOT_SET; } setValue((T) newValue); } }; 可以看到setValue/postValue都可以发送数据,区别是postValue还可以在子线程中发送数据,本质上postValue通过Handler将事件发送到Main线程中,最终也是调用了setValue发送事件,所以只看setValue()方法,该方法最后调用了dispatchingValue()方法并传入了一个参数null,继续看该方法: void dispatchingValue(@Nullable ObserverWrapper initiator) { if (mDispatchingValue) { mDispatchInvalidated = true; return; } mDispatchingValue = true; do { mDispatchInvalidated = false; if (initiator != null) { //2、通过observe()的方式会调用这里 considerNotify(initiator); initiator = null; } else { //1、通过setValue/postValue的方式会调用这里,遍历所有观察者并进行分发 for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) { considerNotify(iterator.next().getValue()); if (mDispatchInvalidated) { break; } } } } while (mDispatchInvalidated); mDispatchingValue = false; } private void considerNotify(ObserverWrapper observer) { if (!observer.mActive) { //观察者不在活跃状态 直接返回 return; } //如果是observe(),则是在STARTED、RESUMED状态时活跃;如果是ObserveForever(),则认为一直是活跃状态 if (!observer.shouldBeActive()) { observer.activeStateChanged(false); return; } //Observer中的Version必须小于LiveData中的Version,防止重复发送 if (observer.mLastVersion >= mVersion) { return; } observer.mLastVersion = mVersion; //回调Observer的onChange方法并接收数据 observer.mObserver.onChanged((T) mData); } 因为传入的参数是null,所以最终走到了1处,遍历所有的观察者并回调Observer的onChange方法接收数据,这样就完成了一次数据的传递。2处是单独调用一个观察者并回调其onChange方法接收数据,是执行observe()方法的时候执行的,具体等后面分析。注册观察者Observer并监听数据变化LiveData.observe()@MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) { assertMainThread("observe"); if (owner.getLifecycle().getCurrentState() == DESTROYED) { //如果当前观察者处于DESTROYED状态,直接返回 return; } //将LifecycleOwner、Observer包装成LifecycleBoundObserver LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); //ObserverWrapper是LifecycleBoundObserver的父类 ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); //如果mObservers中存在该Observer且跟传进来的LifecycleOwner不同,直接抛异常,一个Observer只能对应一个LifecycleOwner if (existing != null && !existing.isAttachedTo(owner)) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } //如果已经存在Observer且跟传进来的LifecycleOwner是同一个,直接返回 if (existing != null) { return; } //通过Lifecycle添加观察者 owner.getLifecycle().addObserver(wrapper); } 可以看到最后observe()将Observer加入到Lifecycle里去了,并通过onStateChanged()回调来监听LifecycleOwner生命周期的变化,主要看onStateChanged()方法:class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver { @NonNull final LifecycleOwner mOwner; LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) { super(observer); mOwner = owner; } @Override boolean shouldBeActive() { return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED); } @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { //Observer对应的LifecycleOwner是DESTROYED状态,直接删除该Observer,所以LiveData有自动解除Observer的能力 if (mOwner.getLifecycle().getCurrentState() == DESTROYED) { removeObserver(mObserver); return; } // activeStateChanged(shouldBeActive()); } @Override boolean isAttachedTo(LifecycleOwner owner) { return mOwner == owner; } @Override void detachObserver() { mOwner.getLifecycle().removeObserver(this); } } //ObserverWrapper.java void activeStateChanged(boolean newActive) { if (newActive == mActive) { return; } mActive = newActive; boolean wasInactive = LiveData.this.mActiveCount == 0; LiveData.this.mActiveCount += mActive ? 1 : -1; if (wasInactive && mActive) { //观察者数量从0变为1时 onActive(); } if (LiveData.this.mActiveCount == 0 && !mActive) { //观察者数量从1变为0时 onInactive(); } if (mActive) { //观察者为活跃状态,进行分发 dispatchingValue(this); } } onActive()在观察者数量从0变为1时执行;onInactive()在观察者数量从1变为0时执行。最后如果当前观察者是活跃状态,直接执行dispatchingValue(this),this是当前ObserverWrapper对象,还记得dispatchingValue()方法吗,前面讲这个方法的时候留了个疑问,这里就会执行前面讲的该方法里2处的代码,用以分发事件并在Observer的onChange()方法里接收并处理事件。LiveData.observeForever()@MainThread public void observeForever(@NonNull Observer<? super T> observer) { assertMainThread("observeForever"); AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); if (existing instanceof LiveData.LifecycleBoundObserver) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing != null) { return; } wrapper.activeStateChanged(true); } observeForever()中不需要传LifecycleOwner参数,因为observeForever()认为是一直活跃的状态,所以不需要监听LifecycleOwner的生命周期,最后是直接执行了wrapper.activeStateChanged(true)方法,后续的逻辑跟上面observe()一样了。这里注意一点,observeForever()注册的观察者当处于DESTROYED的时候并不会自动删除,需要手动删除之。LiveData实现类MutableLiveDatapublic class MutableLiveData<T> extends LiveData<T> { public MutableLiveData(T value) { super(value); } public MutableLiveData() { super(); } @Override public void postValue(T value) { super.postValue(value); } @Override public void setValue(T value) { super.setValue(value); } } MutableLiveData是抽象类LiveData的具体实现类。数据切换/修改 Transformations.map()/switchMap()//Transformations.java public static <X, Y> LiveData<Y> map( @NonNull LiveData<X> source, @NonNull final Function<X, Y> mapFunction) { final MediatorLiveData<Y> result = new MediatorLiveData<>(); result.addSource(source, new Observer<X>() { @Override public void onChanged(@Nullable X x) { result.setValue(mapFunction.apply(x)); } }); return result; } 从源码的注释上,看到了这么一句话This method is analogous to {@link io.reactivex.Observable#map},哦,原来用法是跟RxJava中的Map操作符类似。第一个参数是源LiveData< X>,第2个参数是个Funtion< X,Y>,目的就是将LiveData< X>变换为LiveData< Y>,然后再重新发送事件。map()方法里new了一个MediatorLiveData并执行了addSource()方法,看看这个方法怎么实现的://MediatorLiveData.java public class MediatorLiveData<T> extends MutableLiveData<T> { private SafeIterableMap<LiveData<?>, Source<?>> mSources = new SafeIterableMap<>(); @MainThread public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) { //将源LiveData及Observer包装成Source Source<S> e = new Source<>(source, onChanged); Source<?> existing = mSources.putIfAbsent(source, e); //如果源LiveData中已经有Observer且跟传进来的不一致,直接抛异常 if (existing != null && existing.mObserver != onChanged) { throw new IllegalArgumentException( "This source was already added with the different observer"); } if (existing != null) { return; } if (hasActiveObservers()) { //判断有活跃观察者时 e.plug(); } } @MainThread public <S> void removeSource(@NonNull LiveData<S> toRemote) { Source<?> source = mSources.remove(toRemote); if (source != null) { source.unplug(); } } private static class Source<V> implements Observer<V> { final LiveData<V> mLiveData; final Observer<? super V> mObserver; int mVersion = START_VERSION; Source(LiveData<V> liveData, final Observer<? super V> observer) { mLiveData = liveData; mObserver = observer; } void plug() { //通过observeForever添加观察者,有变动时就会回调下面的onChange()方法 mLiveData.observeForever(this); } void unplug() { mLiveData.removeObserver(this); } @Override public void onChanged(@Nullable V v) { if (mVersion != mLiveData.getVersion()) { mVersion = mLiveData.getVersion(); mObserver.onChanged(v); } } } } 首先将源LiveData及Observer包装成Source,经过了几次判断,最后执行到了Source#plug()方法,里面通过observeForever添加观察者,有变动时就会回调Source#onChange()方法,而这个方法里又会回调传进来的Observer#onChange()方法,即执行到了map()中传入的Observer的onChange()方法,里面通过setValue发送了转换之后的数据格式,这样就完成了整个的数据转换格式。那么再看switchMap()就简单了:public static <X, Y> LiveData<Y> switchMap( @NonNull LiveData<X> source, @NonNull final Function<X, LiveData<Y>> switchMapFunction) { final MediatorLiveData<Y> result = new MediatorLiveData<>(); result.addSource(source, new Observer<X>() { LiveData<Y> mSource; @Override public void onChanged(@Nullable X x) { LiveData<Y> newLiveData = switchMapFunction.apply(x); if (mSource == newLiveData) { return; } if (mSource != null) { result.removeSource(mSource); } mSource = newLiveData; if (mSource != null) { result.addSource(mSource, new Observer<Y>() { @Override public void onChanged(@Nullable Y y) { result.setValue(y); } }); } } }); return result; } 可以看到switchMap()中实现方式跟map()基本一致,只不过map()改变的是数据,而switchMap()改变的是数据源,可以对数据源进行切换。Transformations还有个distinctUntilChanged方法简单看一下:public static <X> LiveData<X> distinctUntilChanged(@NonNull LiveData<X> source) { final MediatorLiveData<X> outputLiveData = new MediatorLiveData<>(); outputLiveData.addSource(source, new Observer<X>() { boolean mFirstTime = true; @Override public void onChanged(X currentValue) { final X previousValue = outputLiveData.getValue(); if (mFirstTime || (previousValue == null && currentValue != null) || (previousValue != null && !previousValue.equals(currentValue))) { mFirstTime = false; outputLiveData.setValue(currentValue); } } }); return outputLiveData; } 也很简单,只有当数据源发生改变时,Observer才会相应,即发送重复的数据时,除第一次之外的数据都会被忽略。最后画一下类图:
0
0
0
浏览量1374
IT大鲨鱼

Kotlin | 搞定泛型使用(in、out、上下界、通配符、获取泛型类型等)

泛型类(或接口) & 泛型方法泛型,通俗解释就是具体的类型泛化,或者说是类型参数化。多用在集合中(如List、Map),编码时使用符号代替,在使用时再确定具体类型。泛型通常用于类和方法中,称为泛型类、泛型方法,使用示例:/** * 泛型类 */abstract class BaseBook<T> { private var books: ArrayList<T> = ArrayList() /** * 泛型方法 */ fun <E : T> add(item: E) { books.add(item) println("list:$books, size:${books.size}") }}/** * 子类继承BaseBook并传入泛型参数MathBook */class BookImpl : BaseBook<MathBook>()fun main() { BookImpl().apply { add(MathBook("数学")) }} 执行main()方法,输出:list:[MathBook(math=数学)], size: 1型变、协变、逆变在继续之前,先来看泛型相关的三个定义,分别是不变、协变、逆变,他们统称为型变:在泛型中,型变指的是子类型和超类型之间的关系中,泛型类型参数是否具有相同的变化方向。Java和Kotlin中支持通常的型变方式:协变和逆变,与不变对应。· 不变:有继承关系的两个类,在变成泛型类型时不再有关系。如:MathBook是Book的子类,而BaseBook<MathBook>与BaseBook<Book>就没关系了,是不同对象。· 协变:如果想让BaseBook<MathBook>与BaseBook<Book>继续有父子关系,即想继续支持协变,在Java中使用? extends E表示;Kotlin中使用out E,表示上界是E。那么BaseBook<MathBook>继续是BaseBook<Book>的子类。· 逆变:与协变相反,有继承关系的两个类,在逆变之后,关系反过来了。Java中使用? super E,Kotlin中使用in E,表示下界为E。Java泛型? extends E 定义上界Java中的泛型是不协变的,举个例子:Integer是Object的子类,但是List<Integer>并不是List<Object>的子类,因为List是不协变的。如果想让List<Integer>成为List<Object>的子类,可以通过上界操作符 ? extends E 来操作。? extends E 表示此方法接受 E 或者 E 的 一些子类型对象的集合,而不只是 E 自身。extends操作符可以限定上界通配符类型,使得通配符类型是协变的。注意,经过协变之后,数据是可读不可写的。示例://继承关系Child -> Parent class Parent{ protected String name = "Parent";}class Child extends Parent { protected String name = "Child";}首先定义了实体类,继承关系:Child 继承自 Parentclass CList<E> { //通过<? extends E>来定义上界 public void addAll(List<? extends E> list) { //... }}/** * <? extends E>来定义上界,可以保证协变性 */ public void GExtends() { //1、Child是Parent的子类 Parent parent = new Child(); //2、协变,泛型参数是Parent CList<Parent> objs = new CList<>(); List<Child> strs = new ArrayList<>(); //声明字符串List strs.add(new Child()); objs.addAll(strs); //addAll()方法中的入参必须为List<? extends E>,从而保证了List<Child>是List<Parent>的子类。}addAll()方法中的入参必须为List<? extends E>,从而保证了List<Child>是List<Parent>的子类。如果addAll()中的入参改为List<E>,则编译器会直接报错,因为List<Child>并不是List<Parent>的子类,如下: ? super E 定义下界? super E 可以看作一个E或者E父类的“未知类型”,这里的父类包括直接和间接父类。super定义泛型的下界,使得通配符类型是逆变的。经过逆变之后,数据是可写不可读的,如: List<? super Child> 是 List<Parent> 的一个超类。示例:class CList<E> { //通过<? super E>来定义下界 public void popAll(List<? super E> dest) { //... } /** * 逆变性 */ public void GSuper(){ CList<Child> objs = new CList<>(); List<Parent> parents = new ArrayList<>(); //声明字符串List parents.add(new Parent()); objs.popAll(parents); //逆变 }可以看到popAll()的入参必须声明为List<? super E>,如果改为List<E>,编译器会直接报错: Kotlin泛型和 Java 一样,Kolin 泛型本身也是不能协变的。· 使用关键字 out 来支持协变,等同于 Java 中的上界通配符 ? extends T。· 使用关键字 in 来支持逆变,等同于 Java 中的下界通配符 ? super T。声明处协变协变< out T>interface GenericsP<T> { fun get(): T //读取并返回T,可以认为只能读取T的对象是生产者}如上声明了GenericsP< T>接口,如果其内部只能读取并返回T,可以认为GenericsP实例对象为生产者(返回T)。open class Book(val name: String)data class EnglishBook(val english: String) : Book(english)data class MathBook(val math: String) : Book(math)已知EnglishBook、MathBook为Book的子类,但是如果将Book、EnglishBook当成泛型放入GenericsP,他们之间的关系还成立吗?即:可以看到编译器直接报错,因为虽然EnglishBook是Book的子类,但是GenericsP<EnglishBook>并不是GenericsP<Book>的子类,如果想让这个关系也成立,Kotlin提供了out修饰符,out修饰符能够确保:· 1、T只能用于函数返回中,不能用于参数输入中;· 2、GenericsP< EnglishBook>可以安全的作为GenericsP< Book>的子类示例如下:interface GenericsP<out T> { fun get(): T //读取并返回T,可以认为只能读取T的对象是生产者 // fun put(item: T) //错误,不允许在输入参数中使用}经过如上的改动后,可以看到GenericsP<EnglishBook>可以正确赋值给GenericsP<Book>了: 逆变< in T>interface GenericsC<T> { fun put(item: T) //写入T,可以认为只能写入T的对象是消费者}如上声明了GenericsC<T>接口,如果其内部只能写入T,可以认为GenericsC实例对象为消费者(消费T)。为了保证T只能出现在参数输入位置,而不能出现在函数返回位置上,Kotlin可以使用in进行控制:interface GenericsC<in T> { fun put(item: T) //写入T,可以认为只能写入T的对象是消费者 //fun get(): T //错误,不允许在返回中使用}继续编写如下函数: /** * 称为GenericsC在Book上是逆变的。 * 跟系统源码中的Comparable类似 */ private fun consume(to: GenericsC<Book>) { //GenericsC<Book>实例赋值给了GenericsC<EnglishBook> val target: GenericsC<EnglishBook> = to target.put(EnglishBook("英语")) }GenericsC中的泛型参数声明为in后,GenericsC<Book>实例可以直接赋值给了GenericsC<EnglishBook>,称为GenericsC在Book上是逆变的。在系统源码中我们经常使用的一个例子就是Comparable://Comparable.ktpublic interface Comparable<in T> { public operator fun compareTo(other: T): Int}使用处型变(类型投影)上一节中in、out都是写在类的声明处,从而控制泛型参数的使用场景,但是如果泛型参数既可能出现在函数入参中,又可能出现在函数返回中,典型的类就是Array:class Array<T>(val size: Int) { fun get(index: Int): T { …… } fun set(index: Int, value: T) { …… }}这时候就不能在声明处做任何协变/逆变的操作了,如下函数中使用Array: fun copy(from: Array<Any>, to: Array<Any>) { if (from.size != to.size) return for (i in from.indices) to[i] = from[i] }调用方:val strs: Array<String> = arrayOf("1", "2")val any = Array<Any>(2) {}copy(strs, any) //编译器报错 strs其类型为 Array<String> 但此处期望 Array<Any>错误原因就是因为Array<String>并不是Array<Any>的子类,即不是协变的,这里是为了保证数据的安全性。如果可以保证Array< String>传入copy()函数之后不能被写入,那么就保证了安全性,既然我们在声明Array时不能限制泛型参数,那么完全可以在使用处进行限制,如下: fun copy(from: Array<out Any>, to: Array<Any>) { if (from.size != to.size) return for (i in from.indices) to[i] = from[i] }可以看到对from添加了out限制,这种被称为使用处型变。即不允许from进行写入操作,那么就可以保证了from的安全性,再进行上面的调用时,copy(strs, any)就可以正确的执行了。星投影< *>当不使用协变、逆变时,某些场景下可以使用<*>来实现泛型,如:· 对于GenericsP<out T: Book>,GenericsP< *>相当于GenericsP<out Book>,当T未知时,可以安全的从GenericsP<*>中读取Book值;· 对于GenericsC<in T>,GenericsC<*>相当于 GenericsC<in Nothing>,当T未知时,没有安全方式写入GenericsC<*>;· 对于Generics<T: Book>,T为有上界Book的不型变参数,当Generics<*>读取时等价于Generics<out Book>;写入时等价于Generics<in Nothing>。泛型擦除泛型参数会在编译期间存在,在运行期间会被擦除,例如:Generics<EnglishBook> 与 Generics<MathBook> 的实例都会被擦除为 Generics<*>。运行时期检测一个泛型类型的实例无法通过is关键字进行判断,另外运行期间具体的泛型类型判断也无法判断,如: books as List<Book>,只会对非泛型部分进行检测,形如:books as List<*>。如果想具体化泛型参数,可以通过下面的方式:1、反射获取父类中的泛型类型(适用于Java & Kotlin) abstract class BaseFragment<T: ViewBinding> : Fragment(){ protected lateinit var binding: ViewBinding protected abstract val layoutRes: Int override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View? { return if (isDataBindingClass()) { binding = DataBindingUtil.inflate<ViewDataBinding>(inflater, layoutRes, container, false) binding.root } else { return inflater.inflate(layoutRes, container, false) } } //反射获取父类中的泛型类型来判断是否是ViewDataBinding类型 private fun isDataBindingClass() : Boolean{ try { val type = javaClass.genericSuperclass //ParameterizedType表示参数化的类型 if (type != null && type is ParameterizedType) { //返回此类型实际类型参数的Type对象数组 val actualTypeArguments = type.actualTypeArguments val tClass = actualTypeArguments[0] as Class<*> return ViewDataBinding::class.java.isAssignableFrom(tClass) } } catch (e: Exception) { e.printStackTrace() } return false } }//子类Fragment中//1、子类正常方式的Fragmentclass NormalFragment : BaseFragment<EmptyBinding>(){} //这里实现是空的,没有实际意义 class EmptyBinding : ViewBinding { override fun getRoot(): View { throw UnsupportedOperationException("Not implemented") } }//2、子类ViewDataBinding方式的Fragmentclass VDBFragment : BaseFragment<VDBBinding>() {}已知在androidx中ViewDataBinding实现了ViewBinding接口,如下:public abstract class ViewDataBinding extends BaseObservable implements ViewBinding {... }BaseFragment基类限制传入T: ViewBinding类型的泛型参数,注意T没有继承ViewDataBinding,目的是不强制子类Fragment中一定要用ViewDataBinding去写布局,主要就是通过isDataBindingClass()中通过反射对父类中的泛型T类型进行了判断:· javaClass.genericSuperclass返回子类实例的父类,也就是BaseFragment<T>· type is ParameterizedType如果成立,说明父类是带泛型参数的类· type.actualTypeArguments泛型参数可以有多个,因此返回值是一个Type[]数组· actualTypeArguments[0] as Class<*>获取第一个实际类型参数,并将其转换为Class对象· ViewDataBinding::class.java.isAssignableFrom(tClass)判断tClass是否为ViewDataBinding或其子类通过以上步骤就可以通过反射对泛型T类型进行判断,进而区分布局加载方式。2、使用 inline + reified 的方式(只适应于Kotlin) /** * inline内联函数 + reified 使得类型参数被实化 reified:实体化的 * 注:带reified类型参数的内联函数,Java是无法直接调用的 */ inline fun <reified T> isAny(value: Any): Boolean { return value is T }
0
0
0
浏览量819
IT大鲨鱼

JUC系列学习(一):线程池Executor框架及其实现ThreadPoolExecutor

Executor框架Executor 框架将任务的提交与任务的执行解耦了。 · Executor 顶层接口,Executor中只有一个execute方法,用于执行任务。线程的创建、调度等细节均由其子类实现· ExecutorService 继承并扩展了Executor,在ExecutorService内部提供了更全面的提交机制以及线程池关闭等方法。· ThreadPoolExecutor:实现了ExecutorService接口,线程池机制都封装在这个类中。· ScheduledExecutorService:实现了ExecutorService接口,增加了定时任务的相关方法。· ScheduledThreadPoolExecutor:继承自ThreadPoolExecutor并实现了ScheduledExecutorService接口· ForkJoinPool:是一种支持任务分解的线程池,一般配合接口ForkJoinTask使用。ThreadPoolExecutor配置ThreadPoolExecutor线程池时,要避免线程池大小出现过大或过小的情况。如果线程池过大,那么大量的线程将会在cpu及内存资源上发生竞争,从而导致更高的内存使用量,严重情况下会导致资源耗尽;如果线程池过小,会导致空闲的cpu处理器无法工作,从而降低了吞吐率。要设置合适的线程池大小,需要考虑下面的几个因素:· CPU个数( 可以通过Runtime.getRuntime().availableProcessors()获取)· 内存大小· 任务类型:计算密集型、I/O密集型还是两者皆可。如:任务为计算密集型,当CPU处理器个数为N时,线程池大小为N+1比较合适。public class ThreadPoolExecutor extends AbstractExecutorService { public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; } }ThreadPoolExecutor继承自AbstractExecutorService类(实现了ExecutorService接口),ThreadPoolExecutor中有四个构造器,但最后都会调用到上面代码中的这个,来看构造器中的这些参数代表的意义:· corePoolSize(int类型): 核心线程数,默认一直存活(即使线程处于空闲状态)。· maximumPoolSize(int类型): 线程池允许的最大线程数,其值大小>=corePoolSize。· keepAliveTime(long类型): 线程的存活时间,默认是超过corePoolSize大小之后启动的非核心线程的存活时间,当线程池设置allowCoreThreadTimeOut=true时,对核心线程也会起作用。· unit(TimeUnit类型): 时间单位NANOSECONDS //纳秒MICROSECONDS //微秒MILLISECONDS //毫秒SECONDS //秒MINUTES //分HOURS //小时DAYS //天· workQueue(BlockingQueue<Runnable>) : 阻塞队列(实现了BlockingQueue接口),当线程数超过核心线程数corePoolSize大小时,会将任务放入阻塞队列中,ThreadPoolExecutor中使用了下面几种队列:ArrayBlockingQueue :数组实现的有界阻塞队列,队列满时,后续提交的任务通过handler中的拒绝策略去处理。LinkedBlockingQueue:链表实现的阻塞队列,默认大小是Integer.MAX_VALUE(无界队列),也可以通过传入指定队列大小capacity。SynchronousQueue:内部并没有缓存数据,缓存的是线程。当生产者线程进行添加操作(put)时必须等待消费者线程的移除操作(take)才会返回。SynchronousQueue可以用于实现生产者与消费者的同步。PriorityBlockingQueue:二叉堆实现的优先级阻塞队列,传入队列的元素不能为null并且必须实现Comparable接口。DelayQueue: 延时阻塞队列,队列元素需要实现Delayed接口。· threadFactory(ThreadFactory): 线程工厂,用于创建线程。Executors中使用了默认的DefaultThreadFactory: private static class DefaultThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); if (t.isDaemon()) t.setDaemon(false); if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); return t; } }也可以自定义ThreadFactory,在newThread中自行配置Thread(如:配置线程名、是否是守护线程、线程优先级等)。· handler(RejectedExecutionHandler): 提交的任务被拒绝执行的饱和策略,通常有下面几种形式:AbortPolicy: 默认饱和策略,丢弃任务并抛出RejectedExecutionException异常。调用者可以捕获这个异常并自行处理。 CallerRunsPolicy:线程池中不再处理该任务,由调用线程处理该任务。 DiscardPolicy:当任务无法添加到队列中等待执行时,DiscardPolicy策略会丢弃任务,并且不抛异常。 DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试提交新的任务。具体使用哪个饱和策略要根据具体的业务场景指定。当然也可以自定义RejectedExecutionHandler来自行决定拒绝任务的处理策略。执行流程线程池ThreadPoolExecutor的工作流程大致分为下面几步:· 当工作线程数小于核心线程数(corePoolSize)时,直接创建核心线程去执行任务· 当线程数超过核心线程数(corePoolSize)时,将任务加入等待队列(BlockingQueue)中· 队列满时,继续创建非核心线程去执行任务(注意:核心线程corePoolSize+非核心线程<=maximumPoolSize)简单总结一下执行顺序:核心线程数满->队列满->最大线程数满->任务拒绝策略用流程图大致表示为: 线程池状态ThreadPoolExecutor有三种状态:运行、关闭、终止。// runState is stored in the high-order bits private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS;· RUNNING: 运行状态,接收并处理队列中的任务· SHUTDOWN: 不再接收新任务,队列中已提交的任务执行完成。· STOP: 尝试取消所有正在运行中的任务,并且不再处理队列中尚未开始执行的任务。· TIDYING: 所有任务已终止,线程池中线程数为变为0,此时线程池状态变为TIDYING。· TERMINATED: 线程池终止。AsyncTaskAsyncTask是Android内置线程池,相当于封装了Thread+Handler,可以很方便地在后台执行耗时任务并将结果更新到UI线程。AsyncTask内部的线程池是通过ThreadPoolExecutor实现的。public abstract class AsyncTask<Params, Progress, Result> { ...... }AsyncTask是一个抽象类,几个关键方法如下:· onPreExecute():运行在UI线程,可以用来做一些初始化工作· doInBackground(Params…):运行在子线程,用于执行一些耗时操作,执行过程中可以通过publishProgress(Progress…values)来通知UI线程任务的进度。· onPostExecute(Result result):运行在UI线程,doInBackground执行完毕后返回Result,并将该值传递到onPostExecute(Result result)中。· onProgressUpdate(Progress…):运行在UI线程,用于显示doInBackground的执行进度,在doInBackground()中执行publishProgress(Progress…values)后会回调到此方法。· onCancelled():运行在UI线程,当调用cancel(true)后会回调此方法。此时onPostExecute()不会再执行。注:AsyncTask有三种泛型类型,Params,Progress、Result(可以都指定为空,如AsyncTask<Void, Void, Void>),其中:· Params:在execute(Params... params)传递,会传递到doInBackground(Params…)中。· Progress:后台任务进度值,在onProgressUpdate(Progress…)使用· Result:最终返回的结果类型,在onPostExecute(Result result)使用AsyncTask中任务的串行&并行private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); // We want at least 2 threads and at most 4 threads in the core pool, // preferring to have 1 less than the CPU count to avoid saturating // the CPU with background work private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE_SECONDS = 30; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); //并行线程池 public static final Executor THREAD_POOL_EXECUTOR; static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); threadPoolExecutor.allowCoreThreadTimeOut(true); THREAD_POOL_EXECUTOR = threadPoolExecutor; } //任务串行 @MainThread public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params);} //任务并行 @MainThread public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; } @MainThread public static void execute(Runnable runnable) { sDefaultExecutor.execute(runnable); }AsynacTask中执行任务可以通过execute()或者executeOnExecutor()去提交并执行任务,其中execute()是串行执行任务,而executeOnExecutor是并行执行任务。其中并行线程池就是用的THREAD_POOL_EXECUTOR,来看串行是如何实现的:private static class SerialExecutor implements Executor { final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } } 串行并不是将任务直接交给THREAD_POOL_EXECUTOR去执行,而是内部自行维护了一个mTasks双向队列,每次有新Runnable任务来时,先添加到队列中,然后判断当前是否有正在执行的任务(mActive != null即表示有正在执行的任务),没有的话才会将任务提交给线程池执行;否则会等待前一个任务执行完成,然后从队列中取出新任务(按FIFO顺序)继续交给线程池执行。
0
0
0
浏览量2010
IT大鲨鱼

Kotlin | 利用by委托机制封装SharedPreference

本文是 by 委托机制的实践,利用委托机制来对SharedPreference(以下简称sp)进行封装,从而简化其使用,默认 sp 的存取数据方式如下:· SharedPreference存数据://获得SharedPreferences的实例 sp_name是文件名val sp: SharedPreferences = getSharedPreferences("sp_name", Context.MODE_PRIVATE)//获得Editor 实例val editor: SharedPreferences.Editor = sp.edit()//以key-value形式保存数据editor.putString("data_key", "data")//apply()是异步写入数据editor.apply()//commit()是同步写入数据 //editor.commit()· SharedPreference取数据://获得SharedPreferences的实例val sp = getSharedPreferences("sp_name", MODE_PRIVATE)//通过key值获取到相应的data,如果没取到,则返回后面的默认值val data = sp.getString("data_key", "defaultValue")通常项目中我们会封装成类似 SPUtil 的方式使用,但是 sp 使用起来还是稍显麻烦,下面就利用 by 委托机制对 sp 进行更优雅的封装。利用委托机制封装SharedPreference/** * SharedPreferences委托代理 * @param context Context * @param spName SP存入的XML名字 * @param defaultValue 默认值 * @param key 存取数据时对应的key */class SharedPreferencesDelegate<T>( private val context: Context, private val spName: String, private val defaultValue: T, private val key: String? = null,) : ReadWriteProperty<Any?, T> { private val sp: SharedPreferences by lazy(LazyThreadSafetyMode.NONE) { context.getSharedPreferences(spName, Context.MODE_PRIVATE) } override fun getValue(thisRef: Any?, property: KProperty<*>): T { val finalKey = key ?: property.name return when (defaultValue) { is Int -> sp.getInt(finalKey, defaultValue) is Long -> sp.getLong(finalKey, defaultValue) is Float -> sp.getFloat(finalKey, defaultValue) is Boolean -> sp.getBoolean(finalKey, defaultValue) is String -> sp.getString(finalKey, defaultValue) is Set<*> -> sp.getStringSet(finalKey, defaultValue as? Set<String>) else -> throw IllegalStateException("Unsupported type") } as T } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { val finalKey = key ?: property.name with(sp.edit()) { when (value) { is Int -> putInt(finalKey, value) is Long -> putLong(finalKey, value) is Float -> putFloat(finalKey, value) is Boolean -> putBoolean(finalKey, value) is String -> putString(finalKey, value) is Set<*> -> putStringSet(finalKey, value.map { it.toString() }.toHashSet()) else -> throw IllegalStateException("Unsupported type") } apply() } }}使用它:class CommonPreferences(context: Context) { companion object { /** * 通过传入不同的SP文件名来存储到不同的XML中 */ const val FIR_SP_NAME = "FIR_SP_NAME" //文件名1 const val SEC_SP_NAME = "SEC_SP_NAME"//文件名2 } var isShow by SharedPreferencesDelegate(context, FIR_SP_NAME, false, "key_is_show") //这里没有用key值,则会默认使用属性名来当做key值 var name by SharedPreferencesDelegate(context, FIR_SP_NAME, "") var age by SharedPreferencesDelegate(context, SEC_SP_NAME, 0, "key_age") //这里没有用key值,则会默认使用属性名来当做key值 var cost by SharedPreferencesDelegate(context, SEC_SP_NAME, 0.0f)}上述代码展示了根据不同的文件名把数据存入到不同的XML文件中,你可以根据自己的需求进行调整。在 Activity 中进行存取值:private val spInfo = CommonPreferences(this)//存值spInfo.isShow = truespInfo.name = "小马快跑"//存值spInfo.age = 18spInfo.cost = 123.4fspInfo.setString = setOf("一", "二", "三")//取值log("isShow -> ${spInfo.isShow}, name -> ${spInfo.name}")log("age -> ${spInfo.age}, cost -> ${spInfo.cost},setString -> ${spInfo.setString}")可以看到对sp 的存取值简直不要太简单,跟对象属性的存取似的,来看执行结果:E/TTT: isShow -> true, name -> 小马快跑E/TTT: age -> 18, cost -> 123.4,setString -> [一, 三, 二]取值成功了,那么存值呢?看下手机对应目录下是否有存入的数据:嗯,存取值都成功了。总结主要利用的是属性委托val/var <属性名>: <类型> by <表达式>,例如:var isShow by SharedPreferencesDelegate(context, FIR_SP_NAME, false, "key_is_show")通过属性代码执行到 SharedPreferencesDelegate 中的逻辑进行存取操作,不必再关心整个的存取细节,nice!
0
0
0
浏览量821
IT大鲨鱼

Android Kotlin之Flow数据流

Flow介绍Flow是google官方提供的一套基于kotlin协程的响应式编程模型,它与RxJava的使用类似,但相比之下Flow使用起来更简单,另外Flow作用在协程内,可以与协程的生命周期绑定,当协程取消时,Flow也会被取消,避免了内存泄漏风险。我们知道 协程是轻量级的线程,本质上协程、线程都是服务于并发场景下,其中协程是协作式任务,线程是抢占式任务。默认协程用来处理实时性不高的数据,请求到结果后整个协程就结束了,即它是一锤子买卖。而Flow数据流可以按顺序发送多个值,官方对数据流三个成员的定义:提供方会生成添加到数据流中的数据。通过协程,数据流还可以异步生成数据。中介(可选),修改发送到数据流的值,或修正数据流本身。使用方:使用或接收数据流中的值。使用举例举个Flow简单例子: flow { log("send hello") emit("hello") //发送数据 log("send world") emit("world") //发送数据 }.flowOn(Dispatchers.IO) .onEmpty { log("onEmpty") } .onStart { log("onStart") } .onEach { log("onEach: $it") } .onCompletion { log("onCompletion") } .catch { exception -> exception.message?.let { log(it) } } .collect { //接收数据流 log("collect: $it") } 执行结果:2021-09-27 19:51:54.433 7240-7240/ E/TTT: onStart 2021-09-27 19:51:54.439 7240-7325/ E/TTT: send hello 2021-09-27 19:51:54.440 7240-7325/ E/TTT: send world 2021-09-27 19:51:54.451 7240-7240/ E/TTT: onEach: hello 2021-09-27 19:51:54.451 7240-7240/ E/TTT: collect:hello 2021-09-27 19:51:54.452 7240-7240/ E/TTT: onEach: world 2021-09-27 19:51:54.452 7240-7240/ E/TTT: collect:world 2021-09-27 19:51:54.453 7240-7240/ E/TTT: onCompletion flow{}为上游数据提供方,并通过emit()发送一个或多个数据,当发送多个数据时,数据流整体是有序的,即先发送先接收;另外发送的数据必须来自同一个协程内,不允许来自多个CoroutineContext,所以默认不能在flow{}中创建新协程或通过withContext()切换协程。如需切换上游的CoroutineContext,可以通过flowOn()进行切换。collect{}为下游数据使用方,collect是一个扩展函数,且是一个非阻塞式挂起函数(使用suspend修饰),所以Flow只能在kotlin协程中使用。其他操作符可以认为都是服务于整个数据流的,包括对上游数据处理、异常处理等。常用操作符创建操作符flow:创建Flow的操作符。flowof:构造一组数据的Flow进行发送。asFlow:将其他数据转换成Flow,一般是集合向Flow的转换,如listOf(1,2,3).asFlow()。callbackFlow:将基于回调的 API 转换为Flow数据流回调操作符onStart:上游flow{}开始发送数据之前执行onCompletion:flow数据流取消或者结束时执行onEach:上游向下游发送数据之前调用,每一个上游数据发送后都会经过onEach()onEmpty:当流完成却没有发出任何元素时执行。如emptyFlow<String>().onEmpty {}onSubscription:SharedFlow 专用操作符,建立订阅之后回调。和onStart的区别:因为SharedFlow是热流,因此如果在onStart发送数据,下游可能接收不到,因为提前执行了。变换操作符map:对上游发送的数据进行变换,collect最后接收的是变换之后的值mapLatest:类似于collectLatest,当emit发送新值,会取消掉map上一次转换还未完成的值。mapNotNull:仅发送map之后不为空的值。transform:对发出的值进行变换 。不同于map的是,经过transform之后可以重新发送数据,甚至发送多个数据,因为transform内部又重新构建了flow。transformLatest:类似于mapLatest,当有新值发送时,会取消掉之前还未转换完成的值。transformWhile:返回值是一个Boolean,当为true时会继续往下执行;反之为false,本次发送的流程会中断。asSharedFlow: MutableStateFlow 转换为 StateFlow ,即从可变状态变成不可变状态。asStateFlow:MutableSharedFlow 转换为 SharedFlow ,即从可变状态变成不可变状态。receiveAsFlow:Channel 转换为Flow ,上游与下游是一对一的关系。如果有多个下游观察者,可能会轮流收到值。consumeAsFlow:Channel 转换为Flow ,有多个下游观察者时会crash。withIndex:将数据包装成IndexedValue类型,内部包含了当前数据的Index。scan(initial: R, operation: suspend (accumulator: R, value: T) -> R):把initial初始值和每一步的操作结果发送出去。produceIn:转换为Channel的 ReceiveChannelrunningFold(initial, operation: (accumulator: R, value: T) -> R):initial值与前面的流共同计算后返回一个新流,将每步的结果发送出去。runningReduce*:返回一个新流,将每步的结果发送出去,默认没有initial值。shareIn:flow 转化为 SharedFlow,后面会详细介绍。stateIn:flow转化为StateFlow,后面会详细介绍。过滤操作符filter:筛选符合条件的值,返回true继续往下执行。filterNot:与filter相反,筛选不符合条件的值,返回false继续往下执行。filterNotNull:筛选不为空的值。filterInstance:筛选对应类型的值,如.filterIsInstance<String>()用来过滤String类型的值drop:drop(count: Int)参数为Int类型,意为丢弃掉前count个值。dropWhile:找到第一个不满足条件的值,返回其和其后所有的值。take:与drop()相反,意为取前n个值。takeWhile:与dropWhile()相反,找到第一个不满足条件的值,返回其前面所有的值。debounce:debounce(timeoutMillis: Long)指定时间内只接收最新的值,其他的过滤掉。sample:sample(periodMillis: Long)在指定周期内,获取最新发出的值。如: flow { repeat(10) { emit(it) delay(110) } }.sample(200) 执行结果:1, 3, 5, 7, 9distinctUntilChangedBy:判断两个连续值是否重复,可以设置是否丢弃重复值。distinctUntilChanged:若连续两个值相同,则跳过后面的值。组合操作符combine:组合两个Flow流最新发出的数据,直到两个流都结束为止。扩展:在kotlinx-coroutines-core-jvm中的FlowKt中,可以将更多的flow结合起来返回一个Flow<Any>,典型应用场景:多个筛选条件选中后,展示符合条件的数据。如果后续某个筛选条件发生了改变,只需要通过发生改变的Flow的flow.value = newValue重新发送,combine就会自动构建出新的Flow<Any>,这样UI层会接收到新的变化条件进行刷新即可。combineTransform: combine + transform操作merge:listOf(flow1, flow2).merge(),多个流合并为一个流。flattenConcat:以顺序方式将给定的流展开为单个流 。示例如下:flow { emit(flowOf(1, 2,)) emit(flowOf(3,4)) } .flattenConcat().collect { value-> print(value) } // 执行结果:1 2 3 4 flattenMerge:作用和 flattenConcat 一样,但是可以设置并发收集流的数量。flatMapContact:相当于 map + flattenConcat , 通过 map 转成一个流,在通过 flattenConcat发送。flatMapLatest:当有新值发送时,会取消掉之前还未转换完成的值。flatMapMerge:相当于 map + flattenMerge ,参数concurrency: Int 来限制并发数。zip:组合两个Flow流最新发出的数据,上游流在同一协程中顺序收集,没有任何缓冲。不同于combine的是,当其中一个流结束时,另外的Flow也会调用cancel,生成的流完成。 lifecycleScope.launch { val flow = flowOf(1, 2, 3).onEach { delay(50) } val flow2 = flowOf("a", "b", "c", "d").onEach { delay(150) } val startTime = System.currentTimeMillis() // 记录开始的时间 flow.zip(flow2) { i, s -> i.toString() + s }.collect { // Will print "1a 2b 3c" log("$it 耗时 ${System.currentTimeMillis() - startTime} ms") } } 执行结果(flow已经执行完,所以flow2中的d被cancel了):2022-05-20 /org.ninetripods.mq.study E/TTT: 1a 耗时 156 ms 2022-05-20 /org.ninetripods.mq.study E/TTT: 2b 耗时 307 ms 2022-05-20 /org.ninetripods.mq.study E/TTT: 3c 耗时 459 ms 如果换做combine,执行结果如下(组合的是最新发出的数据):2022-05-20 /org.ninetripods.mq.study E/TTT: 2a 耗时 156 ms 2022-05-20 /org.ninetripods.mq.study E/TTT: 3a 耗时 159 ms 2022-05-20 /org.ninetripods.mq.study E/TTT: 3b 耗时 311 ms 2022-05-20 /org.ninetripods.mq.study E/TTT: 3c 耗时 466 ms 2022-05-20 /org.ninetripods.mq.study E/TTT: 3d 耗时 620 ms 注:上面combine多次执行的结果可能不一致,但每次组合的是最新发出的数据功能性操作符cancellable:判断当前协程是否被取消 ,如果已取消,则抛出异常catch:对此操作符之前的流发生的异常进行捕获,对此操作符之后的流无影响。当发生异常时,默认collect{}中lambda将不会再执行。当然,可以自行通过emit()继续发送。retry:流发生异常时的重试机制。如果是无限重试,直接调用retry()默认方法即可,retry()最终调用的也是retryWhen()方法。public fun <T> Flow<T>.retry( retries: Int = Int.MAX_VALUE, //指定重试次数 predicate: (Throwable) -> Boolean = { true } //返回true且满足retries次数要求,继续重试;false停止重试 ): Flow<T> { require(retries > 0) { "Expected positive amount of retries, but had $retries" } return retryWhen { cause, attempt -> predicate(cause) && attempt < retries } } retryWhen:流发生异常时的重试机制。public fun <T> Flow<T>.retryWhen(predicate: suspend FlowCollector<T>.(cause: Throwable, attempt: Long) -> Boolean): Flow<T> = { ...... } 有条件的进行重试 ,lambda 中有两个参数: cause是 异常原因,attempt是当前重试的位置,lambda返回true时继续重试; 反之停止重试。buffer:流执行总时间就是所有运算符执行时间之和。如果上下游运算符都比较耗时,可以考虑使用buffer()优化,该运算符会在执行期间为流创建一个单独的协程。public fun <T> Flow<T>.buffer(capacity: Int = BUFFERED, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND): Flow<T> {......} 默认流都是在同一个协程中进行的,示例如下所示(官方示例):flowOf("A", "B", "C") .onEach { println("1$it") } .collect { println("2$it") } //上述代码在协程Q中按以下顺序执行: Q : -->-- [1A] -- [2A] -- [1B] -- [2B] -- [1C] -- [2C] -->-- 此时,如果onEach()、collect()中的运算时间都比较长的话,那么总时间就是所有运算符执行时间之和。buffer运算符会在执行期间为流创建一个单独的协程,如下所示:flowOf("A", "B", "C") .onEach { println("1$it") } .buffer() // <--------------- buffer between onEach and collect .collect { println("2$it") } 上述代码将在两个协程中执行,其中buffer()以上还是在协程P中执行,而buffer()下面的collect()会在协程Q中执行,数据通过Channel进行传递,从而减少了执行的总时间。P : -->-- [1A] -- [1B] -- [1C] ---------->-- // flowOf(...).onEach { ... } | | channel // buffer() V Q : -->---------- [2A] -- [2B] -- [2C] -->-- // collect conflate:仅保留最新值, 内部实现是 buffer(CONFLATED)flowOn:flowOn 会更改上游数据流的 CoroutineContext,且只会影响flowOn之前(或之上)的任何中间运算符。下游数据流(晚于 flowOn 的中间运算符和使用方)不会受到影响。如果有多个 flowOn 运算符,每个运算符都会更改当前位置的上游数据流。末端操作符collect:数据收集操作符,默认的flow是冷流,即当执行collect时,上游才会被触发执行。collectIndexed:带下标的收集操作,如collectIndexed{ index, value -> }。collectLatest:与collect的区别:当新值从上游发出时,如果上个收集还未完成,会取消上个值得收集操作。toCollection、toList、toSet:将flow{}结果转化为集合。注:还有很多操作符没有列出来~冷流 vs 热流flow{}会创建一个数据流,并且这个数据流默认是冷流。除了冷流,还有对应的热流,下面是冷流和热流的区别:冷流:当执行订阅的时候,上游发布者才开始发射数据流。订阅者与发布者是一一对应的关系,即当存在多个订阅者时,每个新的订阅者都会重新收到完整的数据。热流:不管是否被订阅,上游发布者都会发送数据流到内存中。订阅者与发布者是一对多的关系,当上游发送数据时,多个订阅者都会收到消息。来验证一下flow{}创建的是冷流:界面如上图所示,定义了2个订阅者,首先构建数据流: var sendNum = 0 val mSimpleFlow = flow { sendNum++ emit("sendValue:$sendNum") }.flowOn(Dispatchers.IO) 以及两个订阅者:mBtnContent1.setOnClickListener { lifecycleScope.launch { mSimpleFlow.collect { mTvSend.text = it mBtnContent1.text = it } } } mBtnContent2.setOnClickListener { lifecycleScope.launch { mSimpleFlow.collect { mTvSend.text = it mBtnContent2.text = it } } } 当点击订阅者1的按钮时,flow{}中发送了sendValue1,执行结果:此时继续点击右边的订阅者2,flow{}中发送了sendValue2,执行结果:可以看到两个订阅者是互相不干扰的,都是单独与上游flow{}进行数据传递的,即冷流,另外,flow{}可以通过stateIn/shareIn将其转换为StateFlow/SharedFlow热流。SharedFlow我们知道flow{}构建的是冷流,而SharedFlow(共享Flow)默认是热流,发送器与收集器是一对多的关系。public fun <T> MutableSharedFlow( replay: Int = 0, extraBufferCapacity: Int = 0, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND ): MutableSharedFlow<T> replay:重播给新订阅者时缓存数据的个数,默认是0。当新订阅者collect时,会先尝试获取上游replay个数据,为0时则不会获取之前的数据。replay缓存是针对后续所有的订阅者准备的。extraBufferCapacity:除了replay外,缓冲值的数量。当有剩余的缓冲区空间时,Emit不挂起(可选,不能为负,默认为零) 。extraBufferCapacity是为上游快速发射器及下游慢速收集器这种场景提供缓冲的,个人觉得有点类似于线程池中的存储队列。这里注意一点,replay保存的是最新值,而extraBufferCapacity保存的是最先发送的一个或多个值。onBufferOverflow:配置缓冲区溢出的操作(可选,默认为SUSPEND,暂停尝试发出值),可选值有:SUSPEND-暂停发送、DROP_OLDEST-丢弃队列中最老的、DROP_LATEST-丢弃队列中最新的。关于replay与extraBufferCapacity 的不同,可以参考 MutableSharedFlow 有点复杂这篇文章。shareIn将普通flow转化为SharedFlow普通flow{}可以通过shareIn将普通数据流转换成SharedFlowpublic fun <T> Flow<T>.shareIn( scope: CoroutineScope, started: SharingStarted, replay: Int = 0 ): SharedFlow<T> scope:协程作用域范围started:控制共享的开始、结束策略。一共有三种,分别为Eagerly、Lazily、WhileSubscribed。1、SharingStarted.Eagerly, //Eagerly:马上开始,在scope作用域结束时终止 2、SharingStarted.Lazily, //Lazily:当订阅者出现时开始,在scope作用域结束时终止 3、SharingStarted.WhileSubscribed(stopTimeoutMillis: Long = 0,replayExpirationMillis: Long = Long.MAX_VALUE) 其中stopTimeoutMillis:表示最后一个订阅者结束订阅与停止上游流的时间差,默认值为0(立即停止上游流) replayExpirationMillis:数据重播的超时时间。 replay:重播给新订阅者的数量举例: //ViewModel中 普通flow通过shareIn转化为SharedFlow val flowConvertSharedFlow by lazy { flow { emit("1、flow") emit("2、convert") emit("3、SharedFlow") }.shareIn( viewModelScope, //协程作用域范围 SharingStarted.Eagerly, //立即开始 replay = 3 //重播给新订阅者的数量 ).onStart { log("onStart") } } //Activity中 mBtnConvertF.setOnClickListener { val builder: StringBuilder = StringBuilder() lifecycleScope.launch { mFlowModel.flowConvertSharedFlow.collect { log(it) builder.append(it).append("\n") mTvConvertF.text = builder.toString() } } } 执行结果:2021-10-09 15:11:08.340 4549-4549/ E/TTT: onStart 2021-10-09 15:11:08.340 4549-4549/ E/TTT: 1、flow 2021-10-09 15:11:08.341 4549-4549/ E/TTT: 2、convert 2021-10-09 15:11:08.341 4549-4549/ E/TTT: 3、SharedFlow StateFlowStateFlow特点:StateFlow可以认为是一个replay为1,且没有缓冲区的SharedFlow,所以新订阅者collect时会先获取一个默认值,构造函数如下://MutableStateFlow构造函数 public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL) //MutableStateFlow接口继承了MutableSharedFlow接口 public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> { public override var value: T public fun compareAndSet(expect: T, update: T): Boolean } StateFlow有自动去重的功能,即如果上游连续发送的value重复时,下游的接收方只会接收第一次的值,后续的重复值不会再接收可以通过StateFlow.value获取发送的值stateIn将普通flow转化为StateFlow普通flow{}可以通过stateIn将普通数据流转换成StateFlowpublic fun <T> Flow<T>.stateIn( scope: CoroutineScope, started: SharingStarted, initialValue: T ): StateFlow<T> { //这里设置的replay是1 及重播给新订阅者的缓存为1 val config = configureSharing(1) ...... } scope:协程作用域范围started:控制共享的开始、结束策略。一共有三种,分别为Eagerly、Lazily、WhileSubscribed。1、SharingStarted.Eagerly, //Eagerly:马上开始,在scope作用域结束时终止 2、SharingStarted.Lazily, //Lazily:当订阅者出现时开始,在scope作用域结束时终止 3、SharingStarted.WhileSubscribed(stopTimeoutMillis: Long = 0,replayExpirationMillis: Long = Long.MAX_VALUE) 其中stopTimeoutMillis:表示最后一个订阅者结束订阅与停止上游流的时间差,默认值为0(立即停止上游流) replayExpirationMillis:数据重播的超时时间。 initialValue:默认StateFlow的初始值,会发送到下游使用举例: //ViewModel中 val flowConvertStateFlow by lazy { flow { //转化为StateFlow是 emit()可以是0个或1个 或多个,当是多个时,新订阅者collect只会收到最后一个值(replay为1) emit("1、flow convert StateFlow") } .stateIn( viewModelScope, //协程作用域范围 SharingStarted.Eagerly, //立即开始 "0、initialValue" // 默认StateFlow的初始值,会发送到下游 ).onStart { log("onStart") } } //Activity中 mBtnConvertSF.setOnClickListener { lifecycleScope.launch { val builder = StringBuilder() mFlowModel.flowConvertStateFlow.collect { log(it) builder.append(it).append("\n") mTvConvertSF.text = builder.toString() } } } 执行结果:2021-10-09 16:34:07.180 12394-12394/ E/TTT: onStart 2021-10-09 16:34:07.181 12394-12394/ E/TTT: 0、initialValue 2021-10-09 16:34:07.182 12394-12394/ E/TTT: 1、flow convert StateFlow 注:在UI层使用Lifecycle.repeatOnLifecycle 配合上游的SharingStarted.WhileSubscribed一块使用是一种更安全、性能更好的流收集方式。StateFlow vs LiveData在学习LiveData时,我们知道通过LiveData可以让数据被观察,且具备生命周期感知能力,但LiveData的缺点也很明显:LiveData的接收只能在主线程;LiveData发送数据是一次性买卖,不能多次发送;LiveData发送数据的线程是固定的,不能切换线程,setValue/postValue本质上都是在主线程上发送的。当需要来回切换线程时,LiveData就显得无能为力了。StateFlow 和 LiveData 具有相似之处。两者都是可观察的数据容器类,并且在应用架构中使用时,两者都遵循相似模式。但两者还是有不同之处的:StateFlow 需要将初始状态传递给构造函数,而 LiveData 不需要。当 View 进入 STOPPED 状态时,LiveData.observe() 会自动取消注册使用方,而从 StateFlow 或任何其他数据流收集数据的操作并不会自动停止。如需实现相同的行为,需要从 Lifecycle.repeatOnLifecycle 块收集数据流。StateFlow、SharedFlow  vs ChannelFlow底层使用的Channel机制实现,StateFlow、SharedFlow都是一对多的关系,如果上游发送者与下游UI层的订阅者是一对一的关系,可以使用Channel来实现,Channel默认是粘性的。Channel使用场景:一次性消费场景,比如弹窗,需求是在UI层只弹一次,即使App切到后台再切回来,也不会重复订阅(不会多次弹窗);如果使用SharedFlow/StateFlow,UI层使用的lifecycle.repeatOnLifecycle、Flow.flowWithLifecycle,则在App切换前后台时,UI层会重复订阅,弹窗事件可能会多次执行,不符合要求。Channel使用特点:每个消息只有一个订阅者可以收到,用于一对一的通信第一个订阅者可以收到collect之前的事件,即粘性事件Channel使用举例://viewModel中 private val _loadingChannel = Channel<Boolean>() val loadingFlow = _loadingChannel.receiveAsFlow() private suspend fun loadStart() { _loadingChannel.send(true) } private suspend fun loadFinish() { _loadingChannel.send(false) } //UI层接收Loading信息 mViewModel.loadingFlow.flowWithLifecycle2(this, Lifecycle.State.STARTED) { isShow -> mStatusViewUtil.showLoadingView(isShow) } 扩展:suspendCancellableCoroutine & callbackFlow在新项目或者新需求中,我们可以直接使用协程来替代之前的多线程场景的使用方式,如可以通过withContext(Dispatchers.IO)在协程中来回切换线程且能在线程执行完毕后自动切回当前线程,避免使用接口回调的方式导致逻辑可读性变差。然而,如果我们是在现有项目中开发或者网络框架就是回调方式使用时,没有办法直接使用协程,但是可以通过suspendCancellableCoroutine或callbackFlow将接口回调转化成协程:suspendCancellableCoroutine等待单次回调API的结果时挂起协程,并将结果返回给调用者;如果需要返回Flow<T>数据流,可以使用callbackFlow。suspendCancellableCoroutine使用举例: //ViewModel中 /** * suspendCancellableCoroutine将回调转化为协程使用 */ suspend fun suspendCancelableData(): String { return try { getSccInfo() } catch (e: Exception) { "error: ${e.message}" } } /** * suspendCancellableCoroutine将回调转化为协程使用 */ private suspend fun getSccInfo(): String = suspendCancellableCoroutine { continuation -> val callback = object : ICallBack { override fun onSuccess(sucStr: String?) { //1、返回结果 将结果赋值给getSccInfo()挂起函数的返回值 //2、如果调用了continuation.cancel(),resume()的结果将不会返回了,因为协程取消了 continuation.resume(sucStr ?: "empty") } override fun onError(error: Exception) { //这里会将异常抛给上层 需要上层进行处理 continuation.resumeWithException(error) } } continuation.invokeOnCancellation { //协程取消时调用,可以在这里进行解注册 log("invokeOnCancellation") } //模拟网络请求 此时协程被suspendCancellableCoroutine挂起,直到触发回调 Thread { Thread.sleep(500) //模拟Server返回数据 callback.onSuccess("getServerInfo") //模拟抛异常 //callback.onError(IllegalArgumentException("server error")) }.start() //模拟取消协程 //continuation.cancel() } //Activity中 mBtnScc.setOnClickListener { lifecycleScope.launch { val result = mFlowModel.suspendCancelableData() log(result) } } 执行结果:2021-10-11 13:31:41.384 24114-24114/ E/TTT: getServerInfo suspendCancellableCoroutine声明了作用域,并且传入一个CancellableContinuation参数,它可以调用resume、resumeWithException来处理对应的成功、失败回调,还可以调用cancel()方法取消协程的执行(抛出CancellationException 异常,但程序不会崩溃,当然也可以通过catch抓住该异常进行处理)。上面例子中,当开始执行时会将suspendCancellableCoroutine作用域内协程挂起,如果成功返回数据,会回调continuation.resume()方法将结果返回;如果出现异常,会回调continuation.resumeWithException()将异常抛到上层。这样整个函数处理完后,上层会从挂起点恢复并继续往下执行。callbackFlowcallbackFlow相对于suspendCancellableCoroutine,对接口回调封装以后返回的是Flow数据流,后续就可以对数据流进行一系列操作。callbackFlow中的几个重要方法:trySend/offer:在接口回调中使用,用于上游发射数据,类似于flow{}中的emit(),kotlin 1.5.0以下使用offer,1.5.0以上推荐使用trySend()awaitClose:写在最后,这是一个挂起函数, 当 flow 被关闭的时候 block 中的代码会被执行 可以在这里取消接口的注册等。使用举例,比如当前有个场景:去某个地方,需要先对目的地进行搜索,再出发到达目的地,假设搜索、到达目的地两个行为都是使用回调来执行的,我们现在使用callbackFlow对他们进行修改:ViewModel中,搜索目的地: fun getSearchCallbackFlow(): Flow<Boolean> = callbackFlow { val callback = object : ICallBack { override fun onSuccess(sucStr: String?) { //搜索目的地成功 trySend(true) } override fun onError(error: Exception) { //搜索目的地失败 trySend(false) } } //模拟网络请求 Thread { Thread.sleep(500) //模拟Server返回数据 callback.onSuccess("getServerInfo") }.start() //这是一个挂起函数, 当 flow 被关闭的时候 block 中的代码会被执行 可以在这里取消接口的注册等 awaitClose { log("awaitClose") } } ViewModel中,前往目的地:fun goDesCallbackFlow(isSuc: Boolean): Flow<String?> = callbackFlow { val callback = object : ICallBack { override fun onSuccess(sucStr: String?) { trySend(sucStr) } override fun onError(error: Exception) { trySend(error.message) } } //模拟网络请求 Thread { Thread.sleep(500) if (isSuc) { //到达目的地 callback.onSuccess("arrive at the destination") } else { //发生了错误 callback.onError(IllegalArgumentException("Not at destination")) } }.start() awaitClose { log("awaitClose") } } Activity中,使用Flow.flatMapConcat对两者进行整合:mBtnCallbackFlow.setOnClickListener { lifecycleScope.launch { //将两个callbackFlow串联起来 先搜索目的地,然后到达目的地 mFlowModel.getSearchCallbackFlow() .flatMapConcat { mFlowModel.goDesCallbackFlow(it) }.collect { mTvCallbackFlow.text = it ?: "error" } } } 执行结果:2021-10-11 19:13:36.528 10233-10233/ E/TTT: arrive at the destination 以下结论摘自官网:与 flow 构建器不同,callbackFlow 允许通过 send 函数从不同 CoroutineContext 发出值,或者通过 offer/trySend 函数在协程外发出值。在协程内部,callbackFlow 会使用通道,它在概念上与阻塞队列非常相似。通道都有容量配置,限定了可缓冲元素数的上限。在 callbackFlow 中所创建通道的默认容量为 64 个元素。当您尝试向完整通道添加新元素时,send 会将数据提供方挂起,直到新元素有空间为止,而 offer 不会将相关元素添加到通道中,并会立即返回 false。
0
0
0
浏览量71
IT大鲨鱼

JUC系列学习(六):ReentrantReadWriteLock的使用及源码解析

ReentrantReadWriteLock的定义ReentrantReadWriteLock是一种读写锁,跟ReentrantLock一样也是实现了Lock,区别在于ReentrantLock是独占锁,同一时刻只能有一个线程持有锁,ReentrantLock在某些场景下可能会有并发性能的问题。而ReentrantReadWriteLock是独占锁(写锁)、共享锁(读锁)可以同时存在的一种读写锁,在读操作远大于写操作的场景中,能实现更好的并发性。当读锁存在时,其他线程仍然可以获取读锁并进行读操作,但是不能获得写锁进行写操作;当写锁存在时,其他线程的读锁、写锁都是不允许的。使用举例举个ReentrantReadWriteLock的使用例子:public class ReentrantReadWriteLockDemo { private static final String THREAD_READ = "读线程"; private static final String THREAD_WRITE = "写线程"; public static void main(String[] args) { Resource resource = new Resource(); //模拟三个线程去执行写操作 for (int i = 0; i < 3; i++) { new Thread(new Task(resource), THREAD_WRITE + i).start(); } //模拟10个线程去执行读操作 for (int i = 0; i < 10; i++) { new Thread(new Task(resource), THREAD_READ + i).start(); } } public static class Task implements Runnable { Resource resource; Task(Resource resource) { this.resource = resource; } @Override public void run() { String curThreadName = Thread.currentThread().getName(); Person person = new Person(curThreadName, new Random().nextInt(100)); if (curThreadName.startsWith(THREAD_READ)) { //读操作 resource.get(); } else if (Thread.currentThread().getName().startsWith(THREAD_WRITE)) { //写操作 resource.put(person, person.rank); } } } public static class Resource<K extends Comparable, V> { TreeMap<K, V> rankMap = new TreeMap<>(); final ReadWriteLock rwLock = new ReentrantReadWriteLock(); final Lock readLock = rwLock.readLock(); // 读取锁 final Lock writeLock = rwLock.writeLock(); // 写入锁 //写入值 void put(K key, V value) { try { writeLock.lock(); System.out.println(Thread.currentThread().getName() + "准备写入数据"); Thread.sleep(new Random().nextInt(500)); System.out.println(Thread.currentThread().getName() + "写入数据完毕:" + key.toString()); rankMap.put(key, value); } catch (Exception e) { e.printStackTrace(); } finally { writeLock.unlock(); } } //获取值 public List<K> get() { try { readLock.lock(); System.out.println(Thread.currentThread().getName() + "准备读取数据"); Thread.sleep(new Random().nextInt(500)); //treeMap中取出的数据是按rank从大到小排序的 List<K> list = new ArrayList<>(rankMap.keySet()); System.out.println(Thread.currentThread().getName() + "读取数据完毕:" + Arrays.toString(list.toArray())); return list; } catch (Exception e) { e.printStackTrace(); } finally { readLock.unlock(); } return null; } } static class Person implements Comparable<Person> { public String name;//姓名 public int rank;//得分 Person(String name, int rank) { this.name = name; this.rank = rank; } @Override public int compareTo(Person person) { //分数少的在后面 if (rank <= person.rank) { return 1; } return -1; } @Override public String toString() { return "name: " + name + ",rank: " + rank; } } } 执行结果:读线程4准备读取数据 读线程0准备读取数据 读线程3准备读取数据 读线程1准备读取数据 读线程2准备读取数据 读线程1读取数据完毕:[] 读线程4读取数据完毕:[] 读线程2读取数据完毕:[] 读线程3读取数据完毕:[] 读线程0读取数据完毕:[] 写线程1准备写入数据 写线程1写入数据完毕:name: 写线程1,rank: 83 写线程2准备写入数据 写线程2写入数据完毕:name: 写线程2,rank: 47 写线程0准备写入数据 写线程0写入数据完毕:name: 写线程0,rank: 55 读线程5准备读取数据 读线程6准备读取数据 读线程8准备读取数据 读线程9准备读取数据 读线程7准备读取数据 读线程8读取数据完毕:[name: 写线程1,rank: 83, name: 写线程0,rank: 55, name: 写线程2,rank: 47] 读线程9读取数据完毕:[name: 写线程1,rank: 83, name: 写线程0,rank: 55, name: 写线程2,rank: 47] 读线程6读取数据完毕:[name: 写线程1,rank: 83, name: 写线程0,rank: 55, name: 写线程2,rank: 47] 读线程7读取数据完毕:[name: 写线程1,rank: 83, name: 写线程0,rank: 55, name: 写线程2,rank: 47] 读线程5读取数据完毕:[name: 写线程1,rank: 83, name: 写线程0,rank: 55, name: 写线程2,rank: 47] 每次执行读写线程的顺序及数据可能不一样,但有一些结果是固定的:当有写线程操作时,其他线程不能进行任何操作,只能等写入完成后其他线程才能继续执行;但是当有读线程时,其他读线程同样可以执行读操作,但是此时不能进行写操作。源码解析UML类图: 如果熟悉ReentrantLock实现的话,看到上面的类图也会感觉很熟悉,没错,ReentrantReadWriteLock的底层也是通过AQS实现的,不同的是ReentrantLock只能用来做独占锁,而ReentrantReadWriteLock是独占锁(写锁)、共享锁(读锁)共存的一种锁,那么他是如何实现的呢?我们通过看其源码实现来一探究竟:public ReentrantReadWriteLock() { this(false); } public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); } public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; } public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; } abstract static class Sync extends AbstractQueuedSynchronizer {} ReentrantReadWriteLock构造方法中可以传入一个的boolean类型参数fair,表示是否是公平锁,默认是非公平锁,这里跟ReentrantLock一样。在使用ReentrantReadWriteLock时,分别通过writeLock()、readLock()获取对应的写锁、读锁,他们对应于ReentrantReadWriteLock的内部静态类WriteLock、ReadLock,来看对应的实现:public static class WriteLock implements Lock { private final ReentrantReadWriteLock.Sync sync; protected WriteLock(ReentrantReadWriteLock lock) { sync = lock.sync; } public void lock() { sync.acquire(1);//独占锁 } public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1);//独占锁 } public boolean tryLock() { return sync.tryWriteLock(); } public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } public void unlock() { sync.release(1);//释放独占锁 } public Condition newCondition() { return sync.newCondition(); } public boolean isHeldByCurrentThread() { return sync.isHeldExclusively(); } public int getHoldCount() { return sync.getWriteHoldCount(); } } public static class ReadLock implements Lock { private final ReentrantReadWriteLock.Sync sync; protected ReadLock(ReentrantReadWriteLock lock) { sync = lock.sync; } public void lock() { sync.acquireShared(1);//共享锁 } public void lockInterruptibly() throws InterruptedException { sync.acquireSharedInterruptibly(1);//共享锁 } public boolean tryLock() { return sync.tryReadLock(); } public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } public void unlock() { sync.releaseShared(1);//释放共享锁 } public Condition newCondition() { throw new UnsupportedOperationException(); } } WriteLock的实现类似于ReentrantLock,都是独占锁,通过state的0与大于等于1(大于1时是同一线程多次获取锁,即锁的重入性)来控制是否有线程占有锁;ReadLock的实现类似于Semaphore,都是共享锁,通过state的0与非0来控制多个线程的访问。既然ReentrantReadWriteLock既有独占锁,又有共享锁,那么ReentrantReadWriteLock又是如何管理两者的呢?读锁与写锁的关系我们知道了读锁、写锁都是通过AQS中的state来控制线程的访问,其中WriteLock通过Sync的tryAcquire()、ReadLock通过Sync的tryAcquireShared()来尝试获取锁,我们直接看两者获取锁的实现:static final int SHARED_SHIFT = 16; static final int SHARED_UNIT = (1 << SHARED_SHIFT); static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; //共享锁持有的数量 static int sharedCount(int c) { return c >>> SHARED_SHIFT; } //独占锁持有的数量 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); if (c != 0) { //1、如果读锁数量非空或者写锁数量非空并且持有者不是当前线程,直接返回,写锁获取失败,后续会加入到等待队列中 // (Note: if c != 0 and w == 0 then shared count != 0) if (w == 0 || current != getExclusiveOwnerThread()) return false; //2、如果当前持有数量超过最大值(65535),抛出异常 if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); //3、如果该线程是重入获取或队列策略允许获取,则该线程就会尝试获取锁并更新当前锁持有的线程 setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; } protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); //1、如果当前锁被其他线程的写锁持有,直接返回,获取读锁失败。 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; int r = sharedCount(c);//获取当前读锁的数量 if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { //2、当前读锁不阻塞,且小于最大读锁数量,通过CAS尝试获取读锁 if (r == 0) {//当前线程第一个并且第一次获取读锁, firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { //当前线程是第一次获取读锁的线程 firstReaderHoldCount++; } else { // 当前线程不是第一个获取读锁的线程,放入线程本地变量 HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } //3、获取读锁成功后,会通过readHolds(ThreadLocalHoldCounter)来记录当前读锁所在线程的锁获取次数信息,本质上是通过ThreadLocal来保存一个Int变量来统计的。 return 1; } return fullTryAcquireShared(current); } 写锁WriteLock在尝试获取锁时,首先通过AQS中的getState()获取state值,然后通过exclusiveCount(int)对state做了一次操作:static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } EXCLUSIVE_MASK的值是65535(2的16次方减1),即state与低16位1做与操作,结果是state的高16位都会变成0,低16位的值作为其返回值,代表独占锁持有的数量。既然写锁用了state的低16位,那么读锁是不是就用了state的高16位呢?来看下读锁ReadLock通过sharedCount(int)操作的state:static int sharedCount(int c) { return c >>> SHARED_SHIFT; } 果然,读锁中对state做右移16位的操作,即去掉了低16位,高16位的十进制数作为其返回值,代表共享读锁持有的数量。最终通过一个state变量实现了对读锁和写锁的控制。ReentrantReadWriteLock的写锁饥饿问题ReentrantReadWriteLock适用于读多写少的场景,我们知道当读锁存在的时候,写锁只能进入队列等待,那么如果队列前面有大量的读操作等待时,后面的写操作也只能等待前面的读操作都执行完才能执行写操作,所以可能会产生写操作很久得不到执行,数据不能更新,发生写锁饥饿的情况。如何优化呢?ReadLock可以认为是一个悲观读锁,这里的悲观是针对WriteLock来说的,即ReadLock存在时不允许WriteLock进行写操作,因为写操作会改变数据源,进而影响读操作。那么能不能换个思路来想这个问题,读操作可以分为乐观读锁和悲观读锁,乐观读锁认为读操作时不会有写操作来改变数据,所以乐观读锁在读操作时并不会真正的去加锁,读操作时允许写操作执行,等读操作执行完再去校验数据的一致性;悲观读锁恰恰相反,一开始就会进行加锁,不允许读操作和写操作同时进行。首先读操作先采用乐观读锁,即开始读操作不进行加锁,只是在读之前先获取数据对应的版本号,然后将数据copy一份到读线程中,读操作结束后,通过数据版本判断当前读数据是否有效(在读操作进行时可能会有写线程去改变数据),如果有效,可以直接使用;否则说明在乐观读操作时有写操作改变了数据,那么使用悲观读锁ReadLock进行加锁,再重新去读数据,此时拿到的一定是最新的数据。上述的思路已经在JDK1.8 引入的StampedLock实现了,其执行多操作流程大致如下:StampedLock lock = new StampedLock(); long stamp = lock.tryOptimisticRead(); //非阻塞获取版本信息 copyVaraibale2ThreadMemory();//拷贝变量到线程本地堆栈 if(!lock.validate(stamp)){ // 校验 long stamp = lock.readLock();//获取读锁 try { copyVaraibale2ThreadMemory();//拷贝变量到线程本地堆栈 } finally { lock.unlock(stamp);//释放悲观锁 } } 因为读操作远大于写操作,StampedLock中不加锁的读操作效率会更高,同时也能避免WriteLock长时间得不到执行、发生写锁饥饿的情况。总结ReentrantReadWriteLock可以使一个资源同一时间被多个读线程访问,或者被一个写线程访问,但是两者不能同时进行。内部通过ReadLock实现读锁,通过WriteLock实现写锁readLock.lock():· 当有其他写线程在执行时(持有写锁),读锁获取会失败直到其他写线程释放了写锁;· 本线程一旦获取了读锁,其他线程的写锁都不能获取只能等待所有的读锁都释放后才能尝试获取写锁writeLock.lock():· 当其他任何线程如果持有读锁或写锁时,本线程获取写锁失败直到其他线程释放了所有的读锁和写锁,本线程才有机会尝试获取写锁· 一旦本线程获取了写锁,其他线程将不被允许获取任何读锁和写锁,直到本线程释放了写锁。
0
0
0
浏览量2008
IT大鲨鱼

JNI 编程上手指南之从内存角度再看引用类型

1. Java 程序使用的内存Java 程序使用的内存从逻辑上可以分为两个部分:Java MemoryNative MemoryJava Memory 就是我们的 Java 程序使用的内存,通常从逻辑上区分为栈和堆。方法中的局部变量通常存储在栈中,引用类型指向的对象一般存储在堆中。Java Memory 由 JVM 分配和管理,JVM 中通常会有一个 GC 线程,用于回收不再使用的内存。Java 程序的执行依托于 JVM ,JVM 一般使用 C/C++ 代码编写,需要根据 Native 编程规范去操作内存。如:C/C++ 使用 malloc()/new 分配内存,需要手动使用 free()/delete 回收内存。这部分内存我们称为 Native Memory。Java 中的对象对应的内存,由 JVM 来管理,他们都有自己的数据结构。当我们通过 JNI 将一个 Java 对象传递给 Native 程序时,Native 程序要操作这块内存时(即操作这个对象),就需要了解这个数据结构,显然这有点麻烦了,所以 JVM 的设计者在 JNIenv 中定义了很多函数(NewStringUTF,FindClass,NewObject 等)来帮你操作和构造这些对象。同时也提供了引用类型(jobject、jstring、jclass、jarray、jintArray等)来引用这些对象。2. 内存角度的 JNI 引用类型之前我们介绍了,JNI 引用类型有三种:Local Reference、Global Reference、Weak Global Reference。接下来我们就从内存的角度来进一步解析这三类引用。首先,我们需要明确的是引用类型是指针,指向的是 Java 中的对象在 JVM 中对应的内存。引用类型的定义如下:#ifdef __cplusplus class _jobject {}; class _jobject {}; class _jclass : public _jobject {}; class _jthrowable : public _jobject {} class _jstring : public _jobject {}; class _jarray : public _jobject {}; class _jbooleanArray : public _jarray {}; class _jbyteArray : public _jarray {}; class _jcharArray : public _jarray {}; class _jshortArray : public _jarray {}; class _jintArray : public _jarray {}; class _jlongArray : public _jarray {}; class _jfloatArray : public _jarray {}; class _jdoubleArray : public _jarray {}; class _jobjectArray : public _jarray {}; typedef _jobject *jobject; typedef _jclass *jclass; typedef _jthrowable *jthrowable; typedef _jstring *jstring; typedef _jarray *jarray; typedef _jbooleanArray *jbooleanArray; typedef _jbyteArray *jbyteArray; typedef _jcharArray *jcharArray; typedef _jshortArray *jshortArray; typedef _jintArray *jintArray; typedef _jlongArray *jlongArray; typedef _jfloatArray *jfloatArray; typedef _jdoubleArray *jdoubleArray; typedef _jobjectArray *jobjectArray; #else struct _jobject; typedef struct _jobject *jobject; typedef jobject jclass; typedef jobject jthrowable; typedef jobject jstring; typedef jobject jarray; typedef jarray jbooleanArray; typedef jarray jbyteArray; typedef jarray jcharArray; typedef jarray jshortArray; typedef jarray jintArray; typedef jarray jlongArray; typedef jarray jfloatArray; typedef jarray jdoubleArray; typedef jarray jobjectArray; #endif 不是以上类型的指针就不是 JNI 引用类型,比如容易混淆的 jmethod jfield 都不是 JNI 引用类型。JNI 引用类型是指针,但是和 C/C++ 中的普通指针不同,C/C++ 中的指针需要我们自己分配和回收内存(C/C++ 使用 malloc()/new 分配内存,需要手动使用 free()/delete 回收内存)。JNI 引用不需要我们分配和回收内存,这部分工作由 JVM 完成。我们额外需要做的工作是在 JNI 引用类型使用完后,将其从引用表中删除,防止引用表满了。接下来我们就从内存角度分类解析三种类型引用类型。2.1 局部引用(Local Reference)通过 JNI 接口从 Java 传递下来或者通过 NewLocalRef 和各种 JNI 接口(FindClass、NewObject、GetObjectClass和NewCharArray等)创建的引用称为局部引用。当从 Java 环境切换到 Native 环境时,JVM 分配一块内存用于创建一个 Local Reference Table,这个 Table 用来存放本次 Native Method 执行中创建的所有局部引用(Local Reference)。每当在 Native 代码中引用到一个 Java 对象时,JVM 就会在这个 Table 中创建一个 Local Reference。比如,我们调用 NewStringUTF() 在 Java Heap 中创建一个 String 对象后,在 Local Reference Table 中就会相应新增一个 Local Reference。对于开发者来说,Local Reference Table 是不可见的,Local Reference Table 的内存不大,所能存放的 Local Reference 数量也是有限的(在 Android 中默认最大容量是512个)。在开发中应该及时使用 DeleteLocalRef( )删除不必要的 Local Reference,不然可能会出现溢出错误。很多人会误将 JNI 中的 Local Reference 理解为 Native Code 的局部变量。这是错误的:局部变量存储在线程堆栈中,而 Local Reference 存储在 Local Ref 表中。局部变量在函数退栈后被删除,而 Local Reference 在调用 DeleteLocalRef() 后才会从 Local Ref 表中删除,并且失效,或者在整个 Native Method 执行结束后被删除。可以在代码中直接访问局部变量,而 Local Reference 的内容无法在代码中直接访问,必须通过 JNI function 间接访问。JNI function 实现了对 Local Reference 的间接访问,JNI function 的内部实现依赖于具体 JVM。2.2 全局引用(Global Reference)Global Reference 是通过 JNI 函数 NewGlobalRef() 和D eleteGlobalRef() 来创建和删除的。 Global Reference 具有全局性,可以在多个 Native Method 调用过程和多线程中使用。使用 Global reference时,当 native code 不再需要访问 Global reference 时,应当调用 JNI 函数 DeleteGlobalRef() 删除 Global reference 和它引用的 Java 对象。否则 Global Reference 引用的 Java 对象将永远停留在 Java Heap 中,从而导致 Java Heap 的内存泄漏。2.3 弱全局引用(Weak Global Reference)弱全局引用使用 NewWeakGlobalRef() 和 DeleteWeakGlobalRef() 进行创建和删除,它与 Global Reference 的区别在于该类型的引用随时都可能被 GC 回收。对于 Weak Global Reference 而言,可以通过 isSameObject() 将其与 NULL 比较,看看是否已经被回收了。如果返回 JNI_TRUE,则表示已经被回收了,需要重新初始化弱全局引用。Weak Global Reference 的回收时机是不确定的,有可能在前一行代码判断它是可用的,后一行代码就被 GC 回收掉了。为了避免这事事情发生,JNI官方给出了正确的做法,通过 NewLocalRef() 获取 Weak Global Reference,避免被GC回收。
0
0
0
浏览量2009
IT大鲨鱼

Android Jetpack系列之MVVM使用及封装(续)

前情提要在前一篇 Android Jetpack系列之MVVM使用及封装 文章中,介绍了常用的MVC、MVP、MVVM架构及其对MVVM的封装使用,其中MVVM的主旨可以理解为数据驱动:Repository提供数据,ViewModel中发送数据,UI层使用的LiveData订阅数据,当有数据变化时会主动通知UI层进行刷新。接下来继续讨论LiveData的局限性以及google推荐的UI层订阅数据方式。LiveData的缺点在学习LiveData时,我们知道通过LiveData可以让数据被观察,且具备生命周期感知能力,但LiveData的缺点也很明显:· LiveData的接收只能在主线程;· LiveData发送数据是一次性买卖,不能多次发送;· LiveData发送数据的线程是固定的,不能切换线程,setValue/postValue本质上都是在主线程上发送的。当需要来回切换线程时,LiveData就显得无能为力了。除了使用LiveData,还可以采用Flow替换,Flow是google官方提供的一套基于kotlin协程的响应式编程模型。常用的Flow有StateFlow、SharedFlow,详细使用参见:Android Kotlin之Flow数据流。Lifecycle.repeatOnLifecycle、Flow.flowWithLifecycle订阅数据StateFlow 和 LiveData 具有相似之处。两者都是可观察的数据容器类,并且在应用架构中使用时,两者都遵循相似模式。但两者还是有不同之处的:StateFlow 需要将初始状态传递给构造函数,而 LiveData 不需要。当 View 进入 STOPPED 状态时,LiveData.observe() 会自动取消注册使用方,而从 StateFlow 或任何其他数据流收集数据的操作并不会自动停止,即App已经切到后台了,而UI层可能还会继续订阅数据,这样可能会存在隐患。如需保证App只在前台时订阅数据,需要从 Lifecycle.repeatOnLifecycle或Flow.flowWithLifecycle 块收集数据流。google在 使用更为安全的方式收集 Android UI 数据流中给的例子:class LocationActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { // 单次配置任务 val expensiveObject = createExpensiveObject() lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { // 在生命周期进入 STARTED 状态时开始重复任务,在 STOPED 状态时停止 // 对 expensiveObject 进行操作 } // 当协程恢复时,`lifecycle` 处于 DESTROY 状态。repeatOnLifecycle 会在 // 进入 DESTROYED 状态前挂起协程的执行 } }}或者class LocationActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) locationProvider.locationFlow() .flowWithLifecycle(this, Lifecycle.State.STARTED) .onEach { // 新的位置!更新地图 } .launchIn(lifecycleScope) }}其中Flow.flowWithLifecycle内部也是通过Lifecycle.repeatOnLifecycle实现的,上述例子中会在生命周期进入 STARTED 状态时开始重复任务,在 STOPED 状态时停止操作,如果觉得使用起来写的重复代码太多,可以简单对Flow.flowWithLifecycle封装一下:inline fun <T> Flow<T>.flowWithLifecycle2( lifecycleOwner: LifecycleOwner, minActiveState: Lifecycle.State = Lifecycle.State.STARTED, crossinline block: suspend CoroutineScope.(T) -> Unit,) = lifecycleOwner.lifecycleScope.launch { //前后台切换时可以重复订阅数据。如:Lifecycle.State是STARTED,那么在生命周期进入 STARTED 状态时开始任务,在 STOPED 状态时停止订阅 flowWithLifecycle(lifecycleOwner.lifecycle, minActiveState).collect { block(it) }}UI层使用如下:mViewModel.loadingFlow.flowWithLifecycle2(this, Lifecycle.State.STARTED) { isShow -> mStatusViewUtil.showLoadingView(isShow) }嗯,看上去简洁了一些。事件分类导致的新问题UI层订阅的事件通常分成两种:· 一种是同样的事件可以多次消费:比如UI的刷新,多次执行没有任何问题;· 另一种是同样的事件只能消费一次,多次执行可能会有问题:比如Loading弹窗、跳转、播放音乐等。针对第二种情况,写一个简单的例子: //UI层 mBtnQuest.setOnClickListener { mViewModel.getModelByFlow() } lifecycleScope.launch { mViewModel.mIntFlow .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .collect { value -> log("collect here: $value") //......其他...... } } //ViewModel层 private val _intFlow = MutableStateFlow<Int>(-1) val mIntFlow = _intFlow fun getModelByFlow() { viewModelScope.launch { intFlow.emit(1) } }打开当前页面时,log如下:2022-05-08 21:34:17.775 3482-3482/org.ninetripods.mq.study E/TTT: collect here: -1StateFlow的默认值 -1 会先发送到UI层,点击Button之后:bash复制代码2022-05-08 21:34:22.921 3482-3482/org.ninetripods.mq.study E/TTT: collect here: 1ViewModel中发送了1并被UI层接收。一切都很正常,此时我们把App切到后台再切回来:2022-05-08 21:38:01.597 3482-3482/org.ninetripods.mq.study E/TTT: collect here: 1可以看到UI层又接收了一遍,这是因为不管是Lifecycle.repeatOnLifecycle或Flow.flowWithLifecycle ,切换前后台时,当Lifecycle处于STOPED状态,会挂起调用它的协程;并会在进入STARTED状态时重新执行协程。如果此时UI层是播放语音且需求是只播放一次,那么这里就会有问题了,每次切换前后台都会再播一次,不符合需求了,那么怎么办呢?接着往下看。避免UI层重复订阅第一种方式:ChannelFlow底层使用的Channel机制实现,StateFlow、SharedFlow都是一对多的关系,如果上游发送者与下游UI层的订阅者是一对一的关系,可以使用Channel来实现,Channel默认是粘性的。Channel使用场景:一次性消费场景,如上面说的播放背景音乐,需求是在UI层只播一次,即使App切到后台再切回来,也不会重复播放。Channel使用特点:· 每个消息只有一个订阅者可以收到,用于一对一的通信· 第一个订阅者可以收到collect之前的事件,即粘性事件Channel使用举例://viewModel中private val _loadingChannel = Channel<Boolean>()val loadingFlow = _loadingChannel.receiveAsFlow()private suspend fun loadStart() { _loadingChannel.send(true)}private suspend fun loadFinish() { _loadingChannel.send(false)}//UI层接收Loading信息 lifecycleScope.launch { mViewModel.loadingFlow .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .collect { isShow -> mStatusViewUtil.showLoadingView(isShow) } }通过Channel.receiveAsFlow()可以将Channel转化为Flow使用,Channel是一对一的关系,且下游消费完之后事件就没了,切换前后台也不会再重复消费事件了,达到了我们的要求。第二种方式:改造Flow.flowWithLifecycle还有一种写法,是对Flow.flowWithLifecycle改造一下,系统默认的实现如下:@OptIn(ExperimentalCoroutinesApi::class)public fun <T> Flow<T>.flowWithLifecycle( lifecycle: Lifecycle, minActiveState: Lifecycle.State = Lifecycle.State.STARTED): Flow<T> = callbackFlow { lifecycle.repeatOnLifecycle(minActiveState) { this@flowWithLifecycle.collect { send(it) } } close()}改为下面的方式:/** * NOTE: 如果不想对UI层的Lifecycle.repeatOnLifecycle/Flow.flowWithLifecycle在前后台切换时重复订阅,可以使用此方法; * 效果类似于Channel,不过Channel是一对一的,而这里是一对多的 */fun <T> Flow<T>.flowOnSingleLifecycle( lifecycle: Lifecycle, minActiveState: Lifecycle.State = Lifecycle.State.STARTED, isFirstCollect: Boolean = true,): Flow<T> = callbackFlow { var lastValue: T? = null lifecycle.repeatOnLifecycle(minActiveState) { this@flowOnSingleLifecycle.collect { if ((lastValue != null || isFirstCollect) && (lastValue != it)) { send(it) } lastValue = it } } lastValue = null close()}本质上是保存了上次的值lastValue,如果再次订阅时会跟上次的值进行对比,只有值不一样时才会继续接收,从而达到跟Channel类似的效果,不过Channel是一对一的,而这里是一对多的。
0
0
0
浏览量2008
IT大鲨鱼

Kotlin中使用Java数据类时引发的一个Bug

基础复习:Kotlin语言中的对象比较· 比较对象的内容是否相等 (== 或者 equals ):Kotlin 中的操作符 == 和 equals效果相同 ,都用于比较对象的内容是否相等, Kotlin中建议直接使用 ==。· 比较对象的引用是否相等 ( === ):Kotlin 中的操作符 === 用于比较对象的引用是否指向同一个地址,运行时如果是基本数据类型 === 等价于 ==。背景 如图效果,通过RecyclerView实现,每次通过对每个Item前后数据进行对比来确定执行什么操作(如Item的insert、update、delete等),这里使用RecyclerView库中的DiffUtil.Callback()来进行的前后数据对比,如下示例:class DataDiffUtil(private val oldModels: List<Any>, private val newModels: List<Any>) :DiffUtil.Callback() { /** * 旧数据 */ override fun getOldListSize(): Int = oldModels.size /** * 新数据 */ override fun getNewListSize(): Int = newModels.size /** * DiffUtil调用来决定两个对象是否代表相同的Item。true表示两个Item相同(表示View可以复用),false表示不相同(View不可以复用) * 例如,如果你的项目有唯一的id,这个方法应该检查它们的id是否相等。 */ override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldModels[oldItemPosition]::class.java == newModels[newItemPosition]::class.java } /** * 比较两个Item是否有相同的内容(用于判断Item的内容是否发生了改变), * 该方法只有当areItemsTheSame (int, int)返回true时才会被调用。 */ override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldModels[oldItemPosition] == newModels[newItemPosition] } /** * 该方法执行时机:areItemsTheSame(int, int)返回true 并且 areContentsTheSame(int, int)返回false * 该方法返回Item中的变化数据,用于只更新Item中变化数据对应的UI */ override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { return super.getChangePayload(oldItemPosition, newItemPosition) }}以顶部的Item1 模块举例,当服务端有新数据来时,通过下面方式进行更新:/** * use[DiffUtil] 增量更新数据 * @param newList 新数据 */fun submitList(newList: MutableList<Any>) { //传入新旧数据进行比对 val diffUtil = DataDiffUtil(mModels, newList) //经过比对得到差异结果 val diffResult = DiffUtil.calculateDiff(diffUtil) //NOTE:注意这里要重新设置Adapter中的数据 setModels(newList) //将数据传给adapter,最终通过adapter.notifyItemXXX更新数据 diffResult.dispatchUpdatesTo(this)}如果Item1前后数据是一样的,那么DiffUtil.Callback#areContentsTheSame() 中的oldModels[oldItemPosition] == newModels[newItemPosition] 理论上返回的就是true,Item1 模块也不会执行刷新操作了。实际跑起来能按我们的预期走吗?问题出现上述逻辑写的差不多了,还差Model数据类没有写出来,因为项目中是Kotlin & Java混用的,而Model数据类正好是用Java语言编写的:public class VP2Model implements Serializable { public int id; public String content;}看上去一切都是OK的,但是运行之后发现出问题了,即使前后数据完全一样,仍然会进行Item1的刷新,说明DiffUtil.Callback#areContentsTheSame()里的数据对比返回的是false,通过断点发现确实返回了false。到这里不知道大家有没有发现问题所在?开始以为是数据变了,但是通过Log打点发现前后数据是一样的,那么明明是一样的,为什么对比会是不同呢?仔细一想明白了,问题出在Java语言上,出在VP2Model类中没有重新equals()方法:@Overridepublic boolean equals(@Nullable Object obj) { return super.equals(obj);}Java Model 类默认的equals()方法是比较的对象内存地址,刷新前后生成的显然不是同一个对象,那么前后地址对比返回的肯定是false了,问题就出在了这里!如果我们使用 Kotlin 语言编写 Model 类就不会有这个问题,因为 Kotlin 编译器自动帮我们重写了equals()/hashCode()方法,如:data class VP2Model( val id: Int = 0, val content: String = "",)注意这里要用data class开头才行,上述代码转换成Java后: 可以看到 Kotlin 编写的 Model 类自动帮我们实现了其中的equals()/hashCode()方法。解决方式已经知道问题出现的原因,那么解决方式就很简单了,比如下面几种解决方式:方式一重写Java Model 类中的 equals()方法,对每个字段进行对比,字段都相同返回 true,否则返回 false。有一种快捷生成方式,在Mac版的 AS 中,可以使用 command + N 的方式生成,如下:生成结果:方式二在使用的地方用 Kotlin语言编写Model 类进行转换,注意:这里一定要用data class 开头的声明,因为 Kotlin 编译器会自动帮我们重写 equals()/hashCode() 方法。
0
0
0
浏览量2008
IT大鲨鱼

Kotlin内联函数inline、noinline、crossinline

高阶函数首先来看下kotlin里的高阶函数定义:如果一个函数接收另一个函数作为参数,或返回类型是一个函数类型,那么该函数被称为是高阶函数。比如下面的代码:private fun highFuc(name: String, block: (String) -> Unit) { block(name) } 其中highFuc是函数名,函数中传入了2个参数,第一个参数为String类型,第二个参数即是函数类型,->左边的部分用来声明该函数接收什么参数的,多个参数之间用逗号隔开,如果没有参数直接使用()表示就可以了;->右边表示该函数的返回值是什么类型,如果没有返回值直接使用Unit即可。内联函数内联函数,顾名思义,就是在编译时将作为函数参数的函数体直接映射到函数调用处,直接用一个例子来说明:fun requestInfo() { getStr() } fun getStr() { println("inline") } 很简单,getStr()中打印了一个字符串,然后requestInfo()中调用了getStr()函数,将上述代码转换成java代码之后:public final void requestInfo() { this.getStr(); } public final void getStr() { String var1 = "inline"; System.out.println(var1); } 继续,在getStr()的前面加上inline声明,如下:fun requestInfo() { getStr() } //普通函数中并不推荐加inline关键字 inline fun getStr() { println("inline") } 转换成java之后:public final void requestInfo() { String var3 = "inline"; System.out.println(var3); } 可以看到转换成java之后的代码有明显的区别:加上inline之后,getStr()中的函数内容直接“复制粘贴”到requestInfo()中,即内联到函数调用处了。inline通过上面的例子,inline的作用就很明显了,就是在编译时直接将函数内容直接复制粘贴到调用处。我们知道函数调用最终是通过JVM操作数栈的栈帧完成的,每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程,使用了inline关键字理论上可以减少一个栈帧层级。那么是不是所有的函数前面都适合加上inline关键字了呢?答案是否定的,其实JVM本身在编译时,就支持函数内联,并不是kotlin中特有的,那么kotlin中什么样的函数才需要使用inline关键字呢?答:高阶函数!只有高阶函数中才需要inline去做内联优化,普通函数并不需要,如果在普通函数强行加上inline,编辑器会立刻提醒:Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types 意思是 内联对普通函数性能优化的预期影响是微不足道的。内联最适合带有函数类型参数的函数为什么高阶函数要使用inlineinline优化了什么问题呢?因为我们使用的Lambda表示式在编译转换后被换成了匿名类的实现方式。fun requestInfo() { highFuc("inline") { str -> println(str) } } fun highFuc(name: String, block: (String) -> Unit) { block(name) } 转换成java之后:public final void requestInfo() { this.highFuc("inline", (Function1)null.INSTANCE); } private final void highFuc(String name, Function1 block) { block.invoke(name); } public interface Function1<in P1, out R> : Function<R> { public operator fun invoke(p1: P1): R } 所以函数参数最终会转换成interface,并通过创建一个匿名实例来实现。这样就会造成额外的内存开销。为了解决这个问题,kotlin引入inline内联功能,将Lambda表达式带来的性能开销消除。还是上面的例子,这次我们对高阶函数添加inline关键字:fun requestInfo() { highFuc("inline") { str -> println(str) } } //注意:这里添加了inline关键字 inline fun highFuc(name: String, block: (String) -> Unit) { block(name) } 转换成java之后:public final void requestInfo() { String name$iv = "inline"; System.out.println(name$iv); } noinline当函数被inline标记时,使用noinline可以使函数参数不被内联。fun requestInfo() { highFuc({ println("noinline") }, { println("inline") }) } //highFuc被inline修饰,而函数参数block0()使用了noinline修饰 inline fun highFuc(noinline block0: () -> Unit, block1: () -> Unit) { block0() block1() } 转换成java之后:public final void requestInfo() { Function0 block0$iv = (Function0)null.INSTANCE; block0$iv.invoke(); String var5 = "inline"; System.out.println(var5); } 结果也很明显,block0()函数没有被内联,而block()函数被内联,这就是noinline的作用。如果想在非内联函数Lambda中直接return怎么办?比如我想这么写:fun requestInfo() { highFuc { return //错误,不允许在非内联函数中直接return } } fun highFuc(block: () -> Unit) { println("before") block() println("after") } 对不起,不允许!会直接在return的地方报**'return' is not allowed here**错误。 但是可以写成return@highFuc,即:fun requestInfo() { highFuc { return@highFuc //正确,局部返回 } } fun highFuc(block: () -> Unit) { println("before") block() println("after") } 其中return是全局返回,会影响Lambda之后的执行流程;而return@highFuc是局部返回,不会影响Lambda之后的执行流程。如果我就想全局返回,那么可以通过inline来进行声明:fun requestInfo() { highFuc { return } } inline fun highFuc(block: () -> Unit) { println("before") block() println("after") } 因为highFuc通过inline声明为内联函数,所以调用方可以直接使用return进行全局返回,执行requestInfo()的结果:before 可以看到Lambda之后的after并没有被执行,因为是全局返回,当然可以改成return@highFuc局部返回,这样就可以都执行了。结论:内联函数所引用的Lambda表达式中是可以使用return关键字来进行函数返回的,而非内联函数只能进行局部返回。现在有一种场景,我既想使用inline优化高阶函数,同时又不想调用方打断我的执行流程(因为inline是支持全局return的),貌似冲突了,这时候怎么办呢,这时候就需要crossinline了。crossinline允许inline内联函数里的函数类型参数可以被间接调用,但是不能在Lambda表达式中使用全局return返回。fun requestInfo() { highFuc { return //错误,虽然是inline内联函数,但Lambda中使用crossinline修饰,所以不允许全局返回了 } } inline fun highFuc(crossinline block: () -> Unit) { println("before") block() println("after") } crossinline关键字就像一个契约,它用于保证内联函数的Lambda表达式中一定不会使用return全局返回,这样就不会冲突了。当然return@highFuc局部返回还是可以的。总结inline:编译时直接将函数内容直接复制粘贴到调用处。noinline:当函数被inline标记时,使用noinline可以使函数参数不被内联。crossinline: 允许内联函数里的函数类型参数可以被间接调用,但是不能在Lambda表达式中使用全局return返回
0
0
0
浏览量1422
IT大鲨鱼

JNI 编程上手指南之多线程

核心要点JNI 环境下,进行多线程编程,有以下两点是需明确的:JNIEnv 是一个线程作用域的变量,不能跨线程传递,每个线程都有自己的 JNIEnv 且彼此独立局部引用不能在本地函数中跨函数使用,不能跨线前使用,当然也不能直接缓存起来使用示例程序示例程序主要演示:如何在子线程获取到属于子线程自己的 JNIEnv上面说了局部引用不能再线程之间直接传递,所以我们只有另觅他法。Java 层:public void javaCallback(int count) { Log.e(TAG, "onNativeCallBack : " + count); } public native void threadTest(); Native 层:static int count = 0; JavaVM *gJavaVM = NULL;//全局 JavaVM 变量 jobject gJavaObj = NULL;//全局 Jobject 变量 jmethodID nativeCallback = NULL;//全局的方法ID //这里通过标志位来确定 两个线程的工作都完成了再执行 DeleteGlobalRef //当然也可以通过加锁实现 bool main_finished = false; bool background_finished = false; static void *native_thread_exec(void *arg) { LOGE(TAG, "nativeThreadExec"); LOGE(TAG, "The pthread id : %d\n", pthread_self()); JNIEnv *env; //从全局的JavaVM中获取到环境变量 gJavaVM->AttachCurrentThread(&env, NULL); //线程循环 for (int i = 0; i < 5; i++) { usleep(2); //跨线程回调Java层函数 env->CallVoidMethod(gJavaObj, nativeCallback, count++); } gJavaVM->DetachCurrentThread(); background_finished = true; if (main_finished && background_finished) { env->DeleteGlobalRef(gJavaObj); LOGE(TAG, "全局引用在子线程销毁"); } return ((void *) 0); } extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_threadTest(JNIEnv *env, jobject thiz) { //创建全局引用,方便其他函数或线程使用 gJavaObj = env->NewGlobalRef(thiz); jclass clazz = env->GetObjectClass(thiz); nativeCallback = env->GetMethodID(clazz, "javaCallback", "(I)V"); //保存全局 JavaVM,注意 JavaVM 不是 JNI 引用类型 env->GetJavaVM(&gJavaVM); pthread_t id; if (pthread_create(&id, NULL, native_thread_exec, NULL) != 0) { return; } for (int i = 0; i < 5; i++) { usleep(20); //跨线程回调Java层函数 env->CallVoidMethod(gJavaObj, nativeCallback, count++); } main_finished = true; if (main_finished && background_finished && !env->IsSameObject(gJavaObj, NULL)) { env->DeleteGlobalRef(gJavaObj); LOGE(TAG, "全局引用在主线程销毁"); } } 示例代码中,我们的子线程需要使用主线程中的 jobject thiz,该变量是一个局部引用,不能赋值给一个全局变量然后跨线程跨函数使用,我们通过 NewGlobalRef 将局部引用装换为全局引用并保存在全局变量 jobject gJavaObj 中,在使用完成后我们需要使用 DeleteGlobalRef 来释放全局引用,因为多个线程执行顺序的不确定性,我们使用了标志位来确保两个线程所有的工作完成后再执行释放操作。JNIEnv 是一个线程作用域的变量,不能跨线程传递,每个线程都有自己的 JNIEnv 且彼此独立,实际开发中,我们通过以下代码:JavaVM *gJavaVM = NULL; //主线程获取到 JavaVM env->GetJavaVM(&gJavaVM); //子线程通过 JavaVM 获取到自己的 JNIEnv JNIEnv *env; gJavaVM->AttachCurrentThread(&env, NULL); 在子线程中获取到 JNIEnv。JavaVM 是一个普通指针,由 JVM 来管理其内存的分配与回收,不是 JNI 引用类型,所以 我们可以把它赋值给一个全局变量,直接用,也不用考虑他的内存分配与后手问题。
0
0
0
浏览量1204
IT大鲨鱼

Kotlin 作用域函数之let、with、run、also、apply的使用笔记

作用域函数Kotlin 标准库包含几个函数,目的是在对象的上下文中执行代码块。当对一个对象调用这样的函数并提供一个 lambda 表达式时,会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称。这些函数称为作用域函数。共有以下五种:let、run、with、apply 以及 also。作用域函数区别引用上下文对象的方式引用上下文对象的方式作用域函数itlet、 also 可以用it ,主要用做lambda表达式中的参数thisrun、  with、 apply 通过this引用上下文对象,当访问类中的变量或方法时,可以使用this,也可以省略函数返回值返回值作用域函数返回上下文对象apply、 also返回上下文对象返回表达式结果let 、run、 with 返回lambda表达式结果,其中表达式也可以没有结果letlet常用的两个作用:一是定义对象在特定的作用域范围内,另一个更常用的场景是对对象执行 xxx ?.let{ },在 lambda 表达式中执行操作,以一种更优雅的形式来判空。 fun letFunc() { //let{}中可以使用it来代表intList对象 val intList: List<Int> = arrayListOf(1, 2, 3) intList.let { println(it.size) //3 println(it.lastIndex) //2 } //通过?.let{}进行判断,如何为空,后面就不会再执行 val list2: List<Int>? = null list2?.let { println(it.size) } } with fun withExam(person: Person = Person("xmkp", 18, 1)) { //将person对象以参数的方式传递到Lambda表达式中 with(person) { println(name) println(age) println(sex) } } data class Person(var name: String, var age: Int, var sex: Int) 非扩展函数with(){}的使用,一般不需要用返回值, 而是将上下文对象作为参数传递到Lambda表达式中,典型应用是对一个对象在Lambda表达式中调用它的多个函数时使用,如with(RecyclerView.Holder){} 。runrun和with的作用基本一致,run可以以扩展函数的方式调用,通用场景:当表达式中同时包含对象初始化及返回值计算时使用。 fun runExam() { val list = arrayListOf(1, 2, 3) val size = list.run { list.add(4) //添加一条数据 list.size //返回最终的size } println("list.size:$size") //执行结果:list.size:4 } alsoalso通常执行将上下文对象作为参数的操作,返回值是上下文对象本身。lambda表达式中用it来表示上下文对象,可以将also理解为并且用该对象执行以下操作 fun alsoExam() { val list = arrayListOf(1, 2, 3) list.also { println("添加前:$list") }.add(4) println("添加后:$list") } //执行结果: // 添加前:[1, 2, 3] // 添加后:[1, 2, 3, 4] applylambda表达式中不返回值,且主要是对对象成员进行操作的场景使用apply。典型使用场景:对象的配置,可以将apply理解为将以下赋值应用于对象。 fun applyExam() { val person = Person().apply { //给Person对象设置属性 name = "xxx" age = 30 sex = 1 } println(person) // 执行结果:Person(name=xxx, age=30, sex=1) } 总结作用域函数上下文对象返回值使用场景letit表达式结果,也可不返回1、定义对象在特定的作用域范围内; 2、对象执行 xxx ?.let{ }来判空withthis表达式结果,也可不返回对一个对象在Lambda表达式中调用它的多个函数时使用,如with(RecyclerView.Holder){}runthis表达式结果,也可不返回当表达式中同时包含对象初始化及返回值计算时使用alsoit上下文对象附加效果,相当于并且对该对象执行以下操作applythis上下文对象lambda表达式中执行对象的配置,可以理解为**将以下赋值应用于对象takeIf 与 takeUnlesstakeIf、takeUnless 函数可以以链式调用的方式进行对象状态检查,是单个对象的过滤函数。个人认为可以简单理解成是对if/else的链式调用。使用规则:takeIf: 如果takeIf后面的表达式或闭包符合要求,则takeIf返回此对象;否则返回null。takeUnless:与takeIf用法相反,如果takeUnless不匹配后面的表达式或闭包,则返回该对象;否则返回null。使用举例: fun takeXXFunc() { val num = Random.nextInt(100) val evenOrNull = num.takeIf { it % 2 == 0 } //结果为偶数 或 null val oddOrNull = num.takeUnless { it % 2 == 0 } //结果为奇数 或 null print("evenOrNull:$evenOrNull, oddOrNull:$oddOrNull") } 执行结果://奇数:evenOrNull:null, oddOrNull:69 //偶数:evenOrNull:4, oddOrNull:null takeIf结合let使用 /** * takeIf结合let使用 */ fun takeIfExam() { val list = arrayListOf(1, 2, 3) list.takeIf { it.size < 4 }?.let { println("list:$list") } } 执行结果:list:[1, 2, 3] 如果改为val list = arrayListOf(1, 2, 3, 4),则takeIf判断不成立,直接返回null,后面就不会再执行了。
0
0
0
浏览量1960
IT大鲨鱼

Android Kotlin之Flow数据流

Flow介绍Flow是google官方提供的一套基于kotlin协程的响应式编程模型,它与RxJava的使用类似,但相比之下Flow使用起来更简单,另外Flow作用在协程内,可以与协程的生命周期绑定,当协程取消时,Flow也会被取消,避免了内存泄漏风险。我们知道 协程是轻量级的线程,本质上协程、线程都是服务于并发场景下,其中协程是协作式任务,线程是抢占式任务。默认协程用来处理实时性不高的数据,请求到结果后整个协程就结束了,即它是一锤子买卖。而Flow数据流可以按顺序发送多个值,官方对数据流三个成员的定义:提供方会生成添加到数据流中的数据。通过协程,数据流还可以异步生成数据。中介(可选),修改发送到数据流的值,或修正数据流本身。使用方:使用或接收数据流中的值。使用举例举个Flow简单例子: flow { log("send hello") emit("hello") //发送数据 log("send world") emit("world") //发送数据 }.flowOn(Dispatchers.IO) .onEmpty { log("onEmpty") } .onStart { log("onStart") } .onEach { log("onEach: $it") } .onCompletion { log("onCompletion") } .catch { exception -> exception.message?.let { log(it) } } .collect { //接收数据流 log("collect: $it") } 执行结果:2021-09-27 19:51:54.433 7240-7240/ E/TTT: onStart 2021-09-27 19:51:54.439 7240-7325/ E/TTT: send hello 2021-09-27 19:51:54.440 7240-7325/ E/TTT: send world 2021-09-27 19:51:54.451 7240-7240/ E/TTT: onEach: hello 2021-09-27 19:51:54.451 7240-7240/ E/TTT: collect:hello 2021-09-27 19:51:54.452 7240-7240/ E/TTT: onEach: world 2021-09-27 19:51:54.452 7240-7240/ E/TTT: collect:world 2021-09-27 19:51:54.453 7240-7240/ E/TTT: onCompletion flow{}为上游数据提供方,并通过emit()发送一个或多个数据,当发送多个数据时,数据流整体是有序的,即先发送先接收;另外发送的数据必须来自同一个协程内,不允许来自多个CoroutineContext,所以默认不能在flow{}中创建新协程或通过withContext()切换协程。如需切换上游的CoroutineContext,可以通过flowOn()进行切换。collect{}为下游数据使用方,collect是一个扩展函数,且是一个非阻塞式挂起函数(使用suspend修饰),所以Flow只能在kotlin协程中使用。其他操作符可以认为都是服务于整个数据流的,包括对上游数据处理、异常处理等。常用操作符创建操作符flow:创建Flow的操作符。flowof:构造一组数据的Flow进行发送。asFlow:将其他数据转换成Flow,一般是集合向Flow的转换,如listOf(1,2,3).asFlow()。callbackFlow:将基于回调的 API 转换为Flow数据流回调操作符onStart:上游flow{}开始发送数据之前执行onCompletion:flow数据流取消或者结束时执行onEach:上游向下游发送数据之前调用,每一个上游数据发送后都会经过onEach()onEmpty:当流完成却没有发出任何元素时执行。如emptyFlow<String>().onEmpty {}onSubscription:SharedFlow 专用操作符,建立订阅之后回调。和onStart的区别:因为SharedFlow是热流,因此如果在onStart发送数据,下游可能接收不到,因为提前执行了。变换操作符map:对上游发送的数据进行变换,collect最后接收的是变换之后的值mapLatest:类似于collectLatest,当emit发送新值,会取消掉map上一次转换还未完成的值。mapNotNull:仅发送map之后不为空的值。transform:对发出的值进行变换 。不同于map的是,经过transform之后可以重新发送数据,甚至发送多个数据,因为transform内部又重新构建了flow。transformLatest:类似于mapLatest,当有新值发送时,会取消掉之前还未转换完成的值。transformWhile:返回值是一个Boolean,当为true时会继续往下执行;反之为false,本次发送的流程会中断。asSharedFlow: MutableStateFlow 转换为 StateFlow ,即从可变状态变成不可变状态。asStateFlow:MutableSharedFlow 转换为 SharedFlow ,即从可变状态变成不可变状态。receiveAsFlow:Channel 转换为Flow ,上游与下游是一对一的关系。如果有多个下游观察者,可能会轮流收到值。consumeAsFlow:Channel 转换为Flow ,有多个下游观察者时会crash。withIndex:将数据包装成IndexedValue类型,内部包含了当前数据的Index。scan(initial: R, operation: suspend (accumulator: R, value: T) -> R):把initial初始值和每一步的操作结果发送出去。produceIn:转换为Channel的 ReceiveChannelrunningFold(initial, operation: (accumulator: R, value: T) -> R):initial值与前面的流共同计算后返回一个新流,将每步的结果发送出去。runningReduce*:返回一个新流,将每步的结果发送出去,默认没有initial值。shareIn:flow 转化为 SharedFlow,后面会详细介绍。stateIn:flow转化为StateFlow,后面会详细介绍。过滤操作符filter:筛选符合条件的值,返回true继续往下执行。filterNot:与filter相反,筛选不符合条件的值,返回false继续往下执行。filterNotNull:筛选不为空的值。filterInstance:筛选对应类型的值,如.filterIsInstance<String>()用来过滤String类型的值drop:drop(count: Int)参数为Int类型,意为丢弃掉前count个值。dropWhile:找到第一个不满足条件的值,返回其和其后所有的值。take:与drop()相反,意为取前n个值。takeWhile:与dropWhile()相反,找到第一个不满足条件的值,返回其前面所有的值。debounce:debounce(timeoutMillis: Long)指定时间内只接收最新的值,其他的过滤掉。sample:sample(periodMillis: Long)在指定周期内,获取最新发出的值。如: flow { repeat(10) { emit(it) delay(110) } }.sample(200) 执行结果:1, 3, 5, 7, 9distinctUntilChangedBy:判断两个连续值是否重复,可以设置是否丢弃重复值。distinctUntilChanged:若连续两个值相同,则跳过后面的值。组合操作符combine:组合两个Flow流最新发出的数据,直到两个流都结束为止。扩展:在kotlinx-coroutines-core-jvm中的FlowKt中,可以将更多的flow结合起来返回一个Flow<Any>,典型应用场景:多个筛选条件选中后,展示符合条件的数据。如果后续某个筛选条件发生了改变,只需要通过发生改变的Flow的flow.value = newValue重新发送,combine就会自动构建出新的Flow<Any>,这样UI层会接收到新的变化条件进行刷新即可。combineTransform: combine + transform操作merge:listOf(flow1, flow2).merge(),多个流合并为一个流。flattenConcat:以顺序方式将给定的流展开为单个流 。示例如下:flow { emit(flowOf(1, 2,)) emit(flowOf(3,4)) } .flattenConcat().collect { value-> print(value) } // 执行结果:1 2 3 4 flattenMerge:作用和 flattenConcat 一样,但是可以设置并发收集流的数量。flatMapContact:相当于 map + flattenConcat , 通过 map 转成一个流,在通过 flattenConcat发送。flatMapLatest:当有新值发送时,会取消掉之前还未转换完成的值。flatMapMerge:相当于 map + flattenMerge ,参数concurrency: Int 来限制并发数。zip:组合两个Flow流最新发出的数据,上游流在同一协程中顺序收集,没有任何缓冲。不同于combine的是,当其中一个流结束时,另外的Flow也会调用cancel,生成的流完成。 lifecycleScope.launch { val flow = flowOf(1, 2, 3).onEach { delay(50) } val flow2 = flowOf("a", "b", "c", "d").onEach { delay(150) } val startTime = System.currentTimeMillis() // 记录开始的时间 flow.zip(flow2) { i, s -> i.toString() + s }.collect { // Will print "1a 2b 3c" log("$it 耗时 ${System.currentTimeMillis() - startTime} ms") } } 执行结果(flow已经执行完,所以flow2中的d被cancel了):2022-05-20 /org.ninetripods.mq.study E/TTT: 1a 耗时 156 ms 2022-05-20 /org.ninetripods.mq.study E/TTT: 2b 耗时 307 ms 2022-05-20 /org.ninetripods.mq.study E/TTT: 3c 耗时 459 ms 如果换做combine,执行结果如下(组合的是最新发出的数据):2022-05-20 /org.ninetripods.mq.study E/TTT: 2a 耗时 156 ms 2022-05-20 /org.ninetripods.mq.study E/TTT: 3a 耗时 159 ms 2022-05-20 /org.ninetripods.mq.study E/TTT: 3b 耗时 311 ms 2022-05-20 /org.ninetripods.mq.study E/TTT: 3c 耗时 466 ms 2022-05-20 /org.ninetripods.mq.study E/TTT: 3d 耗时 620 ms 注:上面combine多次执行的结果可能不一致,但每次组合的是最新发出的数据功能性操作符cancellable:判断当前协程是否被取消 ,如果已取消,则抛出异常catch:对此操作符之前的流发生的异常进行捕获,对此操作符之后的流无影响。当发生异常时,默认collect{}中lambda将不会再执行。当然,可以自行通过emit()继续发送。retry:流发生异常时的重试机制。如果是无限重试,直接调用retry()默认方法即可,retry()最终调用的也是retryWhen()方法。public fun <T> Flow<T>.retry( retries: Int = Int.MAX_VALUE, //指定重试次数 predicate: (Throwable) -> Boolean = { true } //返回true且满足retries次数要求,继续重试;false停止重试 ): Flow<T> { require(retries > 0) { "Expected positive amount of retries, but had $retries" } return retryWhen { cause, attempt -> predicate(cause) && attempt < retries } } retryWhen:流发生异常时的重试机制。public fun <T> Flow<T>.retryWhen(predicate: suspend FlowCollector<T>.(cause: Throwable, attempt: Long) -> Boolean): Flow<T> = { ...... } 有条件的进行重试 ,lambda 中有两个参数: cause是 异常原因,attempt是当前重试的位置,lambda返回true时继续重试; 反之停止重试。buffer:流执行总时间就是所有运算符执行时间之和。如果上下游运算符都比较耗时,可以考虑使用buffer()优化,该运算符会在执行期间为流创建一个单独的协程。public fun <T> Flow<T>.buffer(capacity: Int = BUFFERED, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND): Flow<T> {......} 默认流都是在同一个协程中进行的,示例如下所示(官方示例):flowOf("A", "B", "C") .onEach { println("1$it") } .collect { println("2$it") } //上述代码在协程Q中按以下顺序执行: Q : -->-- [1A] -- [2A] -- [1B] -- [2B] -- [1C] -- [2C] -->-- 此时,如果onEach()、collect()中的运算时间都比较长的话,那么总时间就是所有运算符执行时间之和。buffer运算符会在执行期间为流创建一个单独的协程,如下所示:flowOf("A", "B", "C") .onEach { println("1$it") } .buffer() // <--------------- buffer between onEach and collect .collect { println("2$it") } 上述代码将在两个协程中执行,其中buffer()以上还是在协程P中执行,而buffer()下面的collect()会在协程Q中执行,数据通过Channel进行传递,从而减少了执行的总时间。P : -->-- [1A] -- [1B] -- [1C] ---------->-- // flowOf(...).onEach { ... } | | channel // buffer() V Q : -->---------- [2A] -- [2B] -- [2C] -->-- // collect conflate:仅保留最新值, 内部实现是 buffer(CONFLATED)flowOn:flowOn 会更改上游数据流的 CoroutineContext,且只会影响flowOn之前(或之上)的任何中间运算符。下游数据流(晚于 flowOn 的中间运算符和使用方)不会受到影响。如果有多个 flowOn 运算符,每个运算符都会更改当前位置的上游数据流。末端操作符collect:数据收集操作符,默认的flow是冷流,即当执行collect时,上游才会被触发执行。collectIndexed:带下标的收集操作,如collectIndexed{ index, value -> }。collectLatest:与collect的区别:当新值从上游发出时,如果上个收集还未完成,会取消上个值得收集操作。toCollection、toList、toSet:将flow{}结果转化为集合。注:还有很多操作符没有列出来~冷流 vs 热流flow{}会创建一个数据流,并且这个数据流默认是冷流。除了冷流,还有对应的热流,下面是冷流和热流的区别:冷流:当执行订阅的时候,上游发布者才开始发射数据流。订阅者与发布者是一一对应的关系,即当存在多个订阅者时,每个新的订阅者都会重新收到完整的数据。热流:不管是否被订阅,上游发布者都会发送数据流到内存中。订阅者与发布者是一对多的关系,当上游发送数据时,多个订阅者都会收到消息。来验证一下flow{}创建的是冷流:界面如上图所示,定义了2个订阅者,首先构建数据流: var sendNum = 0 val mSimpleFlow = flow { sendNum++ emit("sendValue:$sendNum") }.flowOn(Dispatchers.IO) 以及两个订阅者:mBtnContent1.setOnClickListener { lifecycleScope.launch { mSimpleFlow.collect { mTvSend.text = it mBtnContent1.text = it } } } mBtnContent2.setOnClickListener { lifecycleScope.launch { mSimpleFlow.collect { mTvSend.text = it mBtnContent2.text = it } } } 当点击订阅者1的按钮时,flow{}中发送了sendValue1,执行结果:此时继续点击右边的订阅者2,flow{}中发送了sendValue2,执行结果:可以看到两个订阅者是互相不干扰的,都是单独与上游flow{}进行数据传递的,即冷流,另外,flow{}可以通过stateIn/shareIn将其转换为StateFlow/SharedFlow热流。SharedFlow我们知道flow{}构建的是冷流,而SharedFlow(共享Flow)默认是热流,发送器与收集器是一对多的关系。public fun <T> MutableSharedFlow( replay: Int = 0, extraBufferCapacity: Int = 0, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND ): MutableSharedFlow<T> replay:重播给新订阅者时缓存数据的个数,默认是0。当新订阅者collect时,会先尝试获取上游replay个数据,为0时则不会获取之前的数据。replay缓存是针对后续所有的订阅者准备的。extraBufferCapacity:除了replay外,缓冲值的数量。当有剩余的缓冲区空间时,Emit不挂起(可选,不能为负,默认为零) 。extraBufferCapacity是为上游快速发射器及下游慢速收集器这种场景提供缓冲的,个人觉得有点类似于线程池中的存储队列。这里注意一点,replay保存的是最新值,而extraBufferCapacity保存的是最先发送的一个或多个值。onBufferOverflow:配置缓冲区溢出的操作(可选,默认为SUSPEND,暂停尝试发出值),可选值有:SUSPEND-暂停发送、DROP_OLDEST-丢弃队列中最老的、DROP_LATEST-丢弃队列中最新的。关于replay与extraBufferCapacity 的不同,可以参考 MutableSharedFlow 有点复杂这篇文章。shareIn将普通flow转化为SharedFlow普通flow{}可以通过shareIn将普通数据流转换成SharedFlowpublic fun <T> Flow<T>.shareIn( scope: CoroutineScope, started: SharingStarted, replay: Int = 0 ): SharedFlow<T> scope:协程作用域范围started:控制共享的开始、结束策略。一共有三种,分别为Eagerly、Lazily、WhileSubscribed。1、SharingStarted.Eagerly, //Eagerly:马上开始,在scope作用域结束时终止 2、SharingStarted.Lazily, //Lazily:当订阅者出现时开始,在scope作用域结束时终止 3、SharingStarted.WhileSubscribed(stopTimeoutMillis: Long = 0,replayExpirationMillis: Long = Long.MAX_VALUE) 其中stopTimeoutMillis:表示最后一个订阅者结束订阅与停止上游流的时间差,默认值为0(立即停止上游流) replayExpirationMillis:数据重播的超时时间。 replay:重播给新订阅者的数量举例: //ViewModel中 普通flow通过shareIn转化为SharedFlow val flowConvertSharedFlow by lazy { flow { emit("1、flow") emit("2、convert") emit("3、SharedFlow") }.shareIn( viewModelScope, //协程作用域范围 SharingStarted.Eagerly, //立即开始 replay = 3 //重播给新订阅者的数量 ).onStart { log("onStart") } } //Activity中 mBtnConvertF.setOnClickListener { val builder: StringBuilder = StringBuilder() lifecycleScope.launch { mFlowModel.flowConvertSharedFlow.collect { log(it) builder.append(it).append("\n") mTvConvertF.text = builder.toString() } } } 执行结果:2021-10-09 15:11:08.340 4549-4549/ E/TTT: onStart 2021-10-09 15:11:08.340 4549-4549/ E/TTT: 1、flow 2021-10-09 15:11:08.341 4549-4549/ E/TTT: 2、convert 2021-10-09 15:11:08.341 4549-4549/ E/TTT: 3、SharedFlow StateFlowStateFlow特点:StateFlow可以认为是一个replay为1,且没有缓冲区的SharedFlow,所以新订阅者collect时会先获取一个默认值,构造函数如下://MutableStateFlow构造函数 public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL) //MutableStateFlow接口继承了MutableSharedFlow接口 public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> { public override var value: T public fun compareAndSet(expect: T, update: T): Boolean } StateFlow有自动去重的功能,即如果上游连续发送的value重复时,下游的接收方只会接收第一次的值,后续的重复值不会再接收可以通过StateFlow.value获取发送的值stateIn将普通flow转化为StateFlow普通flow{}可以通过stateIn将普通数据流转换成StateFlowpublic fun <T> Flow<T>.stateIn( scope: CoroutineScope, started: SharingStarted, initialValue: T ): StateFlow<T> { //这里设置的replay是1 及重播给新订阅者的缓存为1 val config = configureSharing(1) ...... } scope:协程作用域范围started:控制共享的开始、结束策略。一共有三种,分别为Eagerly、Lazily、WhileSubscribed。1、SharingStarted.Eagerly, //Eagerly:马上开始,在scope作用域结束时终止 2、SharingStarted.Lazily, //Lazily:当订阅者出现时开始,在scope作用域结束时终止 3、SharingStarted.WhileSubscribed(stopTimeoutMillis: Long = 0,replayExpirationMillis: Long = Long.MAX_VALUE) 其中stopTimeoutMillis:表示最后一个订阅者结束订阅与停止上游流的时间差,默认值为0(立即停止上游流) replayExpirationMillis:数据重播的超时时间。 initialValue:默认StateFlow的初始值,会发送到下游使用举例: //ViewModel中 val flowConvertStateFlow by lazy { flow { //转化为StateFlow是 emit()可以是0个或1个 或多个,当是多个时,新订阅者collect只会收到最后一个值(replay为1) emit("1、flow convert StateFlow") } .stateIn( viewModelScope, //协程作用域范围 SharingStarted.Eagerly, //立即开始 "0、initialValue" // 默认StateFlow的初始值,会发送到下游 ).onStart { log("onStart") } } //Activity中 mBtnConvertSF.setOnClickListener { lifecycleScope.launch { val builder = StringBuilder() mFlowModel.flowConvertStateFlow.collect { log(it) builder.append(it).append("\n") mTvConvertSF.text = builder.toString() } } } 执行结果:2021-10-09 16:34:07.180 12394-12394/ E/TTT: onStart 2021-10-09 16:34:07.181 12394-12394/ E/TTT: 0、initialValue 2021-10-09 16:34:07.182 12394-12394/ E/TTT: 1、flow convert StateFlow 注:在UI层使用Lifecycle.repeatOnLifecycle 配合上游的SharingStarted.WhileSubscribed一块使用是一种更安全、性能更好的流收集方式。StateFlow vs LiveData在学习LiveData时,我们知道通过LiveData可以让数据被观察,且具备生命周期感知能力,但LiveData的缺点也很明显:LiveData的接收只能在主线程;LiveData发送数据是一次性买卖,不能多次发送;LiveData发送数据的线程是固定的,不能切换线程,setValue/postValue本质上都是在主线程上发送的。当需要来回切换线程时,LiveData就显得无能为力了。StateFlow 和 LiveData 具有相似之处。两者都是可观察的数据容器类,并且在应用架构中使用时,两者都遵循相似模式。但两者还是有不同之处的:StateFlow 需要将初始状态传递给构造函数,而 LiveData 不需要。当 View 进入 STOPPED 状态时,LiveData.observe() 会自动取消注册使用方,而从 StateFlow 或任何其他数据流收集数据的操作并不会自动停止。如需实现相同的行为,需要从 Lifecycle.repeatOnLifecycle 块收集数据流。StateFlow、SharedFlow  vs ChannelFlow底层使用的Channel机制实现,StateFlow、SharedFlow都是一对多的关系,如果上游发送者与下游UI层的订阅者是一对一的关系,可以使用Channel来实现,Channel默认是粘性的。Channel使用场景:一次性消费场景,比如弹窗,需求是在UI层只弹一次,即使App切到后台再切回来,也不会重复订阅(不会多次弹窗);如果使用SharedFlow/StateFlow,UI层使用的lifecycle.repeatOnLifecycle、Flow.flowWithLifecycle,则在App切换前后台时,UI层会重复订阅,弹窗事件可能会多次执行,不符合要求。Channel使用特点:每个消息只有一个订阅者可以收到,用于一对一的通信第一个订阅者可以收到collect之前的事件,即粘性事件Channel使用举例://viewModel中 private val _loadingChannel = Channel<Boolean>() val loadingFlow = _loadingChannel.receiveAsFlow() private suspend fun loadStart() { _loadingChannel.send(true) } private suspend fun loadFinish() { _loadingChannel.send(false) } //UI层接收Loading信息 mViewModel.loadingFlow.flowWithLifecycle2(this, Lifecycle.State.STARTED) { isShow -> mStatusViewUtil.showLoadingView(isShow) } 扩展:suspendCancellableCoroutine & callbackFlow在新项目或者新需求中,我们可以直接使用协程来替代之前的多线程场景的使用方式,如可以通过withContext(Dispatchers.IO)在协程中来回切换线程且能在线程执行完毕后自动切回当前线程,避免使用接口回调的方式导致逻辑可读性变差。然而,如果我们是在现有项目中开发或者网络框架就是回调方式使用时,没有办法直接使用协程,但是可以通过suspendCancellableCoroutine或callbackFlow将接口回调转化成协程:suspendCancellableCoroutine等待单次回调API的结果时挂起协程,并将结果返回给调用者;如果需要返回Flow<T>数据流,可以使用callbackFlow。suspendCancellableCoroutine使用举例: //ViewModel中 /** * suspendCancellableCoroutine将回调转化为协程使用 */ suspend fun suspendCancelableData(): String { return try { getSccInfo() } catch (e: Exception) { "error: ${e.message}" } } /** * suspendCancellableCoroutine将回调转化为协程使用 */ private suspend fun getSccInfo(): String = suspendCancellableCoroutine { continuation -> val callback = object : ICallBack { override fun onSuccess(sucStr: String?) { //1、返回结果 将结果赋值给getSccInfo()挂起函数的返回值 //2、如果调用了continuation.cancel(),resume()的结果将不会返回了,因为协程取消了 continuation.resume(sucStr ?: "empty") } override fun onError(error: Exception) { //这里会将异常抛给上层 需要上层进行处理 continuation.resumeWithException(error) } } continuation.invokeOnCancellation { //协程取消时调用,可以在这里进行解注册 log("invokeOnCancellation") } //模拟网络请求 此时协程被suspendCancellableCoroutine挂起,直到触发回调 Thread { Thread.sleep(500) //模拟Server返回数据 callback.onSuccess("getServerInfo") //模拟抛异常 //callback.onError(IllegalArgumentException("server error")) }.start() //模拟取消协程 //continuation.cancel() } //Activity中 mBtnScc.setOnClickListener { lifecycleScope.launch { val result = mFlowModel.suspendCancelableData() log(result) } } 执行结果:2021-10-11 13:31:41.384 24114-24114/ E/TTT: getServerInfo suspendCancellableCoroutine声明了作用域,并且传入一个CancellableContinuation参数,它可以调用resume、resumeWithException来处理对应的成功、失败回调,还可以调用cancel()方法取消协程的执行(抛出CancellationException 异常,但程序不会崩溃,当然也可以通过catch抓住该异常进行处理)。上面例子中,当开始执行时会将suspendCancellableCoroutine作用域内协程挂起,如果成功返回数据,会回调continuation.resume()方法将结果返回;如果出现异常,会回调continuation.resumeWithException()将异常抛到上层。这样整个函数处理完后,上层会从挂起点恢复并继续往下执行。callbackFlowcallbackFlow相对于suspendCancellableCoroutine,对接口回调封装以后返回的是Flow数据流,后续就可以对数据流进行一系列操作。callbackFlow中的几个重要方法:trySend/offer:在接口回调中使用,用于上游发射数据,类似于flow{}中的emit(),kotlin 1.5.0以下使用offer,1.5.0以上推荐使用trySend()awaitClose:写在最后,这是一个挂起函数, 当 flow 被关闭的时候 block 中的代码会被执行 可以在这里取消接口的注册等。使用举例,比如当前有个场景:去某个地方,需要先对目的地进行搜索,再出发到达目的地,假设搜索、到达目的地两个行为都是使用回调来执行的,我们现在使用callbackFlow对他们进行修改:ViewModel中,搜索目的地: fun getSearchCallbackFlow(): Flow<Boolean> = callbackFlow { val callback = object : ICallBack { override fun onSuccess(sucStr: String?) { //搜索目的地成功 trySend(true) } override fun onError(error: Exception) { //搜索目的地失败 trySend(false) } } //模拟网络请求 Thread { Thread.sleep(500) //模拟Server返回数据 callback.onSuccess("getServerInfo") }.start() //这是一个挂起函数, 当 flow 被关闭的时候 block 中的代码会被执行 可以在这里取消接口的注册等 awaitClose { log("awaitClose") } } ViewModel中,前往目的地:fun goDesCallbackFlow(isSuc: Boolean): Flow<String?> = callbackFlow { val callback = object : ICallBack { override fun onSuccess(sucStr: String?) { trySend(sucStr) } override fun onError(error: Exception) { trySend(error.message) } } //模拟网络请求 Thread { Thread.sleep(500) if (isSuc) { //到达目的地 callback.onSuccess("arrive at the destination") } else { //发生了错误 callback.onError(IllegalArgumentException("Not at destination")) } }.start() awaitClose { log("awaitClose") } } Activity中,使用Flow.flatMapConcat对两者进行整合:mBtnCallbackFlow.setOnClickListener { lifecycleScope.launch { //将两个callbackFlow串联起来 先搜索目的地,然后到达目的地 mFlowModel.getSearchCallbackFlow() .flatMapConcat { mFlowModel.goDesCallbackFlow(it) }.collect { mTvCallbackFlow.text = it ?: "error" } } } 执行结果:2021-10-11 19:13:36.528 10233-10233/ E/TTT: arrive at the destination 以下结论摘自官网:与 flow 构建器不同,callbackFlow 允许通过 send 函数从不同 CoroutineContext 发出值,或者通过 offer/trySend 函数在协程外发出值。在协程内部,callbackFlow 会使用通道,它在概念上与阻塞队列非常相似。通道都有容量配置,限定了可缓冲元素数的上限。在 callbackFlow 中所创建通道的默认容量为 64 个元素。当您尝试向完整通道添加新元素时,send 会将数据提供方挂起,直到新元素有空间为止,而 offer 不会将相关元素添加到通道中,并会立即返回 false。
0
0
0
浏览量1472
IT大鲨鱼

Kotlin之@JvmOverloads、@JvmStatic、@JvmField、@JvmInli

写在前面Kotlin代码可以经过编译器转换成VM虚拟机能识别的字节码,所以Java与Kotlin可以互相进行调用。而由于Java与Kotlin语言特性的差异,当Java调用Kotlin代码时,可以在Kotlin代码中适当增加一些注解,从而更方便的调用Kotlin代码。@JvmOverloads在Kotlin的方法里有多个默认参数时,如果在Java中直接调用,只能调用一个包含完整参数的方法,如果想暴露更多的重载函数给Java,可以使用@JvmOverloads 用于生成重载。对于每一个有默认值的参数,生成的重载会把当前有默认值的参数及其右边的参数都去掉,所以如果方法中所有的参数都有默认值,生成的重载函数中还会有一个无参的重载函数。@JvmOverloads 主要用于构造函数、方法中,同时不能用于抽象方法、接口中的方法等。· 应用在方法中 @JvmOverloads fun method(a: Int, b: Boolean = true, c: String = "c") { }转换成Java后: //1 @JvmOverloads public final void method(int a, boolean b, @NotNull String c) { Intrinsics.checkNotNullParameter(c, "c"); } //2 @JvmOverloads public final void method(int a, boolean b) { method$default(this, a, b, (String)null, 4, (Object)null); } //3 @JvmOverloads public final void method(int a) { method$default(this, a, false, (String)null, 6, (Object)null); } //4: synthetic method public static void method$default(KtAnnotation var0, int var1, boolean var2, String var3, int var4, Object var5) { if ((var4 & 2) != 0) { var2 = true; } if ((var4 & 4) != 0) { var3 = "c"; } var0.method(var1, var2, var3); }· 应用在自定义View构造函数中class VpLoadMoreView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0,) : LinearLayout(context, attrs, defStyle) {}转换成Java代码后:public final class VpLoadMoreView extends LinearLayout { //1 @JvmOverloads public VpLoadMoreView(@NotNull Context context) { this(context, (AttributeSet)null, 0, 6, (DefaultConstructorMarker)null); } //2 @JvmOverloads public VpLoadMoreView(@NotNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0, 4, (DefaultConstructorMarker)null); } //3 @JvmOverloads public VpLoadMoreView(@NotNull Context context, @Nullable AttributeSet attrs, int defStyle) { Intrinsics.checkNotNullParameter(context, "context"); super(context, attrs, defStyle); } //4: synthetic method public VpLoadMoreView(Context var1, AttributeSet var2, int var3, int var4, DefaultConstructorMarker var5) { if ((var4 & 2) != 0) { var2 = (AttributeSet)null; } if ((var4 & 4) != 0) { var3 = 0; } this(var1, var2, var3); }}如果将注解去掉,转换成Java后:public final class VpLoadMoreView extends LinearLayout { //1 public VpLoadMoreView(@NotNull Context context, @Nullable AttributeSet attrs, int defStyle) { Intrinsics.checkNotNullParameter(context, "context"); super(context, attrs, defStyle); } //2: synthetic method public VpLoadMoreView(Context var1, AttributeSet var2, int var3, int var4, DefaultConstructorMarker var5) { if ((var4 & 2) != 0) { var2 = (AttributeSet)null; } if ((var4 & 4) != 0) { var3 = 0; } this(var1, var2, var3); }}可以看到去掉了@JvmOverloads 注解,少了1个、2个参数的构造函数了,那么在Java中也不能初始化1个、2个参数的构造函数了。@JvmStatic@JvmStatic用于声明静态方法。在具名对象及伴生对象中使用时,既会在相应对象的类中生成静态方法,也会在对象自身中生成实例方法,如:class KtA { companion object { @JvmStatic fun invokeStatic() {} fun invokeNoStatic() {} }}在Java中调用: public void invokeKt() { KtA.invokeStatic(); //正确,可以直接调用 //KtA.invokeNoStatic(); //错误,这里调用不到 KtA.Companion.invokeStatic(); //正确 KtA.Companion.invokeNoStatic(); //正确 }@JvmField@JvmField使得编译器不再对该字段生成getter/setter并将其作为公开字段,如: val id1 = 100 //1 @JvmField val id2 = 200 //2 var id3 = 300 //3 @JvmField var id4 = 400 //4编译成Java后: private final int id1 = 100; @JvmField public final int id2 = 200; private int id3 = 300; @JvmField public int id4 = 400; public final int getId1() { return this.id1; } public final int getId3() { return this.id3; } public final void setId3(int var1) { this.id3 = var1; }@JvmSynthetic@JvmSynthetic可以修饰于方法上,控制只能在Kotlin中调用,如://kt代码class KtA { @JvmSynthetic fun visit() {}}Java中调用: public void invokeKt() { KtA clz = new KtA(); clz.visit(); //错误,这里在Java中调用不到。 } 如果想在Java中调用到Kotlin类中的方法,将@JvmSynthetic去掉即可。@JvmName 、@JvmMultifileClass@JvmName 注解可以生成类名;如果类名已存在,可以修改已生成的 Java 类的类名。 包名相同并且类名相同或者有相同的 @JvmName 注解有会错误,可以通过@JvmMultifileClass把他们合并到一起,如://A.kt@file:JvmName("generate")@file:JvmMultifileClasspackage org.ninetripodsfun getA() {}//B.kt@file:JvmName("generate")@file:JvmMultifileClasspackage org.ninetripodsfun getB() {}Java中调用:org.ninetripods.generate.getA();org.ninetripods.generate.getB();@JvmInline@Target(AnnotationTarget.CLASS)@Retention(AnnotationRetention.RUNTIME)@MustBeDocumented@SinceKotlin("1.5")public actual annotation class JvmInline@JvmInline在1.5.0版本引入,可以指定一个类为内联类,需结合value一起使用;在1.5.0之前使用inline关键字。//1.5.0之前,inline标记内联类inline class Person(private val name: String = "")//1.5.0之后,@JvmInline + value 标记内联类@JvmInlinevalue class Person(private val name: String = "")内联类构造参数中有且只能有一个成员变量,最终被内联到字节码中的value。,上述代码经过内联优化会在字节码中将Person对象转换为String值,从而由堆分配优化为栈分配。
0
0
0
浏览量917
IT大鲨鱼

Kotlin | 在for、forEach循环中正确的使用break、continue

Kotlin 有三种结构化跳转表达式:· return:默认从最直接包围它的函数或者匿名函数返回。· break:终止最直接包围它的循环。· continue:继续下一次最直接包围它的循环。for循环中使用break、continue for (i in 1..5) { if (i == 3) break //1 这里分别使用break continue return println("i: $i") } println("循环外继续执行")1处分别使用break、continue、return 替换,执行结果如下://break i: 1i: 2循环外继续执行//continuei: 1i: 2i: 4i: 5循环外继续执行//returni: 1i: 2嗯,跟Java中的使用姿势是一样的,继续往下看。Label标签在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@、loop@都是有效的标签。 要为一个表达式加标签,我们只要在其前加标签即可。示例:loop@ for (i in 1..5){ //...}这里在嵌套for循环中使用Label,可以控制break及continue的范围:loop@ for (i in 1..2) { println("i: $i") for (j in 1..5) { if (j == 3) break@loop //break continue println("j: $j") }}println("循环外继续执行")执行结果://breaki: 1j: 1j: 2循环外继续执行//continuei: 1j: 1j: 2i: 2j: 1j: 2循环外继续执行结论:Label标签限制的 break 跳转到刚好位于该标签指定的循环后面的执行点。 continue 继续标签指定的循环的下一次迭代。注意不能在上述代码中使用return@loop,因为目标标签表示的不是函数,错误信息如下:Target label does not denote a functionforEach中模拟break、continue在forEach中并不能直接使用break、continue:可以看到直接报错了,错误信息也很明确:break 和 continue 只允许在循环中使用,而这里是forEach的闭包,所以并不能直接使用break 和 continue。那么如何在forEach中分别模拟出break、continue的效果呢?通过Label即可实现,如:fun forEachControl() { listOf(1, 2, 3, 4, 5).forEach forEach@{ if (it == 3) return@forEach println("it:$it") } println("循环外继续执行")}return 只会从 lambda 表达式中返回。通常情况下使用隐式标签更方便(Label 标签与接受该 lambda 的函数同名即可使用隐式标签),简化之后:fun forEachControl() { listOf(1, 2, 3, 4, 5).forEach{ if (it == 3) return@forEach println("it:$it") } println("循环外继续执行")}代码执行结果:it:1it:2it:4it:5循环外继续执行可以看到return@forEach相当于表达式里面的continue了。嗯哼?为什么不是break的效果呢?明明已经return@forEach了呀,其实这是Kotlin闭包带来的副作用,看下forEach的源码:public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit { //注意看下面这行代码的逻辑 for (element in this) action(element)}action函数作为参数传入的,所以在forEach中 return@forEach 只能停止当前闭包的逻辑,后面的循环并不会受影响,会继续后面的循环。那么如何在forEach中模拟break的效果呢?只要把声明Label放到forEach外面即可以了:fun forEachControl() { run loop@{//1 listOf(1, 2, 3, 4, 5).forEach { if (it == 3) return@loop//2 println("it:$it") } println("循环外继续执行") } }it:1it:2可以看到1处的Label标签放到了forEach的外层了,那么当执行2处的return@loop时会直接跳出forEach循环,进而实现了break功能。
0
0
0
浏览量872
IT大鲨鱼

Kotlin常用Collection集合操作整理

常用集合Kotlin 标准库提供了基本集合类型的实现: Set、List 以及 Map。 一对接口代表每种集合类型:一个 只读 接口,提供访问集合元素的操作。一个 可变 接口,通过写操作扩展相应的只读接口:添加、删除和更新其元素。其中灰色是不可变集合,黄色是可变集合。Iterator意为迭代器,Collection是只读接口,而MutableCollection是一个具有写操作的Collection接口:public interface MutableCollection<E> : Collection<E>, MutableIterable<E> { override fun iterator(): MutableIterator<E> public fun add(element: E): Boolean public fun remove(element: E): Boolean public fun addAll(elements: Collection<E>): Boolean public fun removeAll(elements: Collection<E>): Boolean public fun retainAll(elements: Collection<E>): Boolean public fun clear(): Unit } 1.1、ListList< T> 以指定的顺序存储元素,并提供使用索引访问元素的方法。从第一个元素索引0 到最后一个元素索引(list.size - 1)为止。List 的默认实现是 ArrayList。 //不可变List,List 的默认实现是 ArrayList val numList = listOf("one", "two", "three") println(numList[0]) //one println(numList.get(0)) //one println(numList.lastIndex) //最后一个元素位置:2 //取List一部分 println(numList.subList(0, 2))//左边右开区间,如果越界会抛异常。执行结果:[one, two] //first() println(numList.first()) //one 取第一个元素 println(numList.first { it.length > 3 }) //按条件取满足条件的第一个元素 都没有的话抛异常 执行结果:three //find() 等同于 firstOrNull() println(numList.firstOrNull { it.length > 5 }) //null println(numList.find { it.length > 5 }) //null //last() println(numList.last()) //three 取最后一个元素 println(numList.last { it.contains("o") }) //two //findLast() = lastOrNull() println(numList.lastOrNull { it.length > 5 }) //null println(numList.findLast { it.length > 5 }) //null //index为3的位置没有元素 println(numList.elementAtOrNull(3)) //null println(numList.elementAtOrElse(3) { index -> "The value for index $index is undefined" //The value for index 3 is undefined }) println(numList.random()) //随机取一个元素 println(numList.isEmpty())//false println(numList.isNotEmpty()) //true println(numList.isNullOrEmpty()) //false val initList = List(3) { it * it } //第一个参数是size,第二个参数是初始化函数 println(initList) // [0, 1, 4] //List之间的比较 val numList2 = listOf("two", "one", "three") println("numList==numList2:${numList == numList2}") //false 内容和元素都一致时才相等 //可变List val origins = mutableListOf("one", "two", "three") println(origins) // 原始数据:[one, two, three] origins.add("three") println(origins) // 添加一条数据:[one, two, three, three] origins.removeAt(0) println(origins) // 删除第一条数据:[two, three, three] origins.remove("three") println(origins) // 删除符合条件的第一条element: [two, three] origins[0] = "newOne" println(origins) // 更新第一条数据:[newOne, three] origins.shuffle() println(origins) // 随机数据:[three, newOne] origins.removeAll { it.length == 3 } println(origins) //删除全部符合条件的元素 [three, newOne] println(origins.retainAll { it.length == 3 }) //保留全部符合条件的元素 1.1.1、List转为Map val numbers = listOf("one", "two", "three", "four") println(numbers.associateWith { it.length }) 执行结果会转化为Map:{one=3, two=3, three=5, four=4} 1.2、SetSet内部是用Map实现的,Set相关的实现详见:Java Collection系列之:HashSet、LinkedHashSet、TreeSet的使用及源码解析 //Set常用API Set的默认实现 - LinkedHashSet(保留元素插入的顺序) val numSet = setOf("one", "two", "three") println(numSet.first()) // one println(numSet.last()) // three for (index in numSet.indices) { //Set遍历 index位置 println("index:$index") //index:0 index:1 index:2 } numSet.forEach { //Set遍历 Entry println(it) // one two three } numSet.forEachIndexed { index, entry -> //Set遍历 index & Entry println("index:$index, entry:$entry") /** * 执行结果: * index: 0, entry: one * index: 1, entry: two * index: 2, entry: three */ } //Set之间的比较 val numSet2 = setOf("two", "one", "three") println("numSet==numSet2:${numSet == numSet2}") //true 内容一致为true //可变Set val variableSet = mutableSetOf("one", "two", "three") variableSet.add("four") println(variableSet) // [one, two, three, four] variableSet.remove("two") println(variableSet) // [one, three, four] //union()并集、intersect()交集、subtract()差集 val numSets = setOf("one", "two", "three") println(numSets union setOf("four", "five")) //[one, two, three, four, five] println(numSets intersect setOf("two", "one")) //[one, two] println(numSets subtract setOf("three", "four")) //[one, two] 1.3、MapMap<K, V> 不是 Collection 接口的继承者;但是它也是 Kotlin 的一种集合类型。 Map 存储 键-值 对(或 条目);键是唯一的,但是不同的键可以与相同的值配对。Map 接口提供特定的函数进行通过键访问值、搜索键和值等操作。Map相关的实现详见:Java Collection系列之HashMap、ConcurrentHashMap、LinkedHashMap的使用及源码分析 //Map常用API,默认实现 – LinkedHashMap:迭代 Map 时保留元素插入的顺序 val numMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3) println(numMap.keys) //[key1, key2, key3] println(numMap.values) //[1, 2, 3] 注意:在.values中调用remove()仅删除给定值匹配到的的第一个条目。 println(numMap.entries) //[key1=1, key2=2, key3=3] println(numMap["key1"]) // 1 //println(numMap.getOrDefault("key4", 4)) //API24添加 执行结果:4 println("${numMap.containsValue(1)}, ${1 in numMap.values}") //true true println("${numMap.containsKey("key1")}, ${"key1" in numMap.keys}") //true true numMap.forEach { entry -> println(entry) } //Map遍历 默认有顺序 key1=1 key2=2 key3=3 //filter()过滤操作 val filterMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11) println(filterMap.filter { (key, value) -> key.endsWith("1") && value < 10 //{key1=1} }) //filterKeys()过滤key println(filterMap.filterKeys { it.endsWith("2") }) //{key2=2} //filterValues()过滤value println(filterMap.filterValues { it < 3 }) //{key1=1, key2=2} //Map之间的比较 val num2Map = mapOf("key2" to 2, "key1" to 1, "key3" to 3) println("numMap==num2Map :${numMap == num2Map}") //true //可变Map val variableMap = mutableMapOf("key1" to 1, "key2" to 2) variableMap["key3"] = 3 variableMap.put("key1", 111) println(variableMap) //{key1=111, key2=2, key3=3} 2.1、Sequence序列Iterable处理包含多个步骤时,每个处理步骤完成并返回其结果——中间集合,然后在此集合上执行后续步骤。Sequence序列仅当请求整个处理链的结果时才进行实际计算:Sequence 对每个元素逐个执行所有处理步骤。结论:序列可避免生成中间步骤的结果,从而提高了整个集合处理链的性能。 但是,序列的延迟性质增加了一些开销,这些开销在处理较小的集合或进行更简单的计算时可能很重要。 因此,应该同时考虑使用 Sequence 与 Iterable,并确定在哪种情况更适合。 //创建Sequence val sequenceNum = sequenceOf("one", "two", "three", "four") println("sequenceNum: $sequenceNum") //Iterable转为Sequence val numbers = listOf("one", "two", "three", "four") val numSequence = numbers.asSequence() println("numSequence: $numSequence") //通过函数generateSequence()创建序列,默认创建的序列是无限的;如果想创建有限数列,那么最后一个元素需要返回null val oddNumbers = generateSequence(1) { it + 2 } // `it` 是上一个元素 println(oddNumbers.take(5).toList()) // [1, 3, 5, 7, 9] //sequence()函数可以将组块生成序列,yield()-生产单个元素、yieldAll()-可以生产多个、无限个元素 val oNumbers = sequence { yield(1) yieldAll(listOf(3, 5)) yieldAll(generateSequence(7) { it + 2 }) } println(oNumbers.take(4).toList()) // [1, 3, 5, 7] 2.1.1、Iterable & Sequence执行顺序//Iterable & Sequence执行顺序 举例:过滤长于三个字符的单词,并打印前四个单词的长度。 val words = "The quick brown fox jumps over the lazy dog".split(" ") val lengthsList = words.filter { println("filter: $it"); it.length > 3 } .map { println("length: ${it.length}"); it.length } .take(4) println("Lengths of first 4 words longer than 3 chars:") println(lengthsList) 执行结果:filter: The filter: quick filter: brown filter: fox filter: jumps filter: over filter: the filter: lazy filter: dog length: 5 length: 5 length: 5 length: 4 length: 4 Lengths of first 4 words longer than 3 chars: [5, 5, 5, 4] val originWords = "The quick brown fox jumps over the lazy dog".split(" ") // 将列表转换为序列 val wordsSequence = originWords.asSequence() val lengthsSequence = wordsSequence.filter { println("filter: $it"); it.length > 3 } .map { println("length: ${it.length}"); it.length } .take(4) println("Lengths of first 4 words longer than 3 chars") // 末端操作:以列表形式获取结果。 println(lengthsSequence.toList()) 执行结果:Lengths of first 4 words longer than 3 chars filter: The filter: quick length: 5 filter: brown length: 5 filter: fox filter: jumps length: 5 filter: over length: 4 [5, 5, 5, 4] 上述序列中,Sequence处理需要 18 个步骤,Iterable需要 23 个步骤来执行列表操作,上述示例参见 Sequence序列操作。3.1、集合操作3.1.1、集合拷贝:toList()、toMutableList()、toSet()创建与现有集合具有相同元素的集合,可以使用复制操作,例如toList()、toMutableList()、toSet() 等等。标准库中的集合复制操作创建了具有相同元素引用的 浅复制集合。 因此,对集合元素所做的更改会反映在其所有副本中,如果对源集合进行添加或删除元素,则不会影响副本。 val sourceList = mutableListOf<BookModel>() for (i in 0..1) { sourceList.add(BookModel(i, "Android")) } println("sourceList:$sourceList") val copyList = sourceList.toMutableList() println("sourceList新增数据:") sourceList.add(BookModel(100, "IOS")) println("sourceList:$sourceList") println("copyList:$copyList") sourceList.forEachIndexed { index, bookModel -> bookModel.name = "Android$index" } println("sourceList修改之后:") println("sourceList:$sourceList") println("copyList:$copyList") //执行结果: // sourceList:[BookModel(id=0, name=Android), BookModel(id=1, name=Android)] // sourceList新增数据: // sourceList:[BookModel(id=0, name=Android), BookModel(id=1, name=Android), BookModel(id=100, name=IOS)] // copyList:[BookModel(id=0, name=Android), BookModel(id=1, name=Android)] // sourceList修改之后: // sourceList:[BookModel(id=0, name=Android0), BookModel(id=1, name=Android1), BookModel(id=100, name=Android2)] // copyList:[BookModel(id=0, name=Android0), BookModel(id=1, name=Android1)] 3.1.2、集合转换:map()、zip()、associate()、flatten()、flatMap() val numbers = listOf("one", "two", "three") val mIndexes = listOf(1, 2, 3) val mTwoIndex = listOf(1, 2) /** * ----------------map()映射---------------- */ //map()、mapNotNull()映射函数,区别是mapNotNull()会过滤掉结果为null的值 println(numbers.map { "it's $it" }) //[it's one, it's two, it's three] println(numbers.mapNotNull { if (it.length == 3) null else it }) //[three] //mapIndexed()、mapIndexedNotNull()带有元素索引位置的映射函数,区别是mapIndexedNotNull()会过滤掉结果为null的值 println(numbers.mapIndexed { index, s -> "$index-$s" }) //[0-one, 1-two, 2-three] println(numbers.mapIndexedNotNull { index, s -> if (s.length == 3) null else "$index-$s" //[2-three] }) //mapKeys() & mapValues() val numMap = mapOf("one" to 1, "two" to 2, "three" to 3) println(numMap) //{one=1, two=2, three=3} println(numMap.mapKeys { it.key.toUpperCase(Locale.ROOT) }) //{ONE=1, TWO=2, THREE=3} println(numMap.mapValues { it.value + it.key.length }) //{one=4, two=5, three=8} /** * ----------------zip()合拢---------------- */ //zip()操作。如果集合的大小不同,则 zip() 的结果为较小集合的大小 println(numbers.zip(mIndexes)) //[(one, 1), (two, 2), (three, 3)] println(numbers zip mTwoIndex) //中缀表达式方式 [(one, 1), (two, 2)] //zip()中第2个参数为转换函数的使用举例 println(numbers.zip(mIndexes) { number, index -> "number:$number index:$index" }) //执行结果:[number:one index:1, number:two index:2, number:three index:3] //unzip()函数 val numPairs: List<Pair<String, Int>> = listOf("one" to 1, "two" to 2, "three" to 3) println(numPairs.unzip()) //([one, two, three], [1, 2, 3]) println(numPairs.unzip().first) //[one, two, three] println(numPairs.unzip().second) //[1, 2, 3] /** * ----------------associate关联---------------- */ //List转为Map,所以当key相同时,value会被最新的覆盖 println(numbers.associateWith { it.length }) //{one=3, two=3, three=5} //associateBy将元素作为value来构建Map println(numbers.associateBy { it.first() }) //{o=one, t=three} println(numbers.associateBy( //自行设计key和value keySelector = { it.first() }, valueTransform = { it.length }) //{o=3, t=5} ) println(numbers.associate { it.first() to it.length }) // {o=3, t=5} /** * ----------------flatten()、flatMap()---------------- * flatten()返回嵌套集合集合中的所有元素的List * flatMap()需要一个函数将一个集合元素映射到另一个集合。返回单个列表其中包含所有元素的值。等于map()+flatten()的连续调用 */ val containers = listOf( listOf("one", "two"), listOf("three", "four", "five") ) println(containers.flatten()) //[one, two, three, four, five] println(containers.flatMap { subs -> listOf(subs) //[2, 3] }) 3.1.3、集合过滤:filter()、filterTo()、 //不会影响原始集合数据,而是产生一个新的集合 val numbers = mutableListOf("one", "two", "three", "four") val filterNums = numbers.filter { it.length > 3 } println("numbers: $numbers") //numbers: [one, two, three, four] println("filterNums:$filterNums") //filterNums:[three, four] //To相关操作符 val filterResults = mutableListOf("1", "2") numbers.filterTo(filterResults, { it.length > 3 }) println("numbers: $numbers") //numbers: [one, two, three, four] println("filterResults:$filterResults") //filterResults:[1, 2, three, four] //写操作 //对于可变集合,还存在可更改集合状态的`写操作` 。这些操作包括`添加、删除和更新元素`。 //对于某些操作,有成对的函数可以执行相同的操作:`一个函数就地应用该操作,另一个函数将结果作为单独的集合返回` val sortedNums = numbers.sorted() println("numbers: $numbers") //numbers: [one, two, three, four] println("sortedNums:$sortedNums") //numbers: [one, two, three, four] 所以sorted()没有改变原始集合 numbers.sort() println("numbers: $numbers") //numbers: [one, two, three, four] 所以sort()直接在原始集合上进行改动 3.1.4、集合遍历:rangeTo、until、downTo、step、forEach、forEachIndexed //正向迭代 //1 val numbers = mutableListOf("one", "two", "three", "four") for (pos in 0..3) {//表示0<=pos && pos<=3,即pos在[0,3]内;或者用numbers.indices print("${numbers[pos]} ") // one two three four } //如果是左闭右开区间[0,4),使用until关键字 for (pos in 0 until 4) { print("${numbers[pos]} ") // one two three four } //2 numbers.forEach { print("$it ") } //one two three four //3 numbers.forEachIndexed { index, number -> println("index:$index, number:$number") } /** * index:0, number:one * index:1, number:two * index:2, number:three * index:3, number:four */ //反向迭代 for (pos in 3 downTo 0) { print("${numbers[pos]} ") //four three two one } //任意步长迭代, 如下面的步长为2 for (pos in 0..3 step 2) { print("${numbers[pos]} ") //one three } 3.1.5、集合加减 val numbers = listOf("one", "two", "three", "three") val plusNumbers = numbers + "four" println(numbers) //原始集合:[one, two, three, three] println(plusNumbers) //集合加操作:[one, two, three, three, four] val minusNum1 = numbers - listOf("three") val minusNum2 = numbers - "three" println(minusNum1)//集合减操作1:[one, two] println(minusNum2) //集合减操作2:[one, two, three] //注意:minus操作,如果第二个操作数是一个元素,那么 minus 移除其在原始集合中的 第一次 出现; // 如果是一个集合,那么移除其元素在原始集合中的 所有 出现。 3.1.6、集合分组:groupBy()、groupingBy() val numbers = listOf("one", "two", "three") //groupBy() 使用一个 lambda 函数并返回一个 Map。 在此 Map 中,每个键都是 lambda 结果,而对应的值是返回此结果的元素 List。 //在带有两个 lambda 的 groupBy() 结果 Map 中,由 keySelector 函数生成的键映射到值转换函数的结果,而不是原始元素。 println(numbers.groupBy { it.first() })//{o=[one], t=[two, three]} println( numbers.groupBy(keySelector = { it.length }, valueTransform = { it }) //{3=[one, two], 5=[three]} ) //https://www.kotlincn.net/docs/reference/collection-grouping.html println(numbers.groupingBy { it.first() }.eachCount()) //{o=1, t=2, f=2, s=1} 3.1.7、取集合的部分:slice()、take()、drop()、chunked()、windowed()、zipWithNext() val numbers = listOf("one", "two", "three", "four") //slice() println(numbers.slice(1..2)) //slice(indices: IntRange) 执行结果:[two, three] println(numbers.slice(0..3 step 2)) //slice(indices: IntRange)间隔2,执行结果:[one, three] println(numbers.slice(listOf(1, 2))) //slice(indices: Iterable<Int>) 执行结果:[two, three] //take() & drop() //take:从头开始获取指定数量的元素; takeLast:从尾开始获取指定数量的元素 println(numbers.take(2)) //[one, two] println(numbers.takeLast(2)) //[three, four] println(numbers.drop(1)) //[two, three, four] println(numbers.dropLast(2))//[one, two] //takeWhile() & dropWhile() //takeWhile()不停获取元素直到排除与谓词匹配的首个元素。如果首个集合元素与谓词匹配,则结果为空。 println(numbers.takeWhile { !it.startsWith("f") })//[one, two, three] println(numbers.takeLastWhile { !it.startsWith("t") })//[four] //dropWhile()将首个与谓词不匹配的元素返回到末尾 println(numbers.dropWhile { it.length == 3 }) //[three, four] println(numbers.dropLastWhile { it.length > 3 })//[one, two] //chunk():要将集合分解为给定大小的“块”,最后一个块的大小可能较小 val nums = (0..7).toList() println(nums) //[0, 1, 2, 3, 4, 5, 6, 7] println(nums.chunked(3)) //List<List<T>>类型: [[0, 1, 2], [3, 4, 5], [6, 7]] //还可以对返回的块进行转换,如下: println(nums.chunked(3) { it.sum() }) //[3, 12, 13] val numWindows = (0..7).toList() println(numWindows.windowed(3)) //[[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7]] //step:定义相邻两个窗口第一个元素之间的距离 partialWindows:是否包含最后较少元素,true包含 false不包含 println(numWindows.windowed(3, step = 2, partialWindows = true)) //执行结果:[[0, 1, 2], [2, 3, 4], [4, 5, 6], [6, 7]] println(numWindows.windowed(3) { it.sum() }) //[3, 6, 9, 12, 15, 18] //zipWithNext()单独创建两个元素的窗口 println(numWindows.zipWithNext()) //List<Pair<T, T>>执行结果:[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7)] println(numWindows.zipWithNext { s1, s2 -> s1 * s2 }) //List<R>执行结果:[0, 2, 6, 12, 20, 30, 42] 3.1.8、集合排序:Comparable、Comparator、sortedWith、sortedBy、reversed、asReversed、shuffled排序主要使用Comparable及Comparator。其中Comparable可以理解为是内部排序,Comparator是外部排序。/** * Version比较,默认先比较major 再比较minor 从小到大正向排序 */ data class Version(val major: Int, val minor: Int) : Comparable<Version> { /** * 返回值: * 1、正值表明它大于参数。 * 2、负值表明它小于参数。 * 3、返回0说明对象相等。 */ override fun compareTo(other: Version): Int { return when { this.major != other.major -> { this.major - other.major } this.minor != other.minor -> { this.minor - other.minor } else -> 0 } } } 集合排序示例: //Comparable进行比较 println(Version(1, 2) > Version(1, 3)) //false println(Version(2, 0) > Version(1, 5)) //true val origins = listOf("aaa", "c", "bb") //sortedWith() + Comparator自定义顺序进行比较 val lengthComparator = Comparator { o1: String, o2: String -> o1.length - o2.length } println(origins.sortedWith(lengthComparator)) //[c, bb, aaa] println(origins) //[aaa, bb, c] //继续简化 println(origins.sortedWith(compareBy { it.length })) //[c, bb, aaa] //上述sortedWith(compareBy{})代码简写为sortedBy{} println(origins.sortedBy { it.length }) //[c, bb, aaa] println(origins.sortedByDescending { it.length }) //[aaa, bb, c] //自然顺序 println(origins.sorted()) //[aaa, bb, c] println(origins.sortedDescending()) //[c, bb, aaa] //倒序 reversed()产生新集合,改变原始集合不会影响新集合; val originStrs = mutableListOf("aaa", "c", "bb") println(originStrs.reversed()) //使用reversed()倒序 执行结果:[bb, c, aaa] //使用asReversed()倒序 val asReverseList = originStrs.asReversed() println(asReverseList) //[bb, c, aaa] originStrs.add("dd") //对原始集合进行改动 println(asReverseList) //原始集合变化,倒序的集合也自动更新了 执行结果:[dd, bb, c, aaa] //随机顺序 val shuffledNums = listOf("one", "two", "three") println(shuffledNums.shuffled())//随机产生一个新的集合 println(shuffledNums) //[one, two, three] 3.1.9、集合聚合操作:maxOrNull()、minOrNull()、average()、sum()、sumBy()、reduce()、fold() val numbers = listOf(30, 20, 40, 10) println(numbers.count()) //元素数量 4 println(numbers.maxOrNull()) //最大元素 40 println(numbers.minOrNull()) //最小元素 10 println(numbers.average()) //平均值 25.0 println(numbers.sum())//集合元素的总和 100 //接受一个选择器函数并返回使选择器返回最大或最小值的元素。 val min3Remainder = numbers.minByOrNull { it % 3 } //30 val max3Remainder = numbers.maxByOrNull { it % 3 } //20 //接受一个 Comparator 对象并且根据此 Comparator 对象返回最大或最小元素 val maxNum = numbers.maxWithOrNull(compareBy { it }) //40 val minNum = numbers.minWithOrNull(compareBy { it }) //10 println(min3Remainder) //30 println(max3Remainder) //20 println(maxNum) //40 println(minNum) //10 println(numbers.sumBy { it * 2 }) //对lambda函数返回的Int结果进行求和 200 println(numbers.sumByDouble { it.toDouble() / 2 }) //对lambda函数返回的Double结果进行求和 50.0 //fold() & reduce() 依次将所提供的操作应用于集合元素并返回累积的结果。 //区别:fold() 接受一个初始值并将其用作第一步的累积值,而 reduce() 的第一步则将第一个和第二个元素作为第一步的操作参数。 val sum = numbers.reduce { sum, element -> sum + element } println(sum) //100 //val numbers = listOf(30, 20, 40, 10) //reduce() val sumDouble = numbers.reduce { sum1, element -> print("$sum1 ") //30 70 150 170 sum1 + element * 2 } println(sumDouble) //170 //reduceRight() 操作参数会更改其顺序:第一个参数变为元素,然后第二个参数变为累积值。 val sumDoubleRight = numbers.reduceRight { element, sum2 -> print("$sum2 ") //10 90 130 190 sum2 + element * 2 } println(sumDoubleRight) //190 //reduceIndexed() val sumIndex = numbers.reduceIndexed { index, sumI, element -> print("index:$index,sum:$sumI,element:$element ") /** * index:1,sum:30,element:20 * index:2,sum:50,element:40 * index:3,sum:90,element:10 */ sumI + element } println(sumIndex) //reduceRightIndexed() val sumIndexRight = numbers.reduceRightIndexed { index, sumR, element -> print("index:$index,sum:$sumR,element:$element ") /** * index:2,sum:40,element:10 * index:1,sum:20,element:50 * index:0,sum:30,element:70 */ sumR + element } println(sumIndexRight) //100 //注:为了防止为null时抛异常,可以使用对应的reduceOrNull()、 // reduceRightOrNull()、reduceIndexedOrNull()、reduceRightIndexedOrNull() //val numbers = listOf(30, 20, 40, 10) //fold() val sumDouble1 = numbers.fold(0) { sum3, element -> print("$sum3 ") //0 60 100 180 200 sum3 + element * 2 } println(sumDouble1) //200 //foldRight() 操作参数会更改其顺序:第一个参数变为元素,然后第二个参数变为累积值。 val sumDoubleRight1 = numbers.foldRight(0) { element, sum4 -> print("$sum4 ") //0 20 100 140 200 sum4 + element * 2 } println(sumDoubleRight1) //200
0
0
0
浏览量1176
IT大鲨鱼

JNI 编程上手指南之字符串处理

引子JNI 把 Java 中的对象当作一个 C 指针传递到本地方法中,这个指针指向 JVM 中的内部数据结构,通常我们是通过 JNIEnv 中的函数来操作这些数据结构从而我们无需关心这个数据结构的具体构造。字符串处理示例我们来看之前分享过的一个例子:Java 层:private native String sayHello(String msg); C/C++ 层:jJNIEXPORT jstring JNICALL Java_HelloJNI_sayHello__Ljava_lang_String_2(JNIEnv *env, jobject jobj, jstring str) { //jstring -> char* jboolean isCopy; //GetStringChars 用于 utf-16 编码 //GetStringUTFChars 用于 utf-8 编码 const char* cStr = env->GetStringUTFChars(str, &isCopy); //异常处理,后面会专门讲,这里了解即可 if (nullptr == cStr) { return nullptr; } //这个取决于 jvm 的实现,不影响我们的编程 if (JNI_TRUE == isCopy) { cout << "C 字符串是 java 字符串的一份拷贝" << endl; } else { cout << "C 字符串指向 java 层的字符串" << endl; } cout << "C/C++ 层接收到的字符串是 " << cStr << endl; //通过JNI GetStringChars 函数和 GetStringUTFChars 函数获得的C字符串在原生代码中 //使用完之后需要正确地释放,否则将会引起内存泄露。 env->ReleaseStringUTFChars(str, cStr); string outString = "Hello, JNI"; // char* 转换为 jstring return env->NewStringUTF(outString.c_str()); } 我们访问 java.lang.String 对应的 JNI 类型 jstring 时,没有像访问基本数据类型一样直接使用,因为它在 Java 是一个引用类型。那么我们在 JNI 中,怎么操作处理 jstring 数据呢,我们可以通过 JNIEnv 结构体提供的函数的 JNI 函数来访问字符串的内容。JNI 字符串处理函数Java 中参数的类型是 String,JNI 函数中对应的类型是 jstring,jstring 是一个引用类型,我们需要使用 JNIEnv 中的函数来操作 jstring,接下来我们介绍一些操作 jstring 的常用函数:GetStringUTFChars// 参数说明: // * this: JNIEnv 指针 // * string: jstring类型(Java 传递给本地代码的字符串指针) // * isCopy: 它的取值可以是 JNI_TRUE (值为1)或者为 JNI_FALSE (值为0)。如果值为 JNI_TRUE,表示返回 JVM 内部源字符串的一份拷贝,并为新产生的字符串分配内存空间。如果值为 JNI_FALSE,表示返回 JVM 内部源字符串的指针,意味着可以通过指针修改源字符串的内容,不推荐这么做,因为这样做就打破了 Java 字符串不能修改的规定。但我们在开发当中,并不关心这个值是多少,通常情况下这个参数填 NULL 即可。 const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);//C环境中的定义 const char* GetStringUTFChars(jstring string, jboolean* isCopy)//C++环境中的定义 { return functions->GetStringUTFChars(this, string, isCopy); } Java 默认使用 UTF-16 编码,而 C/C++ 默认使用 UTF-8 编码。GetStringUTFChars 可以把一个 jstring 指针(指向 JVM 内部的 UTF-16 字符序列)转换成一个 UTF-8 编码的 C 风格字符串。调用完 GetStringUTFChars 之后不要忘记安全检查,因为 JVM 可能需要为新诞生的字符串分配内存空间,当内存空间不够分配的时候,会导致调用失败,失败后 GetStringUTFChars 会返回 NULL,并抛出一个 OutOfMemoryError 异常。JNI的异常和 Java 中的异常处理流程是不一样的,Java 遇到异常如果没有捕获,程序会立即停止运行。而 JNI 遇到未决的异常不会改变程序的运行流程,也就是程序会继续往下走,这样后面针对这个字符串的所有操作都是非常危险的,因此,我们需要用 return 语句跳过后面的代码,并立即结束当前方法。关于 JNI 中异常的处理我们会在后续文章中解析。ReleaseStringUTFChars// 参数说明: // this: JNIEnv 指针 // string: 指向一个 jstring 变量,即是要释放的本地字符串的来源。在当前环境下指向 Java 中传递过来的 String 字符串对应的 JNI 数据类型 jstring // utf:将要释放的C/C++本地字符串。即我们调用GetStringUTFChars获取的数据的存储指针。 void (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*);//C中的定义 void ReleaseStringUTFChars(jstring string, const char* utf)//C++中的定义 { functions->ReleaseStringUTFChars(this, string, utf); } ReleaseStringUTFChars 函数用于通知虚拟机 jstring 在 jvm 中对应的内存已经不使用了,可以清除了。NewStringUTF// 参数说明 // this: JNIEnv 指针 // bytes: 指向一个char * 变量,即要返回给 Java 层的 C/C++ 中字符串。 jstring (*NewStringUTF)(JNIEnv*, const char*);//C环境中定义 jstring NewStringUTF(const char* bytes)//C++环境中的定义 { return functions->NewStringUTF(this, bytes); } NewStringUTF 构建一个新的 java.lang.String 字符串对象。这个新创建的字符串会自动转换成 Java 支持的 UTF-16 编码。与 GetStringUTFChars 相同,NewStringUTF 在内存不足时抛出 OutOfMemoryError 异常。NewStringjstring (NewString)(JNIEnv env, const jchar* unicodeChars, jsize size); 利用 UTF-16 字符数组构造新的 java.lang.String 对象。与 GetStringUTFChars 相同,NewString 在内存不足时抛出 OutOfMemoryError 异常。GetStringUTFLengthjsize (GetStringUTFLength)(JNIEnv env, jstring string); 返回字符串的 UTF-8 编码的长度,即 C 风格字符串的长度。GetStringLengthconst jchar* (GetStringChars)(JNIEnv env, jstring string, jboolean* isCopy); 返回字符串的 UTF-16 编码的长度,即 Java 字符串长度GetStringCharsconst jchar* (GetStringChars)(JNIEnv env, jstring string, jboolean* isCopy); 返回字符串 string 对应的 UTF-16 字符数组的指针。与 GetStringUTFChars 相同,GetStringChars 在内存不足时抛出 OutOfMemoryError 异常。ReleaseStringCharsvoid ReleaseStringChars (JNIEnv *env, jstring string, const jchar *chars); 通知虚拟机平台释放 chars 所引用的相关资源,以免造成内存泄漏。参数chars 是一个指针,可通过 GetStringChars() 从 string 获得GetStringCritical 和 ReleaseStringCritical该对函数主要是为了提高从虚拟机平台返回源字符串直接指针的可能性。Get/ReleaseStringChars 和 Get/ReleaseStringUTFChars 这对函数返回的源字符串会后分配内存,如果有一个字符串内容相当大,有 1M 左右,而且只需要读取里面的内容打印出来,用这两对函数就有些不太合适了。此时用 Get/ReleaseStringCritical 可直接返回源字符串的指针应该是一个比较合适的方式。不过这对函数有一个很大的限制,在这两个函数之间的本地代码不能调用任何会让线程阻塞或等待 JVM 中其它线程的本地函数或 JNI 函数。因为通过 GetStringCritical 得到的是一个指向 JVM 内部字符串的直接指针,获取这个直接指针后会导致暂停 GC 线程,当 GC 被暂停后,如果其它线程触发 GC 继续运行的话,都会导致阻塞调用者。所以在Get/ReleaseStringCritical 这对函数中间的任何本地代码都不可以执行导致阻塞的调用或为新对象在 JVM 中分配内存,否则,JVM 有可能死锁。另外一定要记住检查是否因为内存溢出而导致它的返回值为 NULL,因为 JVM 在执行 GetStringCritical 这个函数时,仍有发生数据复制的可能性,尤其是当 JVM 内部存储的数组不连续时,为了返回一个指向连续内存空间的指针,JVM 必须复制所有数据。与 GetStringUTFChars 相同,GetStringCritical 也可能在内存不足时抛出 OutOfMemoryError 异常。GetStringRegion 和 GetStringUTFRegion分别表示获取 UTF-16 和 UTF-8 编码字符串指定范围内的内容。这对函数会把源字符串复制到一个预先分配的缓冲区内。NIEXPORT jstring JNICALL Java_HelloJNI_sayHello__Ljava_lang_String_2(JNIEnv *env, jobject jobj, jstring str) { char buff[128]; jsize len = env->GetStringUTFLength(str); // 获取 utf-8 字符串的长度 // 将虚拟机平台中的字符串以 utf-8 编码拷入C缓冲区,该函数内部不会分配内存空间 env->GetStringUTFRegion(str,0,len,buff); } 总结对于小字符串来说,GetStringRegion 和 GetStringUTFRegion 这两对函数是最佳选择,因为缓冲区可以被编译器提前分配,而且永远不会产生内存溢出的异常。当你需要处理一个字符串的一部分时,使用这对函数也是不错。因为它们提供了一个开始索引和子字符串的长度值。另外,复制少量字符串的消耗也是非常小的。使用 GetStringCritical 和 ReleaseStringCritical 这对函数时,必须非常小心。一定要确保在持有一个由 GetStringCritical 获取到的指针时,本地代码不会在 JVM 内部分配新对象,或者做任何其它可能导致系统死锁的阻塞性调用。获取 Unicode 字符串和长度,使用 GetStringChars 和 GetStringLength 函数。获取 UTF-8 字符串的长度,使用 GetStringUTFLength 函数。创建 Unicode 字符串,使用NewString,创建UTF-8使用 NewStringUTF 函数。通过 GetStringUTFChars、GetStringChars、GetStringCritical 获取字符串,这些函数内部会分配内存,必须调用相对应的 ReleaseXXXX 函数释放内存。
0
0
0
浏览量962
IT大鲨鱼

JUC系列学习(四):线程池阻塞队列BlockingQueue及其相关实现ArrayBlocking

一 BlockingQueuepublic interface Queue<E> extends Collection<E> { boolean add(E e); boolean offer(E e); E remove(); E poll(); E element(); E peek(); } public interface BlockingQueue<E> extends Queue<E> { boolean add(E e); boolean offer(E e); void put(E e) throws InterruptedException; boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException; E take() throws InterruptedException; E poll(long timeout, TimeUnit unit) throws InterruptedException; int remainingCapacity(); boolean remove(Object o); public boolean contains(Object o); int drainTo(Collection<? super E> c); int drainTo(Collection<? super E> c, int maxElements); } BlockingQueue是一个接口,定义了元素的添加和删除等操作,其实现类ArrayBlockingQueue、LinkedBlockingQueue等通常用做阻塞队列,使用场景用在生产者-消费者模式中:· 生产者往队列中添加元素,通过add/put/offer实现往队列中添加元素,当队列满时,添加元素的线程会阻塞等待队列至可用为止;· 消费者在队列中取出元素并消费,通过remove/take/poll实现队列中删除元素当队列为空时,消费元素的线程会阻塞等待队列至不为空为止。添加或删除元素时有四种不同的表现形式:· 抛异常(Throws Exception):当队列为空时,调用remove(e)删除元素会抛出异常;当队列满时,调用add(e)添加元素也会抛出异常· 返回特殊值(false或者null) :调用offer(e)添加元素或者调用poll()删除元素时,如果不能马上执行,将返回一个特殊的值,一般为false或null。· 阻塞当前线程直到被唤醒:当队列为空时,消费者线程调用take()方法时会阻塞当前线程,直到队列不为空时重新被唤醒;或者当队列满时,生产者线程调用put(e)方法时会阻塞生产者线程,直到队列不满时会重新被唤醒。· 在某个时间段内阻塞等待,超时失败:当队列为空时,线程调用poll(timeout,unit)尝试取元素会直接阻塞;当队列满时,线程调用offer(e,timeout,unit)添加元素时会阻塞。如果poll和offer在timeout时间内没有被唤醒,则直接退出。总结如下:_添加(Insert)删除(Remove)检查(Examine)抛异常(Throws Exception)add(e)remove(o)element()阻塞(Blocked)put(e)take()返回特殊值(Special value)offer(e)poll()peek()超时(Times out)offer( e, timeout, unit)poll( timeout, unit)1、ArrayBlockingQueue构造函数final Object[] items;//数组队列 int takeIndex;//取元素对应的索引(take/poll/peek/remove) int putIndex;//添加元素对应的索引(put/offer/add) int count;//队列中的元素数量 final ReentrantLock lock;//ReentrantLock锁 private final Condition notEmpty;//消费线程对应的condition private final Condition notFull;//生产线程对应的condition public ArrayBlockingQueue(int capacity) { this(capacity, false); } //capacity表示队列的初始化容量,一旦设置后就不能再改变 //fair是可选参数 默认是非公平锁 传入true的话变为公平锁 public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); //初始化一个容量为capacity的数组 this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); } 添加元素add/put/offer//队列满时,阻塞当前线程,当有空余元素时被唤醒;队列不满时,直接添加元素到队列中 public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) notFull.await(); enqueue(e); } finally { lock.unlock(); } } //队列未满时,添加元素到队列中并返回true;队列满时,抛出异常 public boolean add(E e) { return super.add(e); } //添加元素到队列中,成功返回true;失败返回false public boolean offer(E e) { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { if (count == items.length) return false; else { enqueue(e); return true; } } finally { lock.unlock(); } } //添加元素到队列中,并唤醒消费者线程 private void enqueue(E x) { final Object[] items = this.items; items[putIndex] = x; //添加元素的索引putIndex在队尾时直接变为队首,即数组可循环使用 if (++putIndex == items.length) putIndex = 0; count++; notEmpty.signal(); } 父类AbstractQueue中的add方法:public boolean add(E e) { if (offer(e)) return true; else throw new IllegalStateException("Queue full"); } 删除元素remove/take/poll//删除队列中takeIndex位置处的元素并返回该元素,如果该位置没有元素,返回null public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } } //队列为空时,直接阻塞当前线程并在队列中有元素时被唤醒;队列不为空时直接删除takeIndex位置处元素并返回该元素 public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } } //队列不为空,直接取出takeIndex所在的元素并返回; //队列为空时,阻塞等待timeout时间,如果在等待时间内队列中有新添加元素,那么该线程被唤醒并去消费该元素,如果timeout内依然没有新元素进入队列,直接超时返回null public E poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) { if (nanos <= 0) return null; nanos = notEmpty.awaitNanos(nanos); } return dequeue(); } finally { lock.unlock(); } } //删除队列中的元素o,如果o存在且不为null,删除并返回true;否则返回false public boolean remove(Object o) { if (o == null) return false; final Object[] items = this.items; final ReentrantLock lock = this.lock; lock.lock(); try { //判断队列不为空,为空直接返回false if (count > 0) { final int putIndex = this.putIndex; int i = takeIndex; do { //如果在遍历队列时找到目标元素,直接删除并返回true if (o.equals(items[i])) { removeAt(i); return true; } if (++i == items.length) i = 0; //遍历队列并且i!=putIndex(相等时表示队列已经遍历完) } while (i != putIndex); } return false; } finally { lock.unlock(); } } //删除队列中removeIndex位置的元素 void removeAt(final int removeIndex) { final Object[] items = this.items; //如果删除的位置在队首,直接删除并且索引takeIndex后移 if (removeIndex == takeIndex) { items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued(); } else { // final int putIndex = this.putIndex; for (int i = removeIndex;;) { int next = i + 1; if (next == items.length) next = 0; if (next != putIndex) { //删除的元素不在队尾,直接把队列后面的元素前移一位,然后继续循环 items[i] = items[next]; i = next; } else { //遍历到队尾的元素,将队尾元素置空,并将该位置赋值给添加索引putIndex并跳出循环 items[i] = null; this.putIndex = i; break; } } count--; if (itrs != null) itrs.removedAt(removeIndex); } notFull.signal(); } //取元素索引takeIndex对应的元素置空,并唤醒生产者线程 private E dequeue() { final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; //循环队列 if (++takeIndex == items.length) takeIndex = 0; //队列元素数量减1 count--; if (itrs != null) itrs.elementDequeued(); notFull.signal(); return x; } 其他操作peek/element等//队列为空时,返回null;队列不为空时返回队首takeIndex位置上的元素 public E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { return itemAt(takeIndex); } finally { lock.unlock(); } } final E itemAt(int i) { return (E) items[i]; } //父类AbstractQueue中 对于ArrayBlockingQueue来说,如果队列不为空,返回队首takeIndex位置上的元素;如果队列为空,直接抛出异常 public E element() { E x = peek(); if (x != null) return x; else throw new NoSuchElementException(); } 2、LinkedBlockingQueue构造函数private final int capacity;//队列容量,默认是Integer.MAX_VALUE private final AtomicInteger count = new AtomicInteger();//元素个数 transient Node<E> head;//链表头结点 private transient Node<E> last;//链表尾结点 //取元素的锁 如take/poll中使用 private final ReentrantLock takeLock = new ReentrantLock(); //取元素锁takeLock对应的条件队列(condition queue),链表为空时阻塞,不为空时被唤醒消费 private final Condition notEmpty = takeLock.newCondition(); //添加元素的锁 在put/offer中使用 private final ReentrantLock putLock = new ReentrantLock(); //添加元素锁putLock对应的条件队列(condition queue),链表满时阻塞,不满时被唤醒执行添加操作 private final Condition notFull = putLock.newCondition(); //初始化队列 默认容量是 public LinkedBlockingQueue() { this(Integer.MAX_VALUE); } //初始化队列容量及头结点 尾结点 public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null); } 静态内部类Nodestatic class Node<E> { E item; /** * One of: * - the real successor Node * - this Node, meaning the successor is head.next * - null, meaning there is no successor (this is the last node) */ Node<E> next; Node(E x) { item = x; } } 添加元素add/put/offerpublic void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); // Note: convention in all put/take/etc is to preset local var // holding count negative to indicate failure unless set. int c = -1; Node<E> node = new Node<E>(e); final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { while (count.get() == capacity) { notFull.await(); } enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); } public boolean offer(E e) { if (e == null) throw new NullPointerException(); final AtomicInteger count = this.count; if (count.get() == capacity) return false; int c = -1; Node<E> node = new Node<E>(e); final ReentrantLock putLock = this.putLock; putLock.lock(); try { if (count.get() < capacity) { enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal(); } } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); return c >= 0; } public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { if (e == null) throw new NullPointerException(); long nanos = unit.toNanos(timeout); int c = -1; final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { while (count.get() == capacity) { if (nanos <= 0) return false; nanos = notFull.awaitNanos(nanos); } enqueue(new Node<E>(e)); c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); return true; } //尾节点指向新入节点 尾指针指向新入节点 private void enqueue(Node<E> node) { last = last.next = node; } 删除元素remove/take/pollpublic boolean remove(Object o) { if (o == null) return false; fullyLock(); try { for (Node<E> trail = head, p = trail.next; p != null; trail = p, p = p.next) { if (o.equals(p.item)) { unlink(p, trail); return true; } } return false; } finally { fullyUnlock(); } } void fullyLock() { putLock.lock(); takeLock.lock(); } void fullyUnlock() { takeLock.unlock(); putLock.unlock(); } public E take() throws InterruptedException { E x; int c = -1; final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { while (count.get() == 0) { notEmpty.await(); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; } public E poll(long timeout, TimeUnit unit) throws InterruptedException { E x = null; int c = -1; long nanos = unit.toNanos(timeout); final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { while (count.get() == 0) { if (nanos <= 0) return null; nanos = notEmpty.awaitNanos(nanos); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; } public E poll() { final AtomicInteger count = this.count; if (count.get() == 0) return null; E x = null; int c = -1; final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { if (count.get() > 0) { x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; } void unlink(Node<E> p, Node<E> trail) { // assert isFullyLocked(); // p.next is not changed, to allow iterators that are // traversing p to maintain their weak-consistency guarantee. p.item = null; trail.next = p.next; if (last == p) last = trail; if (count.getAndDecrement() == capacity) notFull.signal(); } //删除链表队首元素 private E dequeue() { Node<E> h = head; Node<E> first = h.next; h.next = h; // help GC head = first; E x = first.item; first.item = null; return x; } 其他操作peek/element等//返回链表队首元素 public E peek() { if (count.get() == 0) return null; final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { Node<E> first = head.next; if (first == null) return null; else return first.item; } finally { takeLock.unlock(); } } ArrayBlockingQueue、LinkedBlockingQueue的异同相同点:· 都是先进先出队列(FIFO),任务的执行顺序与他们到达队列的顺序相同。区别:· ArrayBlockingQueue底层实现是数组,LinkedBlockingQueue底层实现是链表· ArrayBlockingQueue是有界队列,LinkedBlockingQueue默认是无界队列(Integer.MAX_VALUE),当然也可以传入count数量变成有界队列。3、SynchronousQueueSynchronousQueue并不是一个真正的队列,而是一种在线程间进行移交的机制。SynchronousQueue可以避免任务排队,可以直接将任务从生产者移交给消费者。一个线程(生产者线程)要将一个元素放入SynchronousQueue中,必须有另一个线程(消费者线程)等待接收这个元素。SynchronousQueue的使用public static void main(String[] args) throws InterruptedException { //初始化SynchronousQueue 默认是非公平队列 SynchronousQueue<Integer> queue = new SynchronousQueue<>(); //添加操作 PutRunnable putRunnable = new PutRunnable(queue); //删除操作 TakeRunnable takeRunnable = new TakeRunnable(queue); new Thread(putRunnable).start(); Thread.sleep(1500); new Thread(takeRunnable).start(); } static class PutRunnable implements Runnable { private SynchronousQueue<Integer> queue; PutRunnable(SynchronousQueue<Integer> queue) { this.queue = queue; } @Override public void run() { System.out.println("++生产者线程开始执行"); try { System.out.println("++生产者线程添加元素:10"); queue.put(10); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("++生产者线程结束"); } } } static class TakeRunnable implements Runnable { private SynchronousQueue<Integer> queue; TakeRunnable(SynchronousQueue<Integer> queue) { this.queue = queue; } @Override public void run() { System.out.println("--消费者线程开始执行"); try { System.out.println("--消费者线程取出元素:" + queue.take()); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("--消费者线程结束"); } } } 执行结果:++生产者线程开始执行 ++生产者线程添加元素:10 --消费者线程开始执行 --消费者线程取出元素:10 --消费者线程结束 ++生产者线程结束 从结果上可以看到:当生产者开始执行并调用put方法后,发现没有线程来消费(take),此时生产者线程没有继续执行,而是等待消费者线程来获取此元素并唤醒自己,最后生产者线程和消费者线程双双执行完毕并退出。SynchronousQueue浅析构造参数:public SynchronousQueue() { this(false); } //如果传入的是true,所有的生产者和消费者是按顺序一一对应的,即先到的生产者会先被消费;反之如果是false,就生产者和消费者的对应没有了顺序。 public SynchronousQueue(boolean fair) { transferer = fair ? new TransferQueue<E>() : new TransferStack<E>(); } 不同于ArrayBlockingQueue、LinkedBlockingQueue的内部缓存队列,SynchronousQueue内部并没有缓存数据,生产者线程进行添加操作(put)必须等待消费者线程的移除操作(take),反之一样。总结一下:与SynchronousQueue关联的添加(put)操作线程和消费(take)操作线程必须成对出现,并双双继续执行,如果只有一个操作(put或take),那么此线程会阻塞等待,直到另一个线程执行对应的操作时才会唤醒自己。SynchronousQueue适合在两个线程之间做数据交换工作。4、PriorityBlockingQueuePriorityBlockingQueue是一个无界的、基于堆的并发安全优先级队列。PriorityBlockingQueue中传入的元素不允许是null,并且必须要实现Comparable接口。public static void main(String[] args) throws InterruptedException { //Example2 ArrayList<User> list = new ArrayList<>(); list.add(new User("张三", 20)); list.add(new User("李四", 40)); list.add(new User("王五", 30)); list.add(new User("赵六", 10)); //初始化队列 PriorityBlockingQueue<User> priorityBlockingQueue = new PriorityBlockingQueue<>(); //添加元素 添加时是没有顺序的 priorityBlockingQueue.addAll(list); while (priorityBlockingQueue.size() > 0) { User user = priorityBlockingQueue.take(); System.out.println("name:" + user.getName() + ",age:" + user.getAge() + ",队列元素个数:" + priorityBlockingQueue.size()); } } static class User implements Comparable { User(String name, int age) { this.name = name; this.age = age; } String name; int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public int compareTo(Object o) { if (o instanceof User) { User user = (User) o; return age > user.getAge() ? -1 : 1; } return 0; } } 打印结果:name:李四,age:40,队列元素个数:3 name:王五,age:30,队列元素个数:2 name:张三,age:20,队列元素个数:1 name:赵六,age:10,队列元素个数:0 传入的元素时没有顺序的,但是通过compareTo排了优先级,age越大的优先级越高,所有最后的输出结果是按age的大小进行排序的。总结:· 传入PriorityBlockingQueue中的元素必须实现Comparable接口,通过此接口的compareTo方法来确定优先级,如果当前元素优先级高于比较的元素,返回一个负数(如-1),反之返回一个正数(如1)。· PriorityBlockingQueue中只有take的时候会加锁,put的时候并不会加锁,因为PriorityBlockingQueue是无界队列,支持在并发情况下去执行put操作。队列为空时,take方法会阻塞当前线程。
0
0
0
浏览量2008
IT大鲨鱼

提高开发效率!5个对开发者有用的Kotlin扩展函数

Kotlin 中扩展函数是一种允许在已有的类中添加新函数,而无需修改类定义或继承该类。通过使用扩展函数,我们可以轻松地为现有代码添加新功能和增强功能,下面就列举几个有用的扩展函数。runCatching代替try catch· try catch 方式:try { 100 / 0} catch (ex: Throwable) { ex.printStackTrace()}· runCatching 方式:runCatching { 100 / 0 } .onFailure { ex -> ex.printStackTrace() }如果不关心返回值,到这里就结束了,使用起来是不是更简单一些。如果需要继续对lambda表达式中的计算结果进行处理,那么继续往下看。runCatching是在Kotlin 1.3版本新增的,看下源码:@InlineOnly@SinceKotlin("1.3")public inline fun <T, R> T.runCatching(block: T.() -> R): Result<R> { return try { Result.success(block()) } catch (e: Throwable) { Result.failure(e) }}可以看到runCatching函数是一个扩展函数,函数接受一个lambda表达式block作为参数,并在T对象上执行这个lambda表达式,函数内部帮我们添加了try catch。· 如果lambda表达式成功执行并返回结果,则使用Result.success将结果包装成Result类型并返回;· 如果出现异常,则使用Result.failure将异常包装成Result类型并返回。看下 Result 里都有什么:列举一些Result中的常用函数:runCatching { 100 / 0 } .onSuccess { value -> log("onSuccess:$value") } //runCatching{}中执行成功,并传入执行结果 .onFailure { exception -> log("onFailure:$exception") } //runCatching{}中执行失败,并传入exception //.getOrDefault(0) //获取runCatching{}中执行的结果,如果是Failure直接返回默认值 .getOrElse { ex -> //获取runCatching{}中执行的结果,如果是Failure返回else内部的值。相比getOrDefault多了对exception的处理 log("exception:$ex") 100 } //.getOrThrow()//获取runCatching{}中执行的结果,如果是Failure直接抛异常 //.getOrNull() //获取runCatching{}中执行的结果,如果是Failure返回null //.exceptionOrNull() //如果有问题则返回exception;否则返回null .run { log("result:$this") }执行结果:E/TTT: onFailure:java.lang.ArithmeticException: divide by zeroE/TTT: exception:java.lang.ArithmeticException: divide by zeroE/TTT: result:100虽然100/0抛出了异常,还是可以通过getOrElse中重新赋值,并最终把值输出出来,如果需要其他处理,可以使用上述示例中的其他函数,按需使用即可。如果改为runCatching { 100 / 2 },其他代码不变,则输出结果:E/TTT: onSuccess:50E/TTT: result:50View的可见性fun View?.visible() { if (this?.visibility != View.VISIBLE) { this?.visibility = View.VISIBLE }}fun View?.invisible() { if (this?.visibility != View.INVISIBLE) { this?.visibility = View.INVISIBLE }}fun View?.gone() { if (this?.visibility != View.GONE) { this?.visibility = View.GONE }}使用它们:val toolbar: Toolbar = findViewById(R.id.toolbar)toolbar.visible() //设置visibletoolbar.invisible() //设置invisibletoolbar.gone() //设置gonedp、sp、px之间相互转换//dp转pxfun Number.dp2px(): Int { return ScreenUtil.dp2px(MyApplication.getApplication(), toFloat())}//sp转pxfun Number.sp2px(): Int { return ScreenUtil.sp2px(MyApplication.getApplication(), toFloat())}//px转dpfun Number.px2dp(): Int { return ScreenUtil.px2dp(MyApplication.getApplication(), toFloat())}//px转spfun Number.px2sp(): Int { return ScreenUtil.px2sp(MyApplication.getApplication(), toFloat())}object ScreenUtil { fun dp2px(@NonNull context: Context, dp: Float): Int { val scale = context.resources.displayMetrics.density return (dp * scale + 0.5f).toInt() } fun px2dp(@NonNull context: Context, px: Float): Int { val scale = context.resources.displayMetrics.density return (px / scale + 0.5f).toInt() } fun sp2px(@NonNull context: Context, spValue: Float): Int { val fontScale = context.resources.displayMetrics.scaledDensity return (spValue * fontScale + 0.5f).toInt() } fun px2sp(@NonNull context: Context, pxValue: Float): Int { val fontScale = context.resources.displayMetrics.scaledDensity return (pxValue / fontScale + 0.5f).toInt() }}使用它们:100.dp2px()100.sp2px()100.px2dp()100.px2sp()by lazy 替代findViewByIdby lazy是属性延迟委托,关于委托机制的用法参见:Kotlin | 10分钟搞定by委托机制。fun <T : View> Activity.id(id: Int) = lazy { findViewById<T>(id)}Activity中使用:class DemoActivity : AppCompatActivity() { private val mToolBar: Toolbar by id(R.id.toolbar) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_xxx) }}通过by lazy简化了控件的创建流程,避免每次创建都去调用findViewById(id),跟Butterknife的用法很类似。如果是在Fragment中使用呢?首先Fragment中并没有findViewById(id)函数,所以需要稍微改造一下:interface IRootView { fun rootView(): View}//注意,这里声明的是IRootView的扩展函数fun <T : View> IRootView.id(id: Int) = lazy { this.rootView().findViewById<T>(id)}abstract class BaseFragment : Fragment(), IRootView { private var mRootView: View? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { if (mRootView == null) { mRootView = inflater.inflate(getLayoutId(), container, false) } return mRootView } override fun rootView(): View { return mRootView!! } @LayoutRes abstract fun getLayoutId(): Int}· IRootView接口中只有一个rootView()方法,返回类型为android.view.View。· 扩展函数id<T : View>()是针对实现IRootView的对象进行扩展的。该函数需要传入Int类型参数表示控件ID,在调用时会使用lazy委托模式延迟初始化并返回T类型(泛型)控件。· BaseFragment继承自Fragment并且实现了IRootview接口。同时其内部也维护着mRootview变量用于缓存视图,在 onCreateView 方法中创建视图,并将其保存到变量mRootview中以便后面复用。子类Fragment中使用:class DemoFragment : BaseFragment() { private val mToolBar: Toolbar by id(R.id.toolbar) override fun getLayoutId(): Int = R.layout.fragment_demo override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) mToolBar.xxx //可以直接使用了 }}Toast、Logfun Activity.showToast(msg: String, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, msg, duration).show()}fun Activity.showToast(@StringRes msg: Int, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, msg, duration).show()}fun Fragment.showToast(msg: String, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(requireContext(), msg, duration).show()}fun Fragment.showToast(@StringRes message: Int, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(requireContext(), message, duration).show()}fun log(msg: String, tag: String = "TAG") { if (!BuildConfig.DEBUG) return Log.d(tag, msg)}使用它:showToast(R.string.action_settings) //1showToast("棒棒哒", Toast.LENGTH_LONG) //2log("log展示") //
0
0
0
浏览量1089
IT大鲨鱼

JNI 编程上手指南之数组访问

1. 引子JNI 中的数组分为基本类型数组和对象数组,它们的处理方式是不一样的,基本类型数组中的所有元素都是 JNI的基本数据类型,可以直接访问。而对象数组中的所有元素是一个类的实例或其它数组的引用,和字符串操作一样,不能直接访问 Java 传递给 JNI 层的数组,必须选择合适的 JNI 函数来访问和设置 Java 层的数组对象。2. 数组访问示例2.1 基本类型数组Java 层:private native double[] sumAndAverage(int[] numbers); JNI 层:JNIEXPORT jdoubleArray JNICALL Java_HelloJNI_sumAndAverage(JNIEnv *env, jobject obj, jintArray inJNIArray) { //类型转换 jintArray -> jint* jboolean isCopy; jint* inArray = env->GetIntArrayElements(inJNIArray, &isCopy); if (JNI_TRUE == isCopy) { cout << "C 层的数组是 java 层数组的一份拷贝" << endl; } else { cout << "C 层的数组指向 java 层的数组" << endl; } if(nullptr == inArray) return nullptr; //获取到数组长度 jsize length = env->GetArrayLength(inJNIArray); jint sum = 0; for(int i = 0; i < length; ++i) { sum += inArray[i]; } jdouble average = (jdouble)sum / length; //释放数组 env->ReleaseIntArrayElements(inJNIArray, inArray, 0); // release resource //构造返回数据,outArray 是指针类型,需要 free 或者 delete 吗?要的 jdouble outArray[] = {sum, average}; jdoubleArray outJNIArray = env->NewDoubleArray(2); if(NULL == outJNIArray) return NULL; //向 jdoubleArray 写入数据 env->SetDoubleArrayRegion(outJNIArray, 0, 2, outArray); return outJNIArray; } 2.2 引用类型数组Java 层:public native String[] operateStringArrray(String[] array); JNI 层:JNIEXPORT jobjectArray JNICALL Java_com_xxx_jni_JNIArrayManager_operateStringArrray (JNIEnv * env, jobject object, jobjectArray objectArray_in) { //获取到长度信息 jsize size = env->GetArrayLength(objectArray_in); /*******获取从JNI传过来的String数组数据**********/ for(int i = 0; i < size; i++) { jstring string_in= (jstring)env->GetObjectArrayElement(objectArray_in, i); char *char_in = env->GetStringUTFChars(str, nullptr); } /***********从JNI返回String数组给Java层**************/ jclass clazz = env->FindClass("java/lang/String"); jobjectArray objectArray_out; const int len_out = 5; objectArray_out = env->NewObjectArray(len_out, clazz, NULL); char * char_out[]= { "Hello,", "world!", "JNI", "is", "fun" }; jstring temp_string; for( int i= 0; i < len_out; i++ ) { temp_string = env->NewStringUTF(char_out[i]); env->SetObjectArrayElement(objectArray_out, i, temp_string); } return objectArray_out; } 2.3 二维数组Java 层:public native int[][] operateTwoIntDimArray(int[][] array_in); JNI 层:JNIEXPORT jobjectArray JNICALL Java_com_xxx_jni_JNIArrayManager_operateTwoIntDimArray(JNIEnv * env, jobject object, jobjectArray objectArray_in) { /********** 解析从Java得到的int型二维数组 **********/ int i, j ; const int row = env->GetArrayLength(objectArray_in);//获取二维数组的行数 jarray array = (jarray)env->GetObjectArrayElement(objectArray_in, 0); const int col = env->GetArrayLength(array);//获取二维数组每行的列数 //根据行数和列数创建int型二维数组 jint intDimArrayIn[row][col]; for(i =0; i < row; i++) { array = (jintArray)env->GetObjectArrayElement(objectArray_in, i); //操作方式一,这种方法会申请natvie memory内存 jint *coldata = env->GetIntArrayElements((jintArray)array, NULL ); for (j=0; j<col; j++) { intDimArrayIn [i] [j] = coldata[j]; //取出JAVA类中int二维数组的数据,并赋值给JNI中的数组 } //操作方式二,赋值,这种方法不会申请内存 // env->GetIntArrayRegion((jintArray)array, 0, col, (jint*)&intDimArrayIn[i]); env->ReleaseIntArrayElements((jintArray)array, coldata,0 ); } /**************创建一个int型二维数组返回给Java**************/ const int row_out = 2;//行数 const int col_out = 2;//列数 //获取数组的class jclass clazz = env->FindClass("[I");//一维数组的类 //新建object数组,里面是int[] jobjectArray intDimArrayOut = env->NewObjectArray(row_out, clazz, NULL); int tmp_array[row_out][col_out] = {{0,1},{2,3}}; for(i = 0; i< row_out; i ++) { jintArray intArray = env->NewIntArray(col_out); env->SetIntArrayRegion(intArray, 0, col_out, (jint*)&tmp_array[i]); env->SetObjectArrayElement(intDimArrayOut, i, intArray); } return intDimArrayOut; } 3. JNI 字符串处理函数GetArrayLengthjsize (GetArrayLength)(JNIEnv env, jarray array); 返回数组中的元素个数NewObjectArrayjobjectArray NewObjectArray (JNIEnv *env, jsize length, jclass elementClass, jobject initialElement); 构建 JNI 引用类型的数组,它将保存类 elementClass 中的对象。所有元素初始值均设为 initialElement,一般使用 NULL 就好。如果系统内存不足,则抛出 OutOfMemoryError 异常GetObjectArrayElement和SetObjectArrayElementjobject GetObjectArrayElement (JNIEnv *env, jobjectArray array, jsize index) 返回 jobjectArray 数组的元素,通常是获取 JNI 引用类型数组元素。如果 index 不是数组中的有效下标,则抛出 ArrayIndexOutOfBoundsException 异常。void SetObjectArrayElement (JNIEnv *env, jobjectArray array, jsize index, jobject value) 设置 jobjectArray 数组中 index 下标对象的值。如果 index 不是数组中的有效下标,则会抛出 ArrayIndexOutOfBoundsException 异常。如果 value 的类不是数组元素类的子类,则抛出 ArrayStoreException 异常。New<PrimitiveType>Array 函数集NativeTypeArray New<PrimitiveType>Array (JNIEnv* env, jsize size) 用于构造 JNI 基本类型数组对象。在实际应用中把 PrimitiveType 替换为某个实际的基本类型数据类型,然后再将 NativeType 替换成对应的 JNI Native Type 即可,具体的:函数名 返回类型 NewBooleanArray() jbooleanArray NewByteArray() jbyteArray NewCharArray() jcharArray NewShortArray() jshorArray NewIntArray() jintArray NewLongArray() jlongArray NewFloatArray() jfloatArray NewDoubleArray() jdoubleArray Get/ReleaseArrayElements函数集NativeType* Get<PrimitiveType>ArrayElements(JNIEnv *env, NativeTypeArray array, jboolean *isCopy) 该函数用于将 JNI 数组类型转换为 JNI 基本数据类型数组,在实际使用过程中将 PrimitiveType 替换成某个实际的基本类型元素访问函数,然后再将NativeType替换成对应的 JNI Native Type 即可:函数名 转换前类型 转换后类型 GetBooleanArrayElements() jbooleanArray jboolean* GetByteArrayElements() jbyteArray jbyte* GetCharArrayElements() jcharArray jchar* GetShortArrayElements() jshortArray jshort* GetIntArrayElements() jintArray jint* GetLongArrayElements() jlongArray jlong* GetFloatArrayElements() jfloatArray jfloat* GetDoubleArrayElements() jdoubleArray jdouble* void Release<PrimitiveType>ArrayElements (JNIEnv *env, NativeTypeArray array, NativeType *elems,jint mode); 该函数用于通知 JVM,数组不再使用,可以清理先关内存了。在实际使用过程中将 PrimitiveType 替换成某个实际的基本类型元素访问函数,然后再将 NativeType 替换成对应的 JNI Native Type 即可:函数名 NativeTypeArray NativeType ReleaseBooleanArrayElements() jbooleanArray jboolean ReleaseByteArrayElements() jbyteArray jbyte ReleaseCharArrayElements() jcharArray jchar ReleaseShortArrayElements() jshortArray jshort ReleaseIntArrayElements() jintArray jint ReleaseLongArrayElements() jlongArray jlong ReleaseFloatArrayElements() jfloatArray jfloat ReleaseDoubleArrayElements() jdoubleArray jdouble Get/Set<PrimitiveType>ArrayRegionvoid Set<PrimitiveType>ArrayRegion (JNIEnv *env, NativeTypeArray array, jsize start, jsize len, NativeType *buf); 该函数用于将基本类型数组某一区域复制到 JNI 数组类型中。在实际使用过程中将 PrimitiveType 替换成某个实际的基本类型元素访问函数,然后再将 NativeType 替换成对应的 JNI Native Type 即可:函数名 NativeTypeArray NativeType SetBooleanArrayRegion() jbooleanArray jboolean SetByteArrayRegion() jbyteArray jbyte SetCharArrayRegion() jcharArray jchar SetShortArrayRegion() jshortArray jshort SetIntArrayRegion() jintArray jint SetLongArrayRegion() jlongArray jlong SetFloatArrayRegion() jfloatArray jfloat SetDoubleArrayRegion() jdoubleArray jdouble
0
0
0
浏览量145
IT大鲨鱼

Jetpack | Lifecycle 库新旧版本使用姿势对比

回顾Lifecycle的用处Lifecycle可以将一个类或组件变成生命周期感知型的,并且可执行相应操作来响应另一个组件(如 activity 和 fragment)的生命周期状态的变化。Lifecycle使得代码更有条理性、精简、易于维护。Lifecycle中的两个角色:1. LifecycleOwner: 生命周期拥有者,如Activity/Fragment等类都实现了该接口并通过getLifecycle()获得Lifecycle,进而可通过addObserver()添加观察者。2. LifecycleObserver: 生命周期观察者,实现该接口后就可以添加到Lifecycle中,从而在被观察者类生命周期发生改变时能马上收到通知。实现LifecycleOwner的生命周期拥有者可与实现LifecycleObserver的观察者完美配合。老的使用方式(@OnLifecycleEvent 不再推荐使用)open class MyLifeCycleObserver : LifecycleObserver { @OnLifecycleEvent(value = Lifecycle.Event.ON_START) fun connect(owner: LifecycleOwner) { Log.e(JConsts.LIFE_TAG, "Lifecycle.Event.ON_CREATE:connect") } @OnLifecycleEvent(value = Lifecycle.Event.ON_STOP) fun disConnect() { Log.e(JConsts.LIFE_TAG, "Lifecycle.Event.ON_DESTROY:disConnect") }}//Activity中class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //添加LifecycleObserver观察者 lifecycle.addObserver(MyLifeCycleObserver()) }}以上就是老的使用方式了,现在已经不推荐使用,@OnLifecycleEvent 注解方式也标记为@Deprecated 了,执行结果很简单就不再贴出来了,有兴趣的可以看开头的文章介绍。另外自定义 LifecycleOwner、Application/Service 中使用 Lifecycle、Lifecycle 原理都不再本文重复介绍了。新的使用方式(DefaultLifecycleObserver 推荐方式)open class MyLifeCycleObserver : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { Log.e(KConsts.LIFE_TAG, "Lifecycle.Event.ON_START:connect") } override fun onStop(owner: LifecycleOwner) { Log.e(KConsts.LIFE_TAG, "Lifecycle.Event.ON_STOP:disConnect") }}//Activity/Fragment中的使用方式不变:lifecycle.addObserver(MyLifeCycleObserver())可以看到新方式中已经没有 @OnLifecycleEvent 注解,并且没有实现 LifecycleObserver 接口,而是实现了一个叫DefaultLifecycleObserver的接口,来看这个接口的定义: DefaultLifecycleObserver -> FullLifecycleObserver -> LifecycleObserver//DefaultLifecycleObserver -> FullLifecycleObserver public interface DefaultLifecycleObserver extends FullLifecycleObserver { @Override default void onCreate(@NonNull LifecycleOwner owner) {} @Override default void onStart(@NonNull LifecycleOwner owner) {} @Override default void onResume(@NonNull LifecycleOwner owner) {} @Override default void onPause(@NonNull LifecycleOwner owner) {} @Override default void onStop(@NonNull LifecycleOwner owner) {} @Override default void onDestroy(@NonNull LifecycleOwner owner) {}}// FullLifecycleObserver -> LifecycleObserverinterface FullLifecycleObserver extends LifecycleObserver { void onCreate(LifecycleOwner owner); void onStart(LifecycleOwner owner); void onResume(LifecycleOwner owner); void onPause(LifecycleOwner owner); void onStop(LifecycleOwner owner); void onDestroy(LifecycleOwner owner);}新的使用方式中,不必自己新增@OnLifecycleEvent 注解的方法了,而只需要实现DefaultLifecycleObserver接口并按需重写对应生命周期的方法即可。两者对比 LifecycleRegistry负责添加和管理各个LifecycleObserver,最终是从LifecycleOwner中的生命周期Event传到了LifecycleObserver中对应的方法。在旧的实现方式中,LifecycleObserver的最终实现类是ReflectiveGenericLifecycleObserver(见上图),当调用其对应的onStateChanged()方法后,是通过反射找到对应的@OnLifecycleEvent 注解并调用对应的方法的。那么新的实现方式最后如何调用观察者里对应的方法的呢? 上图只把最后接收事件的地方放出来了,前面的不变,可以看到新方式中没有用到LifecycleEventObserver接口,那么如何触发onStateChanged()回调呢?来需要个适配器了,一块看下源码中是如何处理的: addObserver(LifecycleObserver observer) 添加观察者后,如果使用的新的方式,系统会将传入的 FullLifecycleObserver 转换成 FullLifecycleObserverAdapter,从名字看就是一个适配器了,看看里面: 果然,FullLifecycleObserverAdapter 实现了LifecycleEventObserver接口,从而也可以执行onStateChanged()方法了,在onStateChanged()中根据传入的Event事件回调FullLifecycleObserver中对应的生命周期方法,新方式不用再去反射调用,提高了性能。结论 · Lifecycle 库从 2.4.0-beta01开始,建议使用 DefaultLifecycleObserver方式;· 如果 @OnLifecycleEvent 与 DefaultLifecycleObserver都写了(一般也不会这么实现),那么会优先走DefaultLifecycleObserver中回调方法,@OnLifecycleEvent 声明的方法不再生效。
0
0
0
浏览量2007
IT大鲨鱼

Android Jetpack系列之MVVM使用及封装

Android开发架构如果开发过程中大家各自为战,没有统一规范,久而久之,项目代码会变得混乱且后续难以维护。当使用统一的架构模式后,有很多的好处,如:· 统一开发规范,使得代码整洁、规范,后续易于维护及扩展· 提高开发效率(尤其在团队人员较多时)· 模块单一职责,使得模块专注自己内部(面向对象),模块间解耦总之,开发架构是前人总结出来的一套行之有效的开发模式,目的是达到高内聚,低耦合的效果,使得项目代码更健壮、易维护。Android中常见的架构模式有MVC(Model-View-Controller)、MVP(Model-View-Presenter)、MVVM(Model-View-ViewModel),一起来看下各自的特点:MVCMVC(Model-View-Controller)是比较早期的架构模式,模式整体也比较简单。MVC模式将程序分成了三个部分:· Model模型层:业务相关的数据(如网络请求数据、本地数据库数据等)及其对数据的处理· View视图层:页面视图(通过XML布局编写视图层),负责接收用户输入、发起数据请求及展示结果页面· Controller控制器层:M与V之间的桥梁,负责业务逻辑MVC特点:· 简单易用:上图表述了数据整个流程:View接收用户操作,通过Controller去处理业务逻辑,并通过Model去获取/更新数据,然后Model层又将最新的数据传回View层进行页面展示。· 架构简单的另一面往往是对应的副作用:由于XML布局能力弱,我们的View层的很多操作都是写在Activity/Fragment中,同时,Controller、Model层的代码也大都写在Activity/Fragment中,这就会导致一个问题,当业务逻辑比较复杂时,Activity/Fragment中的代码量会很大,其违背了类单一职责,不利于后续扩展及维护。尤其是后期你刚接手的项目,一个Activity/Fragment类中的代码动辄上千行代码,那感觉着实酸爽: 当然,如果业务很简单,使用MVC模式还是一种不错的选择。MVPMVP(Model-View-Presenter),架构图如下: MVP各模块职责如下:· Model模型:业务相关的数据(如网络请求数据、本地数据库数据等)及其对数据的处理· View视图:页面视图(Activity/Fragment),负责接收用户输入、发起数据请求及展示结果页面· Presenter:M与V之间的桥梁,负责业务逻辑MVP特点: View层接收用户操作,并通过持有的Presenter去处理业务逻辑,请求数据;接着Presenter层通过Model去获取数据,然后Model又将最新的数据传回Presenter层,Presenter层又持有View层的引用,进而将数据传给View层进行展示。MVP相比MVC的几处变化:· View层与Model层不再交互,而是通过Presenter去进行联系· 本质上MVP是面向接口编程,Model/View/Presenter每层的职责分工明确,当业务复杂时,整个流程逻辑也是很清晰的当然,MVP也不是十全十美的,MVP本身也存在以下问题:· View层会抽象成IView接口,并在IView中声明一些列View相关的方法;同样的,Presenter会被抽象成IPresenter接口及其一些列方法,每当实现一个功能时,都需要编写多个接口及其对应的方法,实现起来相对比较繁琐,而且每次有改动时,对应的接口方法也基本都会再去改动。· View层与Presenter层相互持有,当View层关闭时,由于Presenter层不是生命周期感知的,可能会导致内存泄漏甚至是崩溃。 ps:如果你的项目中使用了RxJava,可以使用 AutoDispose 自动解绑。MVVMMVVM(Model-View-ViewModel),架构图如下: MVVM各职责如下:· Model模型:业务相关的数据(如网络请求数据、本地数据库数据等)及其对数据的处理· View视图:页面视图(Activity/Fragment),负责接收用户输入、发起数据请求及展示结果页面· ViewModel:M与V之间的桥梁,负责业务逻辑MVVM特点:· View层接收用户操作,并通过持有的ViewModel去处理业务逻辑,请求数据;· ViewModel层通过Model去获取数据,然后Model又将最新的数据传回ViewModel层,到这里,ViewModel与Presenter所做的事基本是一样的。但是ViewModel不会也不能持有View层的引用,而是View层会通过观察者模式监听ViewModel层的数据变化,当有新数据时,View层能自动收到新数据并刷新界面。UI驱动 vs 数据驱动MVP中,Presenter中需要持有View层的引用,当数据变化时,需要主动调用View层对应的方法将数据传过去并进行UI刷新,这种可以认为是UI驱动;而MVVM中,ViewModel并不会持有View层的引用,View层会监听数据变化,当ViewModel中有数据更新时,View层能直接拿到新数据并完成UI更新,这种可以认为是数据驱动,显然,MVVM相比于MVP来说更加解耦。MVVM的具体实现上面介绍了MVC/MVP/MVVM的各自特点,其中MVC/MVP的具体使用方式,本文不再展开实现,接下来主要聊一下MVVM的使用及封装,MVVM也是官方推荐的架构模式。Jetpack MVVMJetpack是官方推出的一系列组件库,使用组件库开发有很多好处,如:· 遵循最佳做法:采用最新的设计方法构建,具有向后兼容性,可以减少崩溃和内存泄漏· 消除样板代码:开发者可以更好地专注业务逻辑· 减少不一致:可以在各种Android版本中运行,兼容性更好。为了实现上面的MVVM架构模式,Jetpack提供了多个组件来实现,具体来说有Lifecycle、LiveData、ViewModel(这里的ViewModel是MVVM中ViewModel层的具体实现),其中Lifecycle负责生命周期相关;LiveData赋予类可观察,同时还是生命周期感知的(内部使用了Lifecycle);ViewModel旨在以注重生命周期的方式存储和管理界面相关的数据,针对这几个库的详细介绍及使用方式不再展开,有兴趣的可以参见前面的文章:· Android Jetpack系列之Lifecycle· Android Jetpack系列之LiveData· Android Jetpack系列之ViewModel通过这几个库,就可以实现MVVM了,官方也发布了MVVM的架构图: 其中Activity/Fragment为View层,ViewModel+LiveData为ViewModel层,为了统一管理网络数据及本地数据数据,又引入了Repository中间管理层,本质上是为了更好地管理数据,为了简单把他们统称为Model层吧。使用举例· View层代码://MvvmExampleActivity.ktclass MvvmExampleActivity : BaseActivity() { private val mTvContent: TextView by id(R.id.tv_content) private val mBtnQuest: Button by id(R.id.btn_request) private val mToolBar: Toolbar by id(R.id.toolbar) override fun getLayoutId(): Int { return R.layout.activity_wan_android } override fun initViews() { initToolBar(mToolBar, "Jetpack MVVM", true) } override fun init() { //获取ViewModel实例,注意这里不能直接new,因为ViewModel的生命周期比Activity长 mViewModel = ViewModelProvider(this).get(WanViewModel::class.java) mBtnQuest.setOnClickListener { //请求数据 mViewModel.getWanInfo() } //ViewModel中的LiveData注册观察者并监听数据变化 mViewModel.mWanLiveData.observe(this) { list -> val builder = StringBuilder() for (index in list.indices) { //每条数据进行折行显示 if (index != list.size - 1) { builder.append(list[index]) builder.append("\n\n") } else { builder.append(list[index]) } } mTvContent.text = builder.toString() } }}· ViewModel层代码://WanViewModel.ktclass WanViewModel : ViewModel() { //LiveData val mWanLiveData = MutableLiveData<List<WanModel>>() //loading val loadingLiveData = SingleLiveData<Boolean>() //异常 val errorLiveData = SingleLiveData<String>() //Repository中间层 管理所有数据来源 包括本地的及网络的 private val mWanRepo = WanRepository() fun getWanInfo(wanId: String = "") { //展示Loading loadingLiveData.postValue(true) viewModelScope.launch(Dispatchers.IO) { try { val result = mWanRepo.requestWanData(wanId) when (result.state) { State.Success -> mWanLiveData.postValue(result.data) State.Error -> errorLiveData.postValue(result.msg) } } catch (e: Exception) { error(e.message ?: "") } finally { loadingLiveData.postValue(false) } } }}· Repository层(Model层)代码:class WanRepository { //请求网络数据 suspend fun requestWanData(drinkId: String): BaseData<List<WanModel>> { val service = RetrofitUtil.getService(DrinkService::class.java) val baseData = service.getBanner() if (baseData.code == 0) { //正确 baseData.state = State.Success } else { //错误 baseData.state = State.Error } return baseData }这里只通过Retrofit请求了网络数据 玩Android 开放API,如果需要添加本地数据,只需要在方法里添加本地数据处理即可,即 Repository是数据的管理中间层,对数据进行统一管理,ViewModel层中不需要关心数据的来源,大家各司其职即可,符合单一职责,代码可读性更好,同时也更加解耦。在View层点击按钮请求数据,执行结果如下:以上就完成了一次网络请求,相比于MVP,MVVM既不用声明多个接口及方法,同时ViewModel也不会像Presenter那样去持有View层的引用,而是生命周期感知的,MVVM方式更加解耦。封装上一节介绍了Jetpack MVVM的使用例子,可以看到有一些代码逻辑是可以抽离出来封装到公共部分的,那么本节就尝试对其做一次封装。首先,请求数据时可能会展示Loading,请求完后可能是空数据、错误数据,对应下面的IStatusView接口声明:interface IStatusView { fun showEmptyView() //空视图 fun showErrorView(errMsg: String) //错误视图 fun showLoadingView(isShow: Boolean) //展示Loading视图}因为ViewModel是在Activity中初始化的,所以可以封装成一个Base类:abstract class BaseMvvmActivity<VM : BaseViewModel> : BaseActivity(), IStatusView { protected lateinit var mViewModel: VM protected lateinit var mView: View private lateinit var mLoadingDialog: LoadingDialog override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mLoadingDialog = LoadingDialog(this, false) mViewModel = getViewModel()!! init() registerEvent() } /** * 获取ViewModel 子类可以复写,自行初始化 */ protected open fun getViewModel(): VM? { //当前对象超类的Type val type = javaClass.genericSuperclass //ParameterizedType表示参数化的类型 if (type != null && type is ParameterizedType) { //返回此类型实际类型参数的Type对象数组 val actualTypeArguments = type.actualTypeArguments val tClass = actualTypeArguments[0] return ViewModelProvider(this).get(tClass as Class<VM>) } return null } override fun showLoadingView(isShow: Boolean) { if (isShow) { mLoadingDialog.showDialog(this, false) } else { mLoadingDialog.dismissDialog() } } override fun showEmptyView() { ...... } //错误视图 并且可以重试 override fun showErrorView(errMsg: String) { ....... } private fun registerEvent() { //接收错误信息 mViewModel.errorLiveData.observe(this) { errMsg -> showErrorView(errMsg) } //接收Loading信息 mViewModel.loadingLiveData.observe(this, { isShow -> showLoadingView(isShow) }) } abstract fun init()}Base类中初始化ViewModel,还可以通过官方activity-ktx、fragment-ktx扩展库,初始化方式:val model: VM by viewModels()。子类中继承如下:class MvvmExampleActivity : BaseMvvmActivity<WanViewModel>() { private val mTvContent: TextView by id(R.id.tv_content) private val mBtnQuest: Button by id(R.id.btn_request) private val mToolBar: Toolbar by id(R.id.toolbar) override fun getLayoutId(): Int { return R.layout.activity_wan_android } override fun initViews() { initToolBar(mToolBar, "Jetpack MVVM", true) } override fun init() { mBtnQuest.setOnClickListener { //请求数据 mViewModel.getWanInfo() } /** * 这里使用了扩展函数,等同于mViewModel.mWanLiveData.observe(this) {} */ observe(mViewModel.mWanLiveData) { list -> val builder = StringBuilder() for (index in list.indices) { //每条数据进行折行显示 if (index != list.size - 1) { builder.append(list[index]) builder.append("\n\n") } else { builder.append(list[index]) } } mTvContent.text = builder.toString() } }}我们把ViewModel的初始化放到了父类里进行,代码看上去更简单了。监听数据变化mViewModel.mWanLiveData.observe(this) {} 方式改成observe(mViewModel.mWanLiveData) {}方式,少传了一个LifecycleOwner,其实这是一个扩展函数,如下:fun <T> LifecycleOwner.observe(liveData: LiveData<T>, observer: (t: T) -> Unit) { liveData.observe(this, { observer(it) })}ps:我们初始化View控件时,如 private val mBtnQuest: Button by id(R.id.btn_request),依然使用了扩展函数,如下:fun <T : View> Activity.id(id: Int) = lazy { findViewById<T>(id)}不用像写java代码中那样时刻要想着判空,同时只会在使用时才会进行初始化,很实用!说回来,接着是ViewModel层的封装,BaseViewModel.kt:abstract class BaseViewModel : ViewModel() { //loading val loadingLiveData = SingleLiveData<Boolean>() //异常 val errorLiveData = SingleLiveData<String>() /** * @param request 正常逻辑 * @param error 异常处理 * @param showLoading 请求网络时是否展示Loading */ fun launchRequest( showLoading: Boolean = true, error: suspend (String) -> Unit = { errMsg -> //默认异常处理,子类可以进行覆写 errorLiveData.postValue(errMsg) }, request: suspend () -> Unit ) { //是否展示Loading if (showLoading) { loadStart() } //使用viewModelScope.launch开启协程 viewModelScope.launch(Dispatchers.IO) { try { request() } catch (e: Exception) { error(e.message ?: "") } finally { if (showLoading) { loadFinish() } } } } private fun loadStart() { loadingLiveData.postValue(true) } private fun loadFinish() { loadingLiveData.postValue(false) }}扩展一下: 1、上面执行网络请求时,使用viewModelScope.launch来启动协程,引入方式:implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'这样就可以直接在ViewModel中启动协程并且当ViewModel生命周期结束时协程也会自动关闭,避免使用GlobalScope.launch { }或MainScope().launch { }还需自行关闭协程, 当然,如果是在Activity/Fragment、liveData中使用协程,也可以按需引入:implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'2、另外细心的读者可能观察到,上面我们的Loading、Error信息监听都是用的SingleLiveData,把这个类打代码贴一下:/** * 多个观察者存在时,只有一个Observer能够收到数据更新 * https://github.com/android/architecture-samples/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java */class SingleLiveData<T> : MutableLiveData<T>() { companion object { private const val TAG = "SingleLiveEvent" } private val mPending = AtomicBoolean(false) @MainThread override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { if (hasActiveObservers()) { Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") } // Observe the internal MutableLiveData super.observe(owner) { t -> //如果expect为true,那么将值update为false,方法整体返回true, //即当前Observer能够收到更新,后面如果还有订阅者,不能再收到更新通知了 if (mPending.compareAndSet(true, false)) { observer.onChanged(t) } } } override fun setValue(@Nullable value: T?) { //AtomicBoolean中设置的值设置为true mPending.set(true) super.setValue(value) } /** * Used for cases where T is Void, to make calls cleaner. */ @MainThread fun call() { value = null }}可以看到SingleLiveData还是继承自MutableLiveData,区别是当多个观察者存在时,只有一个Observer能够收到数据更新,本质上是在observe()时通过CAS加了限制,注释已经很详细了,不再赘述。子类中继承如下:class WanViewModel : BaseViewModel() { //LiveData val mWanLiveData = MutableLiveData<List<WanModel>>() //Repository中间层 管理所有数据来源 包括本地的及网络的 private val mWanRepo = WanRepository() fun getWanInfo(wanId: String = "") { launchRequest { val result = mWanRepo.requestWanData(wanId) when (result.state) { State.Success -> mWanLiveData.postValue(result.data) State.Error -> errorLiveData.postValue(result.msg) } } }}最后是对Model层的封装,BaseRepository.kt:open class BaseRepository { suspend fun <T : Any> executeRequest( block: suspend () -> BaseData<T> ): BaseData<T> { val baseData = block.invoke() if (baseData.code == 0) { //正确 baseData.state = State.Success } else { //错误 baseData.state = State.Error } return baseData }}数据基类BaseData.kt:class BaseData<T> { @SerializedName("errorCode") var code = -1 @SerializedName("errorMsg") var msg: String? = null var data: T? = null var state: State = State.Error}enum class State { Success, Error}子类中继承如下:class WanRepository : BaseRepository() { suspend fun requestWanData(drinkId: String): BaseData<List<WanModel>> { val service = RetrofitUtil.getService(DrinkService::class.java) return executeRequest { service.getBanner() } }}
0
0
0
浏览量1215
IT大鲨鱼

Kotlin | 10分钟搞定by委托机制

类委托委托机制是一种非常灵活的语言特性,它可以让我们将对象的某些属性或方法委托给其他对象来处理。示例:interface ISay { fun sayHello()}class DelegateImp : ISay { override fun sayHello() { println("sayHello from DelegateImp") }}//delegate传入DelegateImp()class RealImp (val delegate : ISay) : ISay { override fun sayHello() { delegate.sayHello() }}· RealImp 类将 sayHello() 方法的实现委托给了 DelegateImp 对象,从而实现了代码复用和模块化。· 当调用 RealImp 的 sayHello() 方法时,实际上是调用了 DelegateImp 对象的 sayHello()方法。Kotlin 中通过使用 by 关键字进行委托,上述 RealImp 的实现方式可以直接通过下面的方式来替代:class RealImp2 : ISay by DelegateImp() //方式1class RealImp3(delegate: ISay) : ISay by delegate //方式2上述两种方式的结果是一样的,可以看到 by 关键字后面的表达式是为了得到 ISay 接口的实例对象。最后执行:RealImp(DelegateImp()).sayHello() //方式1RealImp2().sayHello() //方式2RealImp3(DelegateImp()).sayHello() //方式3上述执行结果都是一样的:sayHello from DelegateImp属性委托除了上一节中的接口类委托,Kotlin 还支持属性委托,语法模板如下:val/var <属性名>: <类型> by <表达式>接口是把接口方法委托出去,那属性要委托什么呢?很简单,对于一个属性,无非有两个操作:获取属性以及修改属性,也就是对应的 get()/set() 。属性委托即是将对应的 get()/set() 操作分发给指定委托对象的 getValue()/setValue() 方法执行;当然,如果是 val 修饰的属性,只需要提供 getValue() 即可。示例:class Delegate { //对应属性中的get(),表示获取数据 operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef,${property.name}" } //对应属性中的set(),表示设置数据,只有var的属性会有 operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$thisRef , ${property.name} , $value") }}· thisRef: 请求其值的对象,即被委托对象的实例· property: 被委托对象属性的元数据。包含了被委托对象属性的名称、类型、可见性等信息。· value: setValue() 中要设置的值。使用示例:class DelegateProperty { var p1: String by Delegate()}fun main() { val property = DelegateProperty() println(property.p1) //getValue() property.p1 = "小马快跑" //setValue()}执行结果:DelegateProperty@6d5380c2, p1 //getValue()DelegateProperty@6d5380c2 , p1 , 小马快跑 //setValue()上述 Delegate中的 getValue()/setValue()方法需要我们手动编写,有点麻烦,Kotlin为我们提供了ReadOnlyProperty、ReadWriteProperty接口://只读public fun interface ReadOnlyProperty<in T, out V> { public operator fun getValue(thisRef: T, property: KProperty<*>): V}//读写都支持public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> { public override operator fun getValue(thisRef: T, property: KProperty<*>): V public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)}使用示例:class DelegateR : ReadOnlyProperty<Any, String> { override fun getValue(thisRef: Any, property: KProperty<*>): String { return "getValue:$thisRef,${property.name}" }}class DelegateRW : ReadWriteProperty<Any, String> { override fun getValue(thisRef: Any, property: KProperty<*>): String { return "getValue:$thisRef,${property.name}" } override fun setValue(thisRef: Any, property: KProperty<*>, value: String) { println("setValue:$thisRef,${property.name},$value") }}class DelegateProperty { val p2: String by DelegateR() var p3: String by DelegateRW()}fun main() { val property = DelegateProperty() println(property.p2) //p2取值 println(property.p3) //p3取值 property.p3 = "小马快跑" //p3设值}执行结果:getValue:DelegateProperty@77a567e1,p2getValue:DelegateProperty@77a567e1,p3setValue:DelegateProperty@77a567e1,p3,小马快跑可以看到利用ReadOnlyProperty、ReadWriteProperty实现的结果跟手动实现的结果是一致的,但是对比手动编写更方便、更快捷。延迟委托 (by lazy)//1public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)//2public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> = when (mode) { LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer) LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer) LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) }lazy() 是接受一个 lambda 表达式, 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用属性的 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果; 后续调用直接返回已经记录的结果。示例:val lazyView by lazy { println("我只有第一次初始化的时候才执行哟~") "Come on"}//调用:println("第1次:${property.lazyView}")println("第2次:${property.lazyView}")//执行结果:我只有第一次初始化的时候才执行哟~第1次:Come on第2次:Come on从结果可以看出只有第一次执行时走到了 lambda 表达式中的逻辑,第二次执行时直接返回第一次生成的结果。 另外再调用 lazy() 函数时可以传入一个 LazyThreadSafetyMode 参数,有下面三个值,分别代表不同的意思:· LazyThreadSafetyMode.SYNCHRONIZED:该值只在一个线程中计算,并且所有线程会看到相同的值。· LazyThreadSafetyMode.PUBLICATION:可以在并发访问未初始化的Lazy实例值时调用多次,但只有第一次返回的值将被用作Lazy实例的值。· LazyThreadSafetyMode.NONE:不会有任何线程安全的保证以及相关的开销,当初始化与使用总是位于相同线程中时使用。如果lazy 属性的求值时不传 LazyThreadSafetyMode 参数,那么默认情况下走的是是LazyThreadSafetyMode.SYNCHRONIZED模式。使用案例先编写一个顶级扩展函数,注意其内部使用 lazy 包裹:fun <T : View> Activity.id(id: Int) = lazy { findViewById<T>(id)}接下来就可以使用了://Activityclass ViewPager2DispatchActivity : AppCompatActivity() { //注意看是在这里使用的 private val mTvTxNews: TextView by id(R.id.tv_tx_news) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_view_pager2_dispatch) mTvTxNews.text = "我已经初始化好了,可以使用了" }}//XML中(activity_view_pager2_dispatch.xml)<androidx.constraintlayout.widget.ConstraintLayout 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"> <TextView android:id="@+id/tv_tx_news" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"/></androidx.constraintlayout.widget.ConstraintLayout>可以看到通过private val mTvTxNews: TextView by id(R.id.tv_tx_news) 声明后,下面就可以正常使用该TextView了,其原理本质上就是通过 by lazy 延迟初始化了该属性,这种写法不需要每次去调用findViewById()了,跟我们使用ButterKnife 库的效果是一样的。同理,如果在Fragment中或其他场景中使用,继续写类似的扩展函数即可。 嗯,Kotlin 的语法糖真甜!可观察属性(observable properties)Delegates.observable()可以认为是一个属性监听器,当监听的属性变更时会收到通知。其接受两个参数:初始值initialValue与onChange()函数,当属性被赋值后就会触发onChange(),内部有三个参数:被赋值的属性、旧值与新值。public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) { override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue) }Delegates.observable() 可以帮助我们在属性值发生变化时自动执行一些操作,例如更新UI、保存数据、防抖动等。它适用于需要对属性值进行监听和处理的场景。使用案例这里以实现一个防抖动功能为例:class ExampleActivity : AppCompatActivity() { private var lastClickTime by Delegates.observable(0L) { _, old, new -> // 在lastClickTime属性值发生变化时执行点击事件 if (new - old > 1000) { onClick() } } override fun onClick(view: View) { //每次点击都会触发Delegates.observable() lastClickTime = System.currentTimeMillis() } private fun onClick() { // 点击事件代码 }}上述代码中,在 lastClickTime 属性值发生变化时,判断两次点击时间的间隔是否大于 1 秒:如果是,则执行点击事件;如果不是,什么都不做。如此通过 Delegates.observable 便实现了防抖动能力。注:在属性被赋新值生效之前想截获赋值,可以使用 vetoable() 取代 observable(),onChange()中会返回一个Boolean值来决定是否生效本次赋值。属性之间的委托从 Kotlin 1.4 开始,一个属性可以把它的 getter 与 setter 委托给另一个属性,被委托的属性属于顶层或普通类属性都可。为将一个属性委托给另一个属性,可以使用 :: 限定符,例如,this::delegate 或 MyClass::delegate。使用案例class DelegateProperty { @Deprecated("Use [newName] instead", ReplaceWith("newName")) var oldName by this::newName //this可以省略 var newName = ""}fun main() { val property = DelegateProperty() property.oldName = "2023" println("oldName: ${property.oldName}") println("newName: ${property.newName}")}//执行结果:oldName: 2023newName: 2023当想要以一种向后兼容的方式重命名一个属性的时候:引入一个新的属性、 使用 @Deprecated 注解来注解旧的属性、并委托其实现
0
0
0
浏览量781
IT大鲨鱼

JNI 编程上手指南之 HelloWorld 实战

JNI 编程上手指南之 HelloWorld 实战JNI 编程是高级/专家 Android 开发的必备技能之一,接下来我们就一步一步掌握 JNI 编程的方方面面。本文示例代码可以在 github.com/yuandaimaah… 这里下载到1. 基本概念JNI(Java Native Interface,JAVA 原生接口)。JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) 内部运行的 Java 代码能够与用其它编程语言(如 C、C++ 和汇编语言)编写的应用程序和库进行互操作。通俗一点讲就是在 Java 代码里调用 C/C++ 等语言的代码或 C/C++ 代码调用 Java 代码。JNI 技术在 Android 领域有大量的应用:Java 程序可以通过 JNI 操作硬件音视频处理,数学运算,实时渲染等领域相关的库基本都使用 C/C++ 编写,我们可以使用 JNI 技术来调用这些库,而不用使用 Java 来重写相比 C/C++,Java 更容易被反编译,一些和安全相关的代码,我们可以使用 C/C++ 来编写,然后使用 JNI 技术调用JNI 技术的应用当然不止以上列举的例子,更多的应用方式等待大家的进一步探索。2. HelloWorld 实战接下来我们通过一个简单的示例程序,快速地掌握 JNI 的基本使用。首先编译一个 Java 文件: HelloJNI.javapublic class HelloJNI { static { System.loadLibrary("hello"); } private native jstring sayHello(); public static void main(String[] args) { new HelloJNI().sayHello(); } }接着生成 C/C++ 头文件 HelloJNI.hjavac -h . HelloJNI.java该命令会生成一个 HelloJNI.h,这个头文件描述了我们需要实现的函数。/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class HelloJNI */ #ifndef _Included_HelloJNI #define _Included_HelloJNI #ifdef __cplusplus extern "C" { #endif /* * Class: HelloJNI * Method: sayHello * Signature: ()V */ JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif生成的函数中有两个参数:JNIEnv:JNIEnv 内部提供了很多函数,方便我们进行 JNI 编程。C 代码中,JNIEnv 是指向 JNINativeInterface 结构的指针,为了访问任何一个 JNI 函数,该指针需要首先被解引用。因为 C 代码中的 JNI 函数不了解当前的 JNI 环境, JNIEnv 实例应该作为第一个参数传递给每一个 JNI 函数调用调用者,调用格式如下:(*env)->NewStringUTF(env,"Hello from JNI !");在 C++ 代码中,JNIEnv 实际上是 C++ 类实例,JNI 函数以成员函数的形式存在,因此 JNI 函数调用不要求 JNIEnv 实例作参数。在 C++ 中,完成同样功能的调用代码格式如下:env->NewstringUTF ( "Hello from JNI ! ");jobject: 指向 "this" 的 Java 对象如果 java 中的 native 函数是 static 的,那第二个参数是 jclass,代表了 java 中的 Class 类。extern "C" 告诉 C++ 编译器以 C 的方式来编译这个函数,以方便其他 C 程序链接和访问该函数。C 和 C++ 有着不同的命名协议,因为 C++ 支持函数重载,用了不同的命名协议来处理重载的函数。在 C 中函数是通过函数名来识别的,而在 C++ 中,由于存在函数的重载问题,函数的识别方式通过函数名,函数的返回类型,函数参数列表三者组合来完成的。因此两个相同的函数,经过C,C++编绎后会产生完全不同的名字。所以,如果把一个用 C 编绎器编绎的目标代码和一个用 C++ 编绎器编绎的目标代码进行链接,就会出现链接失败的错误。JNIEXPORT、JNICALL 两个宏在 linux 平台的定义如下://该声明的作用是保证在本动态库中声明的方法 , 能够在其他项目中可以被调用 #define JNIEXPORT __attribute__ ((visibility ("default"))) //一个空定义 #define JNICALL接着我们来实现具体的 C 程序 HelloJNI.c#include "HelloJNI.h" #include <stdio.h> #include <jni.h> //方法名要和 Java 层包名对应上 JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) { return (*env)->NewStringUTF(env,"Hello from JNI !"); }编译和执行(需要配置好 JAVA_HOME 环境变量):gcc -fpic -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so HelloJNI.c java -Djava.library.path=. HelloJNI至此,一个简单的 demo 就完成了。3. 动态注册以上使用 JNI 的方式称为静态注册,还有一种方式叫动态注册,我们接下来看个动态注册的例子吧java层: com/example/ndk/NativeTest.javapackage com.example.ndk; public class NativeTest { static { System.loadLibrary("nativetest"); } public native void init(); public native void init(int age); public native boolean init(String name); public native void update(); }C 层的实现主要有三步:实现 java 层本地方法构建一个 JNINativeMethod 类型的数组注册本地函数NativeTest.c :#include <jni.h> #include <stdio.h> #ifdef __cplusplus extern "C" { #endif //1 实现 java 层本地方法 JNIEXPORT void JNICALL c_init1(JNIEnv *env, jobject thiz) { printf("c_init1\n"); } JNIEXPORT void JNICALL c_init2(JNIEnv *env, jobject thiz, jint age) { printf("c_init2\n"); } JNIEXPORT jboolean JNICALL c_init3(JNIEnv *env, jobject thiz, jstring name) { printf("c_init3\n"); } JNIEXPORT void JNICALL c_update(JNIEnv *env, jobject thiz) { printf("c_update\n"); } #ifdef __cplusplus } #endif // typedef struct { // //Java层native方法名称 // const char* name; // //方法签名 // const char* signature; // //native层方法指针 // void* fnPtr; // } JNINativeMethod; //2 构建 JNINativeMethod 数组 //中间的方法签名看上去有点怪异,后面我们来讲它的命名规则 static JNINativeMethod methods[] = { {"init", "()V", (void *)c_init1}, {"init", "(I)V", (void *)c_init2}, {"init", "(Ljava/lang/String;)Z", (void *)c_init3}, {"update", "()V", (void *)c_update}, }; /** * 3 完成动态注册的入口函数 * 其内容基本固定 */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env = NULL; jint result = -1; // 获取JNI env变量 if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { // 失败返回-1 return result; } // 获取native方法所在类 const char* className = "com/example/ndk/NativeTest"; jclass clazz = env->FindClass(className); if (clazz == NULL) { return result; } // 动态注册native方法 if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) { return result; } // 返回成功 result = JNI_VERSION_1_6; return result; } JNINativeMethod 第二个成员变量是方法签名,它的组成规则为:(参数类型标识1参数类型标识2...参数类型标识n)返回值类型标识其中的类型标识如下图所示:类型标识Java数据类型ZbooleanBbyteCcharSshortIintJlongFfloatDdoubleL包名/类名;各种引用类型Vvoid编译和执行:cd com/example/ndk javac NativeTest.java #回到项目根目录 cd - g++ -fpic -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libnativetest.so NativeTest.c java -Djava.library.path=. com.example.ndk.NativeTest
0
0
0
浏览量2009
IT大鲨鱼

Java线程基础知识点整理

进程和线程的区别· 进程是资源分配的最小单位,线程是cpu调度的最小单位。· 进程可以看做独立应用,而线程不能。一个程序会产生一个进程,而一个进程包含一个或多个线程。· 进程间是相互隔离的,线程可以共享进程内的资源对象的共享同步代码块及同步方法可以确保以原子形式执行操作,关键字synchronized不仅可以实现原子性、确定临界区;同时还可以保证内存可见性。线程安全性一个对象是否是线程安全的,取决于这个对象是否会被多个线程访问。并发编程中,线程安全问题归根结底其实就是 原子性、可见性、一致性、有序性问题。原子性一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行。可见性可见性指的是当多个线程存在时,某个线程对主内存的写入操作,其他线程都是立即可见的。为什么要保证可见性呢?如:非volatile类型的64位数值变量(double、long)在进行读操作或写操作时会分解为两个32位的操作。如果多线程去读写该类型变量,可能会读取到某个值的高32位和另一个值的低32位。有序性程序执行的顺序按照代码的先后顺序执行。对于单线程,在执行代码时jvm会进行指令重排序,目的是为了提高效率。指令重排可以对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证保存最终执行结果和代码顺序执行的结果是一致的。加锁机制多个线程访问同一个可变状态变量时,需要使用合适的同步,否则程序可能会出现错误。如果不想出现错误,可以通过下面的方式解决:· 不在线程之间共享该状态变量· 将状态变量修改为不可变的变量,即通过final修饰· 多线程访问该变量时使用同步原子性:原子操作指的是,对于访问同一个状态的所有操作都是一个以原子方式执行的操作。 竞态条件:当某个计算的正确性取决于多个线程的交替执行次序时,就会发生竞态条件。Java提供了一种内置的锁机制来支持原子性,以关键字synchronized来实现,内置锁可以作用在对象上或者方法上,称为对象锁;另外内置锁也可以直接作用在static静态的方法上,称为类锁。volatile相对于synchronized,volatile是一种稍弱的同步机制,volatile可以确保将变量的更新操作通知到其他线程,从而可以保证可见性。同时volatile还可以防止指令重排。加锁机制与volatile的异同:· 加锁机制既可以保证可见性又可以保证原子性,volatile只能保证可见性不能保证原子性。Thread中start()和run()方法的区别Runnable接口: public interface Runnable { public abstract void run(); } Thread类实现了Runnable接口: public class Thread implements Runnable { @Override public void run() {} } 调用start()方法会新起一个线程并启动新线程;如果直接执行run(),那么并没有新起线程,而是Thread被当成了一个普通类并运行了其中的run()方法Thread的状态Thread.State类:public static enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; private State() { } } · NEW 创建后尚未启动(还未调用start)的线程的状态· RUNNABLE 正在执行的线程(Running)或者正在等待cpu分配时间片的状态(Ready)· BLOCKED 等待获取排它锁(互斥锁)· WAITING 无限期等待,此状态下的Thread不会被分配时间片,需要显式被唤醒。下列方法都会导致Thread处于Waiting状态:1、Object.wait():如果一个线程中调用了object.wait(),那么这个线程就会处于waiting状态直到另一个线程调用了object.notify或者object.notifyAll才解除 2、线程A中调用线程B的join()那么线程A的状态从Runnable状态变成了Waiting状态,直到线程B执行完,线程A才会继续执行。 3、LockSupport.park()方法· TIMED_WAITING 和Waiting状态是类似,但是是限期等待,即在一定时间后由系统自动唤醒。下列方法都会让Thread重新唤醒 1、Thread.sleep(long TimeOut) 2、Object.wait(long TimeOut),当TimeOut时间过后,当前Thread会重新获取cpu时间片并变成运行状态 3、Thread.join(long TimeOut) 4、LockSupport.parkNanos() 5、LockSupport.parkUntil()· TERMINATED 已终止线程的状态,线程已经结束执行。Thread的几种状态之间的关系 sleep()和wait()的区别· 两个方法来自不同的类:sleep()来自Thread,wait()来自Object。· sleep()没有释放锁,会继续持有锁;wait()释放了锁,其他线程可以抢占该锁。· wait()、notify()和notifyAll()只能在Synchronized关键字作用域内使用;而sleep()可以在任何地方使用(使用范围)· sleep()必须捕获异常,而wait()、notify()和notifyAll()不需要捕获异常· sleep()是Thread类的静态方法。sleep()的作用是让线程休眠指定的时间,在时间到达时恢复,也就是说sleep()将在设置的Time到达后恢复线程执行;wait()是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait()将会将调用者的线程挂起,直到其他线程调用同一个对象的notify()方法才会重新激活调用者。notify()和notifyAll()的区别先介绍两个概念,锁(monitor)池和等待池,wait、notify、notifyAll都是Object中的方法。· 锁池: 假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。· 等待池: 假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.notify、notifyAll的区别: 更详细,请移步:https://blog.csdn.net/u014561933/article/details/58639411yield()· yield()是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。· yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。join()阻塞当前线程,等待子线程执行完毕之后继续执行当前线程。interrupt()线程一旦开始执行,就不能强制停止,否则会产生致命的问题,如:持有的锁不能被释放。所以Thread.stop()等方法已经被废弃了。那么如果优雅的让线程结束呢?那就是给正在执行的线程一个中断信号, 让它自己决定该怎么办。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断。· thread.interrupt():对于非阻塞中的线程, 只是改变了中断状态, 即thread.isInterrupted()将返回true; 对于可取消的阻塞状态中的线程, 如:Thread.sleep()、Object.wait()、Thread.join(), 这个线程收到中断信号后,会抛出InterruptedException, 同时会把中断状态置回为true。· Thread.interrupted():当调用interrupt()后,Thread.interrupted()返回true,并且会对中断状态进行复位(此时isInterrupted()将返回false)。使用示例:public void run(){ try{ .... while(!Thread.currentThread().isInterrupted()){ // do more work; } }catch(InterruptedException e){ // thread was interrupted during sleep or wait } finally{ // cleanup, if required } } Thread.currentThread().interrupt();//中断信号
0
0
0
浏览量2013
IT大鲨鱼

Android 基于Kotlin Flow实现一个倒计时功能

前情提要上一篇 Android Kotlin之Flow数据流 中介绍了协程Flow,我们知道Flow数据流可以按顺序发送多个值,一个倒计时功能刚好符合这种场景,本文就尝试使用Flow来实现一个倒计时功能。上文中举过一个简单示例: flow { log("send hello") emit("hello") //发送数据 log("send world") emit("world") //发送数据 }.flowOn(Dispatchers.IO) .onEmpty { log("onEmpty") } .onStart { log("onStart") } .onEach { log("onEach: $it") } .onCompletion { log("onCompletion") } .catch { exception -> exception.message?.let { log(it) } } .collect { //接收数据流 log("collect: $it") } 执行结果:2021-09-27 19:51:54.433 7240-7240/ E/TTT: onStart 2021-09-27 19:51:54.439 7240-7325/ E/TTT: send hello 2021-09-27 19:51:54.440 7240-7325/ E/TTT: send world 2021-09-27 19:51:54.451 7240-7240/ E/TTT: onEach: hello 2021-09-27 19:51:54.451 7240-7240/ E/TTT: collect:hello 2021-09-27 19:51:54.452 7240-7240/ E/TTT: onEach: world 2021-09-27 19:51:54.452 7240-7240/ E/TTT: collect:world 2021-09-27 19:51:54.453 7240-7240/ E/TTT: onCompletion onStart:上游flow{}开始发送数据之前执行onCompletion:flow数据流取消或者结束时执行onEach:上游向下游发送数据之前调用,每一个上游数据发送后都会经过onEach()使用上面几个操作符,将传入的数据在flow{}中每隔1s(通过delay(1000)实现)发射出去,下游对接收的数据进行处理,即是一个倒计时功能,如下: /** * 使用Flow实现一个倒计时功能 */ private fun countDownByFlow( max: Int, scope: CoroutineScope, onTick: (Int) -> Unit, onFinish: (() -> Unit)? = null, ): Job { return flow { for (num in max downTo 0) { emit(num) if (num != 0) delay(1000) } }.flowOn(Dispatchers.Main) .onEach { onTick.invoke(it) } .onCompletion { cause -> if (cause == null) onFinish?.invoke() } .launchIn(scope) //保证在一个协程中执行 } 实现倒计时功能先上效果图:主要代码逻辑:class CountDownCircleView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, ) : View(context, attrs, defStyleAttr) { private val mPaint: Paint = Paint().apply { isAntiAlias = true isDither = true style = Paint.Style.STROKE strokeCap = Paint.Cap.ROUND strokeWidth = 8.dp2px().toFloat() color = Color.parseColor("#F7F9FA") } private val mCirclePaint: Paint = Paint().apply { isAntiAlias = true isDither = true style = Paint.Style.FILL color = Color.WHITE } private val mTextPaint: TextPaint = TextPaint().apply { isAntiAlias = true isDither = true color = Color.parseColor("#A1EA42") textAlign = Paint.Align.CENTER //绘制方向 居中绘制 textSize = 50.sp2px().toFloat() typeface = Typeface.DEFAULT_BOLD } private val mRect = RectF() private var mCenterX: Float = 0f private var mCenterY: Float = 0f private var mRadius: Float = 0f private var mMinWH: Float = 0f private val colorArr = intArrayOf(Color.parseColor("#BAF900"), Color.parseColor("#84F000")) //设置渐变色 private val mLinearShader = LinearGradient(0f, 0f, mMinWH, mMinWH, colorArr, null, Shader.TileMode.MIRROR) private var mMaxCount: Int = 0 //最大数 private var mSweepAngle: Float = 360f //扫描过的度数 private var mCountDown: Job? = null private var mText = "" /** * 开始倒计数 */ fun startCountDown(count: Int, finishFuc: () -> Unit) { if (context !is FragmentActivity) return this.mMaxCount = count mCountDown = countDownByFlow(mMaxCount, (context as FragmentActivity).lifecycleScope, onTick = { if (it == 0) mCountDown?.cancel() mText = it.toString() mSweepAngle = (it / mMaxCount.toFloat()) * 360 invalidate() }, onFinish = { finishFuc.invoke() }) } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { mCenterX = (w / 2).toFloat() mCenterY = (h / 2).toFloat() mMinWH = min(w, h).toFloat() mRadius = (mMinWH - mPaint.strokeWidth) / 2 //设置矩形范围 val strokeHalf = mPaint.strokeWidth / 2 mRect.set(strokeHalf, strokeHalf, mMinWH - strokeHalf, mMinWH - strokeHalf) } override fun onDraw(canvas: Canvas?) { canvas?.let { mPaint.shader = null //绘制白色背景圆 canvas.drawCircle(mCenterX, mCenterY, mRadius, mCirclePaint) //绘制灰色背景圆 canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint) //绘制渐变色弧形 从12点方向开始绘制 mPaint.shader = mLinearShader canvas.drawArc(mRect, -90f, mSweepAngle, false, mPaint) //绘制中间倒计时数字 //如果设置的 Align = LEFT,那么baseX = (mMinWH - mTextPaint.measureText(mText)) / 2 val baseX = mCenterX // 计算Baseline绘制的Y坐标 ,计算方式:画布高度的一半 - 文字总高度的一半 val baseY = (mCenterY - (mTextPaint.descent() + mTextPaint.ascent()) / 2).toInt() // 居中画一个文字 canvas.drawText(mText, baseX, baseY.toFloat(), mTextPaint) } } /** * 使用Flow实现一个倒计时功能 */ private fun countDownByFlow( max: Int, scope: CoroutineScope, onTick: (Int) -> Unit, onFinish: (() -> Unit)? = null, ): Job { return flow { for (num in max downTo 0) { emit(num) if (num != 0) delay(1000) } }.flowOn(Dispatchers.Main) .onEach { onTick.invoke(it) } .onCompletion { cause -> if (cause == null) onFinish?.invoke() } .launchIn(scope) //保证在一个协程中执行 } override fun onDetachedFromWindow() { super.onDetachedFromWindow() mCountDown?.cancel() } } Activity中:mBtnStart.setOnClickListener { mCountDownView.startCountDown(10) { showToast("倒计时结束") } } 注意事项这里实现的倒计时功能是基于协程Flow实现的,所以必须保证项目里是支持Kotlin协程的才能使用;如果未使用协程Flow,将这里的倒计时逻辑改成CountDownTimer或者Timer来实现即可。
0
0
0
浏览量833
IT大鲨鱼

Android 基于Jetpack LiveData实现消息总线

前面的文章 Android Jetpack系列之LiveData 介绍了LiveData的基本用法,本文来介绍一下LiveData的一个进阶用法 — 基于LiveData实现消息总线消息总线在Android开发中,跨页面传递数据(尤其是跨多个页面传递数据)是一个很常见的操作,可以通过Handler、接口回调等方式进行传递,但这几种方式都不太优雅,消息总线传递数据的方式相比更优雅。消息总线最大的优势就是解耦,避免了类与类之间强耦合,通常消息总线有以下几种实现方式:EventBus:github.com/greenrobot/…RxBus : 基于RxJava实现的消息总线LiveDataBus:基于Jetpack中的LiveData实现,也是本文主要介绍的实现方式。EventBusEventBus整体思想如下:EventBus基于发布/订阅模式,发布者和订阅者是一对多的关系,发布者只有一个,订阅者可以有多个,他们之间都是通过EventBus这个调度中心来进行数据处理与传递。其中发布者将数据传递到调度中心,然后调度中心会找到该发布者对应的订阅者,并将数据依次传递到订阅者,从而完成了数据的传递;如果没有订阅者,那么也就不会传递数据了。整个过程发布者和订阅者不需要知道彼此的存在,即数据传递过程是解耦的。RxBusRxBus本身是依赖RxJava的强大功能实现的。RxJava中有一个Subject,是一种特殊的存在,它既是Observable,又是Observer,可以将其看做一个桥梁或代理。Subject有以下四种:AsyncSubject: 无论订阅发生在什么时候,Observer只会接收AsyncSubject发送的在onComplete()之前的最后一个数据,且onComplete()是必须要调用的。BehaviorSubject:Observer会先接收BehaviorSubject被订阅之前的最后一个事件,然后接收订阅之后发送的所有事件。PublishSubject: Observer只接收PublishSubject被订阅之后发送的事件。ReplaySubject:无论subscribe订阅是何时开始的,Observer会接收ReplaySubject发送的所有事件。具体使用方式可以参考:RxJava中关于Subject和Processor的使用可以通过Subject来实现一个消息总线,因为不是本文的重点介绍,就不再贴代码了,可以自行搜索其实现方式。LiveDataBusLiveDataBus是基于LiveData实现的,上篇文章中详细介绍了其用法及优点:确保界面符合数据状态 LiveData 遵循观察者模式。当数据发生变化时,LiveData 会通知 Observer 对象,那么Observer回调的方法中就可以进行UI更新,即数据驱动。不会发生内存泄漏 观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁(如Activity进入ONDESTROY状态)后进行自我清理。不会因 Activity 停止而导致崩溃 如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。不再需要手动处理生命周期 界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。数据始终保持最新状态 如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。配置更改时自动保存数据 如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。共享资源 使用单例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象。原理消息:发布者发送,订阅者接收。消息可以是基本类型,也可以是自定义类型的消息。消息通道:LiveData 扮演了消息通道的角色,不同的消息通道用不同的名字区分,名字是 String 类型的,可以通过名字获取到一个LiveData 消息通道。消息总线: 消息总线通过单例实现,不同的消息通道存放在一个 HashMap 中。订阅:订阅者通过 get() 获取消息通道,然后调用 observe() 订阅这个通道的消息。发布:发布者通过 get() 获取消息通道,然后调用 setValue()发布消息。图片来源:LiveData实现消息总线LiveData实现消息总线的优势相比于EventBus、RxBus,使用LiveData实现消息总线有下面几个优势:EventBus、RxBus、LiveDataBus都需要对事件进行注册、解注册。不同于EventBus、RxBus手动解注册,LiveData可以自动管理生命周期,所以也能实现自动解注册,避免忘记解注册而导致内存泄漏。LiveData实现简单,其为Jetpack中重要的一员,且为官方推出,支持更好LiveData相比于EventBus、RxBus,类更少,包更小。LiveData实现消息总线存在的隐患LiveData默认是粘性消息上一篇介绍LiveData的文章Android Jetpack系列之LiveData 中,我们也看到了LiveData发送的消息为粘性消息,即先发布后订阅也能收到消息,再把订阅observe()的逻辑贴出来:@MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) { assertMainThread("observe"); if (owner.getLifecycle().getCurrentState() == DESTROYED) { //如果当前观察者处于DESTROYED状态,直接返回 return; } //将LifecycleOwner、Observer包装成LifecycleBoundObserver LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); //ObserverWrapper是LifecycleBoundObserver的父类 ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); //如果mObservers中存在该Observer且跟传进来的LifecycleOwner不同,直接抛异常,一个Observer只能对应一个LifecycleOwner if (existing != null && !existing.isAttachedTo(owner)) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } //如果已经存在Observer且跟传进来的LifecycleOwner是同一个,直接返回 if (existing != null) { return; } //通过Lifecycle添加观察者 owner.getLifecycle().addObserver(wrapper); } 最后执行addObserver()后,内部通过LifecycleRegistry添加Observer,进而会执行到onStateChanged()方法,该方法辗转又调用到dispatchingValue方法(setValue/postValue最终也会调用到该方法),接着会调用到我们最关心的considerNotify(): void dispatchingValue(@Nullable ObserverWrapper initiator) { if (mDispatchingValue) { mDispatchInvalidated = true; return; } mDispatchingValue = true; do { mDispatchInvalidated = false; if (initiator != null) { //2、通过observe()的方式会调用这里 considerNotify(initiator); initiator = null; } else { //1、通过setValue/postValue的方式会调用这里,遍历所有观察者并进行分发 for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) { considerNotify(iterator.next().getValue()); if (mDispatchInvalidated) { break; } } } } while (mDispatchInvalidated); mDispatchingValue = false; } private void considerNotify(ObserverWrapper observer) { if (!observer.mActive) { //观察者不在活跃状态 直接返回 return; } //如果是observe(),则是在STARTED、RESUMED状态时活跃;如果是ObserveForever(),则认为一直是活跃状态 if (!observer.shouldBeActive()) { observer.activeStateChanged(false); return; } //Observer中的Version必须小于LiveData中的Version,防止重复发送 if (observer.mLastVersion >= mVersion) { return; } observer.mLastVersion = mVersion; //回调Observer的onChange方法并接收数据 observer.mObserver.onChanged((T) mData); } 可以看到considerNotify()里有这么一个逻辑:if (observer.mLastVersion >= mVersion) { return; } mVersion代表版本号,发送方、订阅方都有这个变量,默认是-1。发送方每发送一个消息,mVersion都会进行+1操作;而Observer中的mVersion每成功接收一次消息,都会将发送方最新的version赋值给自己的mLastVersion,当Observer中的mLastVersion>=发送方mVersion时,Observer会拒绝接收消息,防止重复发送消息。所以,如果当发送方之前的mVersion不是默认值-1,说明LiveData发送过消息。如果此时执行LiveData.observe(),因为Observer中的mLastVersion为默认值-1,小于发送方的mVersion,所以该消息不会被拦截,Observer一定可以拿到之前发送的消息,即粘性消息。LiveData.postValue可能会丢失消息当频繁使用LiveData.postValue发送多个消息时,LiveData.observe()接收消息时可能会发生丢失,为什么会这样呢?来看postValue()的内部实现//LiveData.java //postValue发送数据,可以在子线程中使用 protected void postValue(T value) { boolean postTask; synchronized (mDataLock) { //mPendingData默认值是NOT_SET,第一次发送时postTask是true postTask = mPendingData == NOT_SET; //将发送的值赋值给mPendingData mPendingData = value; } //第一次发送时postTask是true,当第一个消息还未处理时,后面再发送消息时postTask会变成false,所以后面的消息都会被拦截,但是发送的值可以更新到第一次发送里里面 if (!postTask) { return; } ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); } //setValue发送数据,只能在主线程中使用 protected void setValue(T value) { assertMainThread("setValue"); mVersion++; mData = value; dispatchingValue(null); } private final Runnable mPostValueRunnable = new Runnable() { @SuppressWarnings("unchecked") @Override public void run() { Object newValue; synchronized (mDataLock) { //将mPendingData中的值通过setValue传给Observer,并将自身格式化为NOT_SET newValue = mPendingData; mPendingData = NOT_SET; } setValue((T) newValue); } }; 详细的过程写在注释中了,主要的原因就是postValue发送消息时,会判断之前的消息是否已经处理,如果还未处理,会将当前发送的最新值更新到之前的消息中去(之前的消息存在mPendingData中,直接更新之),所以当多次频繁使用postValue发送消息时,Observer收到的为最后一次发送的最新值。个人猜测官方这么实现的目的主要是LiveData在MVVM架构中使用,既主要为了更新UI的最新数据即可,但是当用LiveData实现的消息总线时,可能就会出现丢失消息的隐患了,这是我们不想看到的,那么怎么解决呢?放弃使用postValue,都通过setValue去发送消息,如果是在子线程中发送消息,自行构建Handler发送到主线程中即可,后续贴代码。解决方案支持粘性、非粘性消息因为LiveData默认即是粘性消息,我们只需要添加非粘性消息支持即可,LiveData的mVersion默认是private的,如果想在其他类中使用,可以通过反射获取,但是效率相对低;还可以通过androidx.lifecycle包名来避免反射获取LiveData.mVersion,代码如下://package androidx.lifecycle open class ExternalLiveData<T> : MutableLiveData<T>() { companion object { //通过androidx.lifecycle包名来避免反射获取LiveData.START_VERSION const val START_VERSION = LiveData.START_VERSION } override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { if (owner.lifecycle.currentState == Lifecycle.State.DESTROYED) { // ignore return } try { val wrapper = ExternalLifecycleBoundObserver(owner, observer) val existing = callMethodPutIfAbsent(observer, wrapper) as? LiveData<*>.LifecycleBoundObserver require(!(existing != null && !existing.isAttachedTo(owner))) { ("Cannot add the same observer" + " with different lifecycles") } if (existing != null) return owner.lifecycle.addObserver(wrapper) } catch (e: Exception) { //ignore } } //继承父类并将修饰符改为public,可以对外暴露 public override fun getVersion(): Int { return super.getVersion() } internal inner class ExternalLifecycleBoundObserver( owner: LifecycleOwner, observer: Observer<in T>? ) : LifecycleBoundObserver(owner, observer) { override fun shouldBeActive(): Boolean { return mOwner.lifecycle.currentState.isAtLeast(observerActiveLevel()) } } /** * @return Lifecycle.State */ protected open fun observerActiveLevel(): Lifecycle.State { return Lifecycle.State.STARTED } //反射获取LiveData.mObservers private val fieldObservers: Any get() { val fieldObservers = LiveData::class.java.getDeclaredField("mObservers") fieldObservers.isAccessible = true return fieldObservers } /** * 反射调用LiveData的putIfAbsent方法 */ private fun callMethodPutIfAbsent(observer: Any, wrapper: Any): Any? { val mObservers = fieldObservers.javaClass val putIfAbsent = mObservers.getDeclaredMethod("putIfAbsent", Any::class.java, Any::class.java) putIfAbsent.isAccessible = true return putIfAbsent.invoke(mObservers, observer, wrapper) } } 这样外面就可以使用mVersion了,整体思路是通过装饰者模式对Observer进行控制,如:/** * Observer装饰者模式 */ class ObserverWrapper<T>( private val observer: Observer<T>, var preventNextEvent: Boolean = false ) : Observer<T> { override fun onChanged(t: T) { if (preventNextEvent) { preventNextEvent = false return } observer.onChanged(t) } } 非粘性消息:val observerWrapper = ObserverWrapper(observer) observerWrapper.preventNextEvent = liveData.version > ExternalLiveData.START_VERSION liveData.observe(owner, observerWrapper) liveData.version > ExternalLiveData.START_VERSION 说明liveData里发送过消息,version值已经不是初始值,如果是后注册的观察者,observerWrapper.preventNextEvent返回的是true,即会屏蔽当前消息,观察者不执行;如果是先注册的观察者,则不受影响,这样就是实现了非粘性消息。粘性消息:val observerWrapper = ObserverWrapper(observer) liveData.observe(owner, observerWrapper) 没什么可说的,默认就是粘性的,无需特殊处理。支持子线程发送消息判断是否在主线程:object ThreadUtils { /** * 是否是在主线程 */ fun isMainThread(): Boolean { return Looper.myLooper() == Looper.getMainLooper() } } 发送消息时判断当前所在线程:private val mainHandler = Handler(Looper.getMainLooper()) override fun post(value: T) { if (ThreadUtils.isMainThread()) { postInternal(value) } else { mainHandler.post(PostValueTask(value)) } } @MainThread private fun postInternal(value: T) { liveData.value = value } inner class PostValueTask(val newValue: T) : Runnable { override fun run() { postInternal(newValue) } } 当post消息时,先判断当前所在线程,主线程的话直接发送,在子线程的话通过MainHandler将消息发送到主线程再发送,从而支持了在子线程发送消息。
0
0
0
浏览量1964

履历