;
}
-keep class io.reactivex.schedulers.Schedulers {
public static ** test();
}
-keepclassmembers class io.reactivex.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class io.reactivex.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
long producerNode;
long consumerNode;
}
-keepclassmembers class io.reactivex.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
io.reactivex.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class io.reactivex.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
io.reactivex.internal.util.atomic.LinkedQueueNode consumerNode;
}
-dontwarn io.reactivex.internal.util.unsafe.**
################espresso###############
-keep class android.support.test.espresso.** { *; }
-keep interface android.support.test.espresso.** { *; }
################annotation###############
-keep class android.support.annotation.** { *; }
-keep interface android.support.annotation.** { *; }
################RxLifeCycle#################
-keep class com.trello.rxlifecycle2.** { *; }
-keep interface com.trello.rxlifecycle2.** { *; }
################RxPermissions#################
-keep class com.tbruyelle.rxpermissions2.** { *; }
-keep interface com.tbruyelle.rxpermissions2.** { *; }
################RxCache#################
-dontwarn io.rx_cache2.internal.**
-keep class io.rx_cache2.internal.Record { *; }
-keep class io.rx_cache2.Source { *; }
-keep class io.victoralbertos.jolyglot.** { *; }
-keep interface io.victoralbertos.jolyglot.** { *; }
################RxErrorHandler#################
-keep class me.jessyan.rxerrorhandler.** { *; }
-keep interface me.jessyan.rxerrorhandler.** { *; }
################Timber#################
-dontwarn org.jetbrains.annotations.**
################Canary#################
-dontwarn com.squareup.haha.guava.**
-dontwarn com.squareup.haha.perflib.**
-dontwarn com.squareup.haha.trove.**
-dontwarn com.squareup.leakcanary.**
-keep class com.squareup.haha.** { *; }
-keep class com.squareup.leakcanary.** { *; }
# Marshmallow removed Notification.setLatestEventInfo()
-dontwarn android.app.Notification
================================================
FILE: arms/src/main/AndroidManifest.xml
================================================
================================================
FILE: arms/src/main/java/com/jess/arms/base/AdapterViewPager.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import java.util.List;
/**
* ================================================
* 基类 {@link FragmentStatePagerAdapter}
*
* Created by JessYan on 22/03/2016
* Contact me
* Follow me
* ================================================
*/
public class AdapterViewPager extends FragmentStatePagerAdapter {
private List mList;
private CharSequence[] mTitles;
public AdapterViewPager(FragmentManager fragmentManager, List list) {
super(fragmentManager);
this.mList = list;
}
public AdapterViewPager(FragmentManager fragmentManager, List list, CharSequence[] titles) {
super(fragmentManager);
this.mList = list;
this.mTitles = titles;
}
@Override
public Fragment getItem(int position) {
return mList.get(position);
}
@Override
public CharSequence getPageTitle(int position) {
if (mTitles != null && position < mTitles.length) {
return mTitles[position];
}
return super.getPageTitle(position);
}
@Override
public int getCount() {
return mList.size();
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/App.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base;
import androidx.annotation.NonNull;
import com.jess.arms.di.component.AppComponent;
/**
* ================================================
* 框架要求框架中的每个 {@link android.app.Application} 都需要实现此类, 以满足规范
*
* @see BaseApplication
* @see 请配合官方 Wiki 文档学习本框架
* @see 更新日志, 升级必看!
* @see 常见 Issues, 踩坑必看!
* @see MVPArms 官方组件化方案 ArmsComponent, 进阶指南!
* Created by JessYan on 25/04/2017 14:54
* Contact me
* Follow me
* ================================================
*/
public interface App {
@NonNull
AppComponent getAppComponent();
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/BaseActivity.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.InflateException;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.jess.arms.base.delegate.IActivity;
import com.jess.arms.integration.cache.Cache;
import com.jess.arms.integration.cache.CacheType;
import com.jess.arms.integration.lifecycle.ActivityLifecycleable;
import com.jess.arms.mvp.IPresenter;
import com.jess.arms.utils.ArmsUtils;
import com.trello.rxlifecycle2.android.ActivityEvent;
import javax.inject.Inject;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import io.reactivex.subjects.BehaviorSubject;
import io.reactivex.subjects.Subject;
import static com.jess.arms.utils.ThirdViewUtil.convertAutoView;
/**
* ================================================
* 因为 Java 只能单继承, 所以如果要用到需要继承特定 {@link Activity} 的三方库, 那你就需要自己自定义 {@link Activity}
* 继承于这个特定的 {@link Activity}, 然后再按照 {@link BaseActivity} 的格式, 将代码复制过去, 记住一定要实现{@link IActivity}
*
* @see 请配合官方 Wiki 文档学习本框架
* @see 更新日志, 升级必看!
* @see 常见 Issues, 踩坑必看!
* @see MVPArms 官方组件化方案 ArmsComponent, 进阶指南!
* Created by JessYan on 22/03/2016
* Contact me
* Follow me
* ================================================
*/
public abstract class BaseActivity extends AppCompatActivity implements IActivity, ActivityLifecycleable {
protected final String TAG = this.getClass().getSimpleName();
private final BehaviorSubject mLifecycleSubject = BehaviorSubject.create();
@Inject
@Nullable
protected P mPresenter;//如果当前页面逻辑简单, Presenter 可以为 null
private Cache mCache;
private Unbinder mUnbinder;
@NonNull
@Override
public synchronized Cache provideCache() {
if (mCache == null) {
//noinspection unchecked
mCache = ArmsUtils.obtainAppComponentFromContext(this).cacheFactory().build(CacheType.ACTIVITY_CACHE);
}
return mCache;
}
@NonNull
@Override
public final Subject provideLifecycleSubject() {
return mLifecycleSubject;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
View view = convertAutoView(name, context, attrs);
return view == null ? super.onCreateView(name, context, attrs) : view;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
int layoutResID = initView(savedInstanceState);
//如果initView返回0,框架则不会调用setContentView(),当然也不会 Bind ButterKnife
if (layoutResID != 0) {
setContentView(layoutResID);
//绑定到butterknife
mUnbinder = ButterKnife.bind(this);
}
} catch (Exception e) {
if (e instanceof InflateException) {
throw e;
}
e.printStackTrace();
}
initData(savedInstanceState);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mUnbinder != null && mUnbinder != Unbinder.EMPTY) {
mUnbinder.unbind();
}
this.mUnbinder = null;
if (mPresenter != null) {
mPresenter.onDestroy();//释放资源
}
this.mPresenter = null;
}
/**
* 是否使用 EventBus
* Arms 核心库现在并不会依赖某个 EventBus, 要想使用 EventBus, 还请在项目中自行依赖对应的 EventBus
* 现在支持两种 EventBus, greenrobot 的 EventBus 和畅销书 《Android源码设计模式解析与实战》的作者 何红辉 所作的 AndroidEventBus
* 确保依赖后, 将此方法返回 true, Arms 会自动检测您依赖的 EventBus, 并自动注册
* 这种做法可以让使用者有自行选择三方库的权利, 并且还可以减轻 Arms 的体积
*
* @return 返回 {@code true} (默认为 {@code true}), Arms 会自动注册 EventBus
*/
@Override
public boolean useEventBus() {
return true;
}
/**
* 这个 {@link Activity} 是否会使用 {@link Fragment}, 框架会根据这个属性判断是否注册 {@link FragmentManager.FragmentLifecycleCallbacks}
* 如果返回 {@code false}, 那意味着这个 {@link Activity} 不需要绑定 {@link Fragment}, 那你再在这个 {@link Activity} 中绑定继承于 {@link BaseFragment} 的 {@link Fragment} 将不起任何作用
*
* @return 返回 {@code true} (默认为 {@code true}), 则需要使用 {@link Fragment}
*/
@Override
public boolean useFragment() {
return true;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/BaseApplication.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
import com.jess.arms.base.delegate.AppDelegate;
import com.jess.arms.base.delegate.AppLifecycles;
import com.jess.arms.di.component.AppComponent;
import com.jess.arms.utils.ArmsUtils;
import com.jess.arms.utils.Preconditions;
/**
* ================================================
* MVPArms 是一个整合了大量主流开源项目的 Android MVP 快速搭建框架, 其中包含 Dagger2、Retrofit、RxJava 以及
* RxLifecycle、RxCache 等 Rx 系三方库, 并且提供 UI 自适应方案, 本框架将它们结合起来, 并全部使用 Dagger2 管理
* 并提供给开发者使用, 使用本框架开发您的项目, 就意味着您已经拥有一个 MVP + Dagger2 + Retrofit + RxJava 项目
*
* @see 请配合官方 Wiki 文档学习本框架
* @see 更新日志, 升级必看!
* @see 常见 Issues, 踩坑必看!
* @see MVPArms 官方组件化方案 ArmsComponent, 进阶指南!
* Created by JessYan on 22/03/2016
* Contact me
* Follow me
* ================================================
*/
public class BaseApplication extends Application implements App {
private AppLifecycles mAppDelegate;
/**
* 这里会在 {@link BaseApplication#onCreate} 之前被调用,可以做一些较早的初始化
* 常用于 MultiDex 以及插件化框架的初始化
*
* @param base
*/
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
if (mAppDelegate == null) {
this.mAppDelegate = new AppDelegate(base);
}
this.mAppDelegate.attachBaseContext(base);
}
@Override
public void onCreate() {
super.onCreate();
if (mAppDelegate != null) {
this.mAppDelegate.onCreate(this);
}
}
/**
* 在模拟环境中程序终止时会被调用
*/
@Override
public void onTerminate() {
super.onTerminate();
if (mAppDelegate != null) {
this.mAppDelegate.onTerminate(this);
}
}
/**
* 将 {@link AppComponent} 返回出去, 供其它地方使用, {@link AppComponent} 接口中声明的方法所返回的实例, 在 {@link #getAppComponent()} 拿到对象后都可以直接使用
*
* @return AppComponent
* @see ArmsUtils#obtainAppComponentFromContext(Context) 可直接获取 {@link AppComponent}
*/
@NonNull
@Override
public AppComponent getAppComponent() {
Preconditions.checkNotNull(mAppDelegate, "%s cannot be null", AppDelegate.class.getName());
Preconditions.checkState(mAppDelegate instanceof App, "%s must be implements %s", mAppDelegate.getClass().getName(), App.class.getName());
return ((App) mAppDelegate).getAppComponent();
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/BaseFragment.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.jess.arms.base.delegate.IFragment;
import com.jess.arms.integration.cache.Cache;
import com.jess.arms.integration.cache.CacheType;
import com.jess.arms.integration.lifecycle.FragmentLifecycleable;
import com.jess.arms.mvp.IPresenter;
import com.jess.arms.utils.ArmsUtils;
import com.trello.rxlifecycle2.android.FragmentEvent;
import javax.inject.Inject;
import io.reactivex.subjects.BehaviorSubject;
import io.reactivex.subjects.Subject;
/**
* ================================================
* 因为 Java 只能单继承, 所以如果要用到需要继承特定 @{@link Fragment} 的三方库, 那你就需要自己自定义 @{@link Fragment}
* 继承于这个特定的 @{@link Fragment}, 然后再按照 {@link BaseFragment} 的格式, 将代码复制过去, 记住一定要实现{@link IFragment}
*
* @see 请配合官方 Wiki 文档学习本框架
* @see 更新日志, 升级必看!
* @see 常见 Issues, 踩坑必看!
* @see MVPArms 官方组件化方案 ArmsComponent, 进阶指南!
* Created by JessYan on 22/03/2016
* Contact me
* Follow me
* ================================================
*/
public abstract class BaseFragment extends Fragment implements IFragment, FragmentLifecycleable {
protected final String TAG = this.getClass().getSimpleName();
private final BehaviorSubject mLifecycleSubject = BehaviorSubject.create();
protected Context mContext;
@Inject
@Nullable
protected P mPresenter;//如果当前页面逻辑简单, Presenter 可以为 null
private Cache mCache;
@NonNull
@Override
public synchronized Cache provideCache() {
if (mCache == null) {
//noinspection unchecked
mCache = ArmsUtils.obtainAppComponentFromContext(getActivity()).cacheFactory().build(CacheType.FRAGMENT_CACHE);
}
return mCache;
}
@NonNull
@Override
public final Subject provideLifecycleSubject() {
return mLifecycleSubject;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return initView(inflater, container, savedInstanceState);
}
@Override
public void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.onDestroy();//释放资源
}
this.mPresenter = null;
}
@Override
public void onDetach() {
super.onDetach();
mContext = null;
}
/**
* 是否使用 EventBus
* Arms 核心库现在并不会依赖某个 EventBus, 要想使用 EventBus, 还请在项目中自行依赖对应的 EventBus
* 现在支持两种 EventBus, greenrobot 的 EventBus 和畅销书 《Android源码设计模式解析与实战》的作者 何红辉 所作的 AndroidEventBus
* 确保依赖后, 将此方法返回 true, Arms 会自动检测您依赖的 EventBus, 并自动注册
* 这种做法可以让使用者有自行选择三方库的权利, 并且还可以减轻 Arms 的体积
*
* @return 返回 {@code true} (默认为 {@code true}), Arms 会自动注册 EventBus
*/
@Override
public boolean useEventBus() {
return true;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/BaseHolder.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.jess.arms.utils.ThirdViewUtil;
import com.zhy.autolayout.utils.AutoUtils;
/**
* ================================================
* 基类 {@link RecyclerView.ViewHolder}
*
* Created by JessYan on 2015/11/24.
* Contact me
* Follow me
* ================================================
*/
public abstract class BaseHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
protected final String TAG = this.getClass().getSimpleName();
protected OnViewClickListener mOnViewClickListener = null;
public BaseHolder(View itemView) {
super(itemView);
//点击事件
itemView.setOnClickListener(this);
//屏幕适配
if (ThirdViewUtil.isUseAutolayout()) {
AutoUtils.autoSize(itemView);
}
//绑定 ButterKnife
ThirdViewUtil.bindTarget(this, itemView);
}
/**
* 设置数据
*
* @param data 数据
* @param position 在 RecyclerView 中的位置
*/
public abstract void setData(@NonNull T data, int position);
/**
* 在 Activity 的 onDestroy 中使用 {@link DefaultAdapter#releaseAllHolder(RecyclerView)} 方法 (super.onDestroy() 之前)
* {@link BaseHolder#onRelease()} 才会被调用, 可以在此方法中释放一些资源
*/
protected void onRelease() {
}
@Override
public void onClick(View view) {
if (mOnViewClickListener != null) {
mOnViewClickListener.onViewClick(view, this.getPosition());
}
}
public void setOnItemClickListener(OnViewClickListener listener) {
this.mOnViewClickListener = listener;
}
/**
* item 点击事件
*/
public interface OnViewClickListener {
/**
* item 被点击
*
* @param view 被点击的 {@link View}
* @param position 在 RecyclerView 中的位置
*/
void onViewClick(View view, int position);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/BaseLazyLoadFragment.java
================================================
package com.jess.arms.base;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.jess.arms.mvp.IPresenter;
import java.util.List;
import javax.inject.Inject;
/**
* 子类覆写{@link BaseLazyLoadFragment}lazyLoadData可快速实现Fragment懒加载
*/
public abstract class BaseLazyLoadFragment extends BaseFragment
{
@Inject
Unused mUnused;
private boolean isViewCreated; // 界面是否已创建完成
private boolean isVisibleToUser; // 是否对用户可见
private boolean isDataLoaded; // 数据是否已请求
/**
* 第一次可见时触发调用,此处实现具体的数据请求逻辑
*/
protected abstract void lazyLoadData();
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
tryLoadData();
}
/**
* 保证在initData后触发
*/
@Override
public void onResume() {
super.onResume();
isViewCreated = true;
tryLoadData();
}
/**
* ViewPager场景下,判断父fragment是否可见
*/
private boolean isParentVisible() {
Fragment fragment = getParentFragment();
return fragment == null || (fragment instanceof BaseLazyLoadFragment && ((BaseLazyLoadFragment) fragment).isVisibleToUser);
}
/**
* ViewPager场景下,当前fragment可见时,如果其子fragment也可见,则让子fragment请求数据
*/
private void dispatchParentVisibleState() {
FragmentManager fragmentManager = getChildFragmentManager();
List fragments = fragmentManager.getFragments();
if (fragments.isEmpty()) {
return;
}
for (Fragment child : fragments) {
if (child instanceof BaseLazyLoadFragment && ((BaseLazyLoadFragment) child).isVisibleToUser) {
((BaseLazyLoadFragment) child).tryLoadData();
}
}
}
public void tryLoadData() {
if (isViewCreated && isVisibleToUser && isParentVisible() && !isDataLoaded) {
lazyLoadData();
isDataLoaded = true;
//通知子Fragment请求数据
dispatchParentVisibleState();
}
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/BaseService.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.Nullable;
import com.jess.arms.integration.EventBusManager;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
/**
* ================================================
* 基类 {@link Service}
*
* Created by jess on 2016/5/6.
* Contact me
* Follow me
* ================================================
*/
public abstract class BaseService extends Service {
protected final String TAG = this.getClass().getSimpleName();
protected CompositeDisposable mCompositeDisposable;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
if (useEventBus()) {
EventBusManager.getInstance().register(this);
}
init();
}
@Override
public void onDestroy() {
super.onDestroy();
if (useEventBus()) {
EventBusManager.getInstance().unregister(this);
}
unDispose();//解除订阅
this.mCompositeDisposable = null;
}
/**
* 是否使用 EventBus
* Arms 核心库现在并不会依赖某个 EventBus, 要想使用 EventBus, 还请在项目中自行依赖对应的 EventBus
* 现在支持两种 EventBus, greenrobot 的 EventBus 和畅销书 《Android源码设计模式解析与实战》的作者 何红辉 所作的 AndroidEventBus
* 确保依赖后, 将此方法返回 true, Arms 会自动检测您依赖的 EventBus, 并自动注册
* 这种做法可以让使用者有自行选择三方库的权利, 并且还可以减轻 Arms 的体积
*
* @return 返回 {@code true} (默认为 {@code true}), Arms 会自动注册 EventBus
*/
public boolean useEventBus() {
return true;
}
protected void addDispose(Disposable disposable) {
if (mCompositeDisposable == null) {
mCompositeDisposable = new CompositeDisposable();
}
mCompositeDisposable.add(disposable);//将所有 Disposable 放入容器集中处理
}
protected void unDispose() {
if (mCompositeDisposable != null) {
mCompositeDisposable.clear();//保证 Activity 结束时取消所有正在执行的订阅
}
}
/**
* 初始化
*/
abstract public void init();
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/DefaultAdapter.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* ================================================
* 基类 {@link RecyclerView.Adapter}, 如果需要实现非常复杂的 {@link RecyclerView}, 请尽量使用其他优秀的三方库
*
* Created by jess on 2015/11/27.
* Contact me
* Follow me
* ================================================
*/
public abstract class DefaultAdapter extends RecyclerView.Adapter> {
protected List mInfos;
protected OnRecyclerViewItemClickListener mOnItemClickListener = null;
public DefaultAdapter(List infos) {
super();
this.mInfos = infos;
}
/**
* 遍历所有 {@link BaseHolder}, 释放他们需要释放的资源
*
* @param recyclerView {@link RecyclerView}
*/
public static void releaseAllHolder(RecyclerView recyclerView) {
if (recyclerView == null) {
return;
}
for (int i = recyclerView.getChildCount() - 1; i >= 0; i--) {
final View view = recyclerView.getChildAt(i);
RecyclerView.ViewHolder viewHolder = recyclerView.getChildViewHolder(view);
if (viewHolder instanceof BaseHolder) {
((BaseHolder) viewHolder).onRelease();
}
}
}
/**
* 创建 {@link BaseHolder}
*
* @param parent 父容器
* @param viewType 布局类型
* @return {@link BaseHolder}
*/
@NotNull
@Override
public BaseHolder onCreateViewHolder(ViewGroup parent, final int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(getLayoutId(viewType), parent, false);
BaseHolder mHolder = getHolder(view, viewType);
//设置Item点击事件
mHolder.setOnItemClickListener((view1, position) -> {
if (mOnItemClickListener != null && mInfos.size() > 0) {
//noinspection unchecked
mOnItemClickListener.onItemClick(view1, viewType, mInfos.get(position), position);
}
});
return mHolder;
}
/**
* 绑定数据
*
* @param holder {@link BaseHolder}
* @param position 在 RecyclerView 中的位置
*/
@Override
public void onBindViewHolder(BaseHolder holder, int position) {
holder.setData(mInfos.get(position), position);
}
/**
* 返回数据总个数
*
* @return 数据总个数
*/
@Override
public int getItemCount() {
return mInfos.size();
}
/**
* 返回数据集合
*
* @return 数据集合
*/
public List getInfos() {
return mInfos;
}
/**
* 获得 RecyclerView 中某个 position 上的 item 数据
*
* @param position 在 RecyclerView 中的位置
* @return 数据
*/
public T getItem(int position) {
return mInfos == null ? null : mInfos.get(position);
}
/**
* 让子类实现用以提供 {@link BaseHolder}
*
* @param v 用于展示的 {@link View}
* @param viewType 布局类型
* @return {@link BaseHolder}
*/
@NonNull
public abstract BaseHolder getHolder(@NonNull View v, int viewType);
/**
* 提供用于 item 布局的 {@code layoutId}
*
* @param viewType 布局类型
* @return 布局 id
*/
public abstract int getLayoutId(int viewType);
/**
* 设置 item 点击事件
*
* @param listener
*/
public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {
this.mOnItemClickListener = listener;
}
/**
* item 点击事件
*
* @param
*/
public interface OnRecyclerViewItemClickListener {
/**
* item 被点击
*
* @param view 被点击的 {@link View}
* @param viewType 布局类型
* @param data 数据
* @param position 在 RecyclerView 中的位置
*/
void onItemClick(@NonNull View view, int viewType, @NonNull T data, int position);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/Platform.java
================================================
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base;
/**
* ================================================
* Created by JessYan on 2018/7/27 15:32
* Contact me
* Follow me
* ================================================
*/
public class Platform {
public static final boolean DEPENDENCY_AUTO_LAYOUT;
public static final boolean DEPENDENCY_SUPPORT_DESIGN;
public static final boolean DEPENDENCY_GLIDE;
public static final boolean DEPENDENCY_ANDROID_EVENTBUS;
public static final boolean DEPENDENCY_EVENTBUS;
static {
DEPENDENCY_AUTO_LAYOUT = findClassByClassName("com.zhy.autolayout.AutoLayoutInfo");
DEPENDENCY_SUPPORT_DESIGN = findClassByClassName("com.google.android.material.snackbar.Snackbar");
DEPENDENCY_GLIDE = findClassByClassName("com.bumptech.glide.Glide");
DEPENDENCY_ANDROID_EVENTBUS = findClassByClassName("org.simple.eventbus.EventBus");
DEPENDENCY_EVENTBUS = findClassByClassName("org.greenrobot.eventbus.EventBus");
}
private static boolean findClassByClassName(String className) {
boolean hasDependency;
try {
Class.forName(className);
hasDependency = true;
} catch (ClassNotFoundException e) {
hasDependency = false;
}
return hasDependency;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/Unused.java
================================================
package com.jess.arms.base;
import javax.inject.Inject;
/**
* Created by yexiaokang on 2019/11/12.
*/
public class Unused {
@Inject
public Unused() {
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/delegate/ActivityDelegate.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base.delegate;
import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* ================================================
* {@link Activity} 代理类,用于框架内部在每个 {@link Activity} 的对应生命周期中插入需要的逻辑
*
* @see ActivityDelegateImpl
* @see ActivityDelegate wiki 官方文档
* Created by JessYan on 26/04/2017 20:23
* Contact me
* Follow me
* ================================================
*/
public interface ActivityDelegate {
String LAYOUT_LINEARLAYOUT = "LinearLayout";
String LAYOUT_FRAMELAYOUT = "FrameLayout";
String LAYOUT_RELATIVELAYOUT = "RelativeLayout";
String ACTIVITY_DELEGATE = "ACTIVITY_DELEGATE";
void onCreate(@Nullable Bundle savedInstanceState);
void onStart();
void onResume();
void onPause();
void onStop();
void onSaveInstanceState(@NonNull Bundle outState);
void onDestroy();
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/delegate/ActivityDelegateImpl.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base.delegate;
import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.jess.arms.integration.EventBusManager;
import com.jess.arms.utils.ArmsUtils;
/**
* ================================================
* {@link ActivityDelegate} 默认实现类
*
* Created by JessYan on 26/04/2017 20:23
* Contact me
* Follow me
* ================================================
*/
public class ActivityDelegateImpl implements ActivityDelegate {
private Activity mActivity;
private IActivity iActivity;
public ActivityDelegateImpl(@NonNull Activity activity) {
this.mActivity = activity;
this.iActivity = (IActivity) activity;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
//如果要使用 EventBus 请将此方法返回 true
if (iActivity.useEventBus()) {
//注册到事件主线
EventBusManager.getInstance().register(mActivity);
}
//这里提供 AppComponent 对象给 BaseActivity 的子类, 用于 Dagger2 的依赖注入
iActivity.setupActivityComponent(ArmsUtils.obtainAppComponentFromContext(mActivity));
}
@Override
public void onStart() {
}
@Override
public void onResume() {
}
@Override
public void onPause() {
}
@Override
public void onStop() {
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
}
@Override
public void onDestroy() {
//如果要使用 EventBus 请将此方法返回 true
if (iActivity != null && iActivity.useEventBus()) {
EventBusManager.getInstance().unregister(mActivity);
}
this.iActivity = null;
this.mActivity = null;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/delegate/AppDelegate.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base.delegate;
import android.app.Activity;
import android.app.Application;
import android.app.Service;
import android.content.ComponentCallbacks2;
import android.content.ContentProvider;
import android.content.Context;
import android.content.res.Configuration;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.jess.arms.base.App;
import com.jess.arms.base.BaseApplication;
import com.jess.arms.di.component.AppComponent;
import com.jess.arms.di.component.DaggerAppComponent;
import com.jess.arms.di.module.GlobalConfigModule;
import com.jess.arms.integration.ConfigModule;
import com.jess.arms.integration.ManifestParser;
import com.jess.arms.integration.cache.IntelligentCache;
import com.jess.arms.utils.ArmsUtils;
import com.jess.arms.utils.Preconditions;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
/**
* ================================================
* AppDelegate 可以代理 Application 的生命周期,在对应的生命周期,执行对应的逻辑,因为 Java 只能单继承
* 所以当遇到某些三方库需要继承于它的 Application 的时候,就只有自定义 Application 并继承于三方库的 Application
* 这时就不用再继承 BaseApplication,只用在自定义Application中对应的生命周期调用AppDelegate对应的方法
* (Application一定要实现APP接口),框架就能照常运行
*
* @see BaseApplication
* @see AppDelegate wiki 官方文档
* Created by JessYan on 24/04/2017 09:44
* Contact me
* Follow me
* ================================================
*/
public class AppDelegate implements App, AppLifecycles {
@Inject
@Named("ActivityLifecycle")
protected Application.ActivityLifecycleCallbacks mActivityLifecycle;
@Inject
@Named("ActivityLifecycleForRxLifecycle")
protected Application.ActivityLifecycleCallbacks mActivityLifecycleForRxLifecycle;
private Application mApplication;
private AppComponent mAppComponent;
private List mModules;
private List mAppLifecycles = new ArrayList<>();
private List mActivityLifecycles = new ArrayList<>();
private ComponentCallbacks2 mComponentCallback;
public AppDelegate(@NonNull Context context) {
//用反射, 将 AndroidManifest.xml 中带有 ConfigModule 标签的 class 转成对象集合(List)
this.mModules = new ManifestParser(context).parse();
//遍历之前获得的集合, 执行每一个 ConfigModule 实现类的某些方法
for (ConfigModule module : mModules) {
//将框架外部, 开发者实现的 Application 的生命周期回调 (AppLifecycles) 存入 mAppLifecycles 集合 (此时还未注册回调)
module.injectAppLifecycle(context, mAppLifecycles);
//将框架外部, 开发者实现的 Activity 的生命周期回调 (ActivityLifecycleCallbacks) 存入 mActivityLifecycles 集合 (此时还未注册回调)
module.injectActivityLifecycle(context, mActivityLifecycles);
}
}
@Override
public void attachBaseContext(@NonNull Context base) {
//遍历 mAppLifecycles, 执行所有已注册的 AppLifecycles 的 attachBaseContext() 方法 (框架外部, 开发者扩展的逻辑)
for (AppLifecycles lifecycle : mAppLifecycles) {
lifecycle.attachBaseContext(base);
}
}
@Override
public void onCreate(@NonNull Application application) {
this.mApplication = application;
mAppComponent = DaggerAppComponent
.builder()
.application(mApplication)//提供application
.globalConfigModule(getGlobalConfigModule(mApplication, mModules))//全局配置
.build();
mAppComponent.inject(this);
//将 ConfigModule 的实现类的集合存放到缓存 Cache, 可以随时获取
//使用 IntelligentCache.KEY_KEEP 作为 key 的前缀, 可以使储存的数据永久存储在内存中
//否则存储在 LRU 算法的存储空间中 (大于或等于缓存所能允许的最大 size, 则会根据 LRU 算法清除之前的条目)
//前提是 extras 使用的是 IntelligentCache (框架默认使用)
mAppComponent.extras().put(IntelligentCache.getKeyOfKeep(ConfigModule.class.getName()), mModules);
this.mModules = null;
//注册框架内部已实现的 Activity 生命周期逻辑
mApplication.registerActivityLifecycleCallbacks(mActivityLifecycle);
//注册框架内部已实现的 RxLifecycle 逻辑
mApplication.registerActivityLifecycleCallbacks(mActivityLifecycleForRxLifecycle);
//注册框架外部, 开发者扩展的 Activity 生命周期逻辑
//每个 ConfigModule 的实现类可以声明多个 Activity 的生命周期回调
//也可以有 N 个 ConfigModule 的实现类 (完美支持组件化项目 各个 Module 的各种独特需求)
for (Application.ActivityLifecycleCallbacks lifecycle : mActivityLifecycles) {
mApplication.registerActivityLifecycleCallbacks(lifecycle);
}
mComponentCallback = new AppComponentCallbacks(mApplication, mAppComponent);
//注册回掉: 内存紧张时释放部分内存
mApplication.registerComponentCallbacks(mComponentCallback);
//执行框架外部, 开发者扩展的 App onCreate 逻辑
for (AppLifecycles lifecycle : mAppLifecycles) {
lifecycle.onCreate(mApplication);
}
}
@Override
public void onTerminate(@NonNull Application application) {
if (mActivityLifecycle != null) {
mApplication.unregisterActivityLifecycleCallbacks(mActivityLifecycle);
}
if (mActivityLifecycleForRxLifecycle != null) {
mApplication.unregisterActivityLifecycleCallbacks(mActivityLifecycleForRxLifecycle);
}
if (mComponentCallback != null) {
mApplication.unregisterComponentCallbacks(mComponentCallback);
}
if (mActivityLifecycles != null && mActivityLifecycles.size() > 0) {
for (Application.ActivityLifecycleCallbacks lifecycle : mActivityLifecycles) {
mApplication.unregisterActivityLifecycleCallbacks(lifecycle);
}
}
if (mAppLifecycles != null && mAppLifecycles.size() > 0) {
for (AppLifecycles lifecycle : mAppLifecycles) {
lifecycle.onTerminate(mApplication);
}
}
this.mAppComponent = null;
this.mActivityLifecycle = null;
this.mActivityLifecycleForRxLifecycle = null;
this.mActivityLifecycles = null;
this.mComponentCallback = null;
this.mAppLifecycles = null;
this.mApplication = null;
}
/**
* 将app的全局配置信息封装进module(使用Dagger注入到需要配置信息的地方)
* 需要在AndroidManifest中声明{@link ConfigModule}的实现类,和Glide的配置方式相似
*
* @return GlobalConfigModule
*/
private GlobalConfigModule getGlobalConfigModule(Context context, List modules) {
GlobalConfigModule.Builder builder = GlobalConfigModule
.builder();
//遍历 ConfigModule 集合, 给全局配置 GlobalConfigModule 添加参数
for (ConfigModule module : modules) {
module.applyOptions(context, builder);
}
return builder.build();
}
/**
* 将 {@link AppComponent} 返回出去, 供其它地方使用, {@link AppComponent} 接口中声明的方法返回的实例, 在 {@link #getAppComponent()} 拿到对象后都可以直接使用
*
* @return AppComponent
* @see ArmsUtils#obtainAppComponentFromContext(Context) 可直接获取 {@link AppComponent}
*/
@NonNull
@Override
public AppComponent getAppComponent() {
Preconditions.checkNotNull(mAppComponent,
"%s == null, first call %s#onCreate(Application) in %s#onCreate()",
AppComponent.class.getName(), getClass().getName(), mApplication == null
? Application.class.getName() : mApplication.getClass().getName());
return mAppComponent;
}
/**
* {@link ComponentCallbacks2} 是一个细粒度的内存回收管理回调
* {@link Application}、{@link Activity}、{@link Service}、{@link ContentProvider}、{@link Fragment} 实现了 {@link ComponentCallbacks2} 接口
* 开发者应该实现 {@link ComponentCallbacks2#onTrimMemory(int)} 方法, 细粒度 release 内存, 参数的值不同可以体现出不同程度的内存可用情况
* 响应 {@link ComponentCallbacks2#onTrimMemory(int)} 回调, 开发者的 App 会存活的更持久, 有利于用户体验
* 不响应 {@link ComponentCallbacks2#onTrimMemory(int)} 回调, 系统 kill 掉进程的几率更大
*/
private static class AppComponentCallbacks implements ComponentCallbacks2 {
AppComponentCallbacks(Application application, AppComponent appComponent) {
}
/**
* 在你的 App 生命周期的任何阶段, {@link ComponentCallbacks2#onTrimMemory(int)} 发生的回调都预示着你设备的内存资源已经开始紧张
* 你应该根据 {@link ComponentCallbacks2#onTrimMemory(int)} 发生回调时的内存级别来进一步决定释放哪些资源
* {@link ComponentCallbacks2#onTrimMemory(int)} 的回调可以发生在 {@link Application}、{@link Activity}、{@link Service}、{@link ContentProvider}、{@link Fragment}
*
* @param level 内存级别
* @see level 官方文档
*/
@Override
public void onTrimMemory(int level) {
//状态1. 当开发者的 App 正在运行
//设备开始运行缓慢, 不会被 kill, 也不会被列为可杀死的, 但是设备此时正运行于低内存状态下, 系统开始触发杀死 LRU 列表中的进程的机制
// case TRIM_MEMORY_RUNNING_MODERATE:
//设备运行更缓慢了, 不会被 kill, 但请你回收 unused 资源, 以便提升系统的性能, 你应该释放不用的资源用来提升系统性能 (但是这也会直接影响到你的 App 的性能)
// case TRIM_MEMORY_RUNNING_LOW:
//设备运行特别慢, 当前 App 还不会被杀死, 但是系统已经把 LRU 列表中的大多数进程都已经杀死, 因此你应该立即释放所有非必须的资源
//如果系统不能回收到足够的 RAM 数量, 系统将会清除所有的 LRU 列表中的进程, 并且开始杀死那些之前被认为不应该杀死的进程, 例如那个包含了一个运行态 Service 的进程
// case TRIM_MEMORY_RUNNING_CRITICAL:
//状态2. 当前 App UI 不再可见, 这是一个回收大个资源的好时机
// case TRIM_MEMORY_UI_HIDDEN:
//状态3. 当前的 App 进程被置于 Background LRU 列表中
//进程位于 LRU 列表的上端, 尽管你的 App 进程并不是处于被杀掉的高危险状态, 但系统可能已经开始杀掉 LRU 列表中的其他进程了
//你应该释放那些容易恢复的资源, 以便于你的进程可以保留下来, 这样当用户回退到你的 App 的时候才能够迅速恢复
// case TRIM_MEMORY_BACKGROUND:
//系统正运行于低内存状态并且你的进程已经已经接近 LRU 列表的中部位置, 如果系统的内存开始变得更加紧张, 你的进程是有可能被杀死的
// case TRIM_MEMORY_MODERATE:
//系统正运行于低内存的状态并且你的进程正处于 LRU 列表中最容易被杀掉的位置, 你应该释放任何不影响你的 App 恢复状态的资源
//低于 API 14 的 App 可以使用 onLowMemory 回调
// case TRIM_MEMORY_COMPLETE:
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
}
/**
* 当系统开始清除 LRU 列表中的进程时, 尽管它会首先按照 LRU 的顺序来清除, 但是它同样会考虑进程的内存使用量, 因此消耗越少的进程则越容易被留下来
* {@link ComponentCallbacks2#onTrimMemory(int)} 的回调是在 API 14 才被加进来的, 对于老的版本, 你可以使用 {@link ComponentCallbacks2#onLowMemory} 方法来进行兼容
* {@link ComponentCallbacks2#onLowMemory} 相当于 {@code onTrimMemory(TRIM_MEMORY_COMPLETE)}
*
* @see #TRIM_MEMORY_COMPLETE
*/
@Override
public void onLowMemory() {
//系统正运行于低内存的状态并且你的进程正处于 LRU 列表中最容易被杀掉的位置, 你应该释放任何不影响你的 App 恢复状态的资源
}
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/delegate/AppLifecycles.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base.delegate;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
/**
* ================================================
* 用于代理 {@link Application} 的生命周期
*
* @see AppDelegate
* Created by JessYan on 18/07/2017 17:43
* Contact me
* Follow me
* ================================================
*/
public interface AppLifecycles {
void attachBaseContext(@NonNull Context base);
void onCreate(@NonNull Application application);
void onTerminate(@NonNull Application application);
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/delegate/FragmentDelegate.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base.delegate;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
/**
* ================================================
* {@link Fragment} 代理类,用于框架内部在每个 {@link Fragment} 的对应生命周期中插入需要的逻辑
*
* @see FragmentDelegateImpl
* @see FragmentDelegate wiki 官方文档
* Created by JessYan on 29/04/2017 14:30
* Contact me
* Follow me
* ================================================
*/
public interface FragmentDelegate {
String FRAGMENT_DELEGATE = "FRAGMENT_DELEGATE";
void onAttach(@NonNull Context context);
void onCreate(@Nullable Bundle savedInstanceState);
void onCreateView(@Nullable View view, @Nullable Bundle savedInstanceState);
void onActivityCreate(@Nullable Bundle savedInstanceState);
void onStart();
void onResume();
void onPause();
void onStop();
void onSaveInstanceState(@NonNull Bundle outState);
void onDestroyView();
void onDestroy();
void onDetach();
/**
* Return true if the fragment is currently added to its activity.
*/
boolean isAdded();
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/delegate/FragmentDelegateImpl.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base.delegate;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.jess.arms.integration.EventBusManager;
import com.jess.arms.utils.ArmsUtils;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import timber.log.Timber;
/**
* ================================================
* {@link FragmentDelegate} 默认实现类
*
* Created by JessYan on 29/04/2017 16:12
* Contact me
* Follow me
* ================================================
*/
public class FragmentDelegateImpl implements FragmentDelegate {
private FragmentManager mFragmentManager;
private Fragment mFragment;
private IFragment iFragment;
private Unbinder mUnbinder;
public FragmentDelegateImpl(@NonNull FragmentManager fragmentManager, @NonNull Fragment fragment) {
this.mFragmentManager = fragmentManager;
this.mFragment = fragment;
this.iFragment = (IFragment) fragment;
}
@Override
public void onAttach(@NonNull Context context) {
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
if (iFragment.useEventBus())//如果要使用eventbus请将此方法返回true
{
EventBusManager.getInstance().register(mFragment);//注册到事件主线
}
iFragment.setupFragmentComponent(ArmsUtils.obtainAppComponentFromContext(mFragment.getActivity()));
}
@Override
public void onCreateView(@Nullable View view, @Nullable Bundle savedInstanceState) {
//绑定到butterknife
if (view != null) {
mUnbinder = ButterKnife.bind(mFragment, view);
}
}
@Override
public void onActivityCreate(@Nullable Bundle savedInstanceState) {
iFragment.initData(savedInstanceState);
}
@Override
public void onStart() {
}
@Override
public void onResume() {
}
@Override
public void onPause() {
}
@Override
public void onStop() {
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
}
@Override
public void onDestroyView() {
if (mUnbinder != null && mUnbinder != Unbinder.EMPTY) {
try {
mUnbinder.unbind();
} catch (IllegalStateException e) {
e.printStackTrace();
//fix Bindings already cleared
Timber.w("onDestroyView: %s", e.getMessage());
}
}
}
@Override
public void onDestroy() {
if (iFragment != null && iFragment.useEventBus())//如果要使用eventbus请将此方法返回true
{
EventBusManager.getInstance().unregister(mFragment);//注册到事件主线
}
this.mUnbinder = null;
this.mFragmentManager = null;
this.mFragment = null;
this.iFragment = null;
}
@Override
public void onDetach() {
}
/**
* Return true if the fragment is currently added to its activity.
*/
@Override
public boolean isAdded() {
return mFragment != null && mFragment.isAdded();
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/delegate/IActivity.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base.delegate;
import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
import com.jess.arms.base.BaseActivity;
import com.jess.arms.base.BaseFragment;
import com.jess.arms.di.component.AppComponent;
import com.jess.arms.integration.ActivityLifecycle;
import com.jess.arms.integration.cache.Cache;
import com.jess.arms.integration.cache.LruCache;
/**
* ================================================
* 框架要求框架中的每个 {@link Activity} 都需要实现此类,以满足规范
*
* @see BaseActivity
* Created by JessYan on 26/04/2017 21:42
* Contact me
* Follow me
* ================================================
*/
public interface IActivity {
/**
* 提供在 {@link Activity} 生命周期内的缓存容器, 可向此 {@link Activity} 存取一些必要的数据
* 此缓存容器和 {@link Activity} 的生命周期绑定, 如果 {@link Activity} 在屏幕旋转或者配置更改的情况下
* 重新创建, 那此缓存容器中的数据也会被清空, 如果你想避免此种情况请使用 LifecycleModel
*
* @return like {@link LruCache}
*/
@NonNull
Cache provideCache();
/**
* 提供 AppComponent (提供所有的单例对象) 给实现类, 进行 Component 依赖
*
* @param appComponent
*/
void setupActivityComponent(@NonNull AppComponent appComponent);
/**
* 是否使用 EventBus
* Arms 核心库现在并不会依赖某个 EventBus, 要想使用 EventBus, 还请在项目中自行依赖对应的 EventBus
* 现在支持两种 EventBus, greenrobot 的 EventBus 和畅销书 《Android源码设计模式解析与实战》的作者 何红辉 所作的 AndroidEventBus
* 确保依赖后, 将此方法返回 true, Arms 会自动检测您依赖的 EventBus, 并自动注册
* 这种做法可以让使用者有自行选择三方库的权利, 并且还可以减轻 Arms 的体积
*
* @return 返回 {@code true}, Arms 会自动注册 EventBus
*/
boolean useEventBus();
/**
* 初始化 View, 如果 {@link #initView(Bundle)} 返回 0, 框架则不会调用 {@link Activity#setContentView(int)}
*
* @param savedInstanceState
* @return
*/
int initView(@Nullable Bundle savedInstanceState);
/**
* 初始化数据
*
* @param savedInstanceState
*/
void initData(@Nullable Bundle savedInstanceState);
/**
* 这个 Activity 是否会使用 Fragment,框架会根据这个属性判断是否注册 {@link FragmentManager.FragmentLifecycleCallbacks}
* 如果返回{@code false},那意味着这个 Activity 不需要绑定 Fragment,那你再在这个 Activity 中绑定继承于 {@link BaseFragment} 的 Fragment 将不起任何作用
*
* @return
* @see ActivityLifecycle#registerFragmentCallbacks (Fragment 的注册过程)
*/
boolean useFragment();
}
================================================
FILE: arms/src/main/java/com/jess/arms/base/delegate/IFragment.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.base.delegate;
import android.app.Activity;
import android.os.Bundle;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.jess.arms.base.BaseFragment;
import com.jess.arms.di.component.AppComponent;
import com.jess.arms.integration.cache.Cache;
import com.jess.arms.integration.cache.LruCache;
/**
* ================================================
* 框架要求框架中的每个 {@link Fragment} 都需要实现此类,以满足规范
*
* @see BaseFragment
* Created by JessYan on 29/04/2017 14:31
* Contact me
* Follow me
* ================================================
*/
public interface IFragment {
/**
* 提供在 {@link Fragment} 生命周期内的缓存容器, 可向此 {@link Fragment} 存取一些必要的数据
* 此缓存容器和 {@link Fragment} 的生命周期绑定, 如果 {@link Fragment} 在屏幕旋转或者配置更改的情况下
* 重新创建, 那此缓存容器中的数据也会被清空, 如果你想避免此种情况请使用 LifecycleModel
*
* @return like {@link LruCache}
*/
@NonNull
Cache provideCache();
/**
* 提供 AppComponent (提供所有的单例对象) 给实现类, 进行 Component 依赖
*
* @param appComponent
*/
void setupFragmentComponent(@NonNull AppComponent appComponent);
/**
* 是否使用 EventBus
* Arms 核心库现在并不会依赖某个 EventBus, 要想使用 EventBus, 还请在项目中自行依赖对应的 EventBus
* 现在支持两种 EventBus, greenrobot 的 EventBus 和畅销书 《Android源码设计模式解析与实战》的作者 何红辉 所作的 AndroidEventBus
* 确保依赖后, 将此方法返回 true, Arms 会自动检测您依赖的 EventBus, 并自动注册
* 这种做法可以让使用者有自行选择三方库的权利, 并且还可以减轻 Arms 的体积
*
* @return 返回 {@code true}, Arms 会自动注册 EventBus
*/
boolean useEventBus();
/**
* 初始化 View
*
* @param inflater
* @param container
* @param savedInstanceState
* @return
*/
View initView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState);
/**
* 初始化数据
*
* @param savedInstanceState
*/
void initData(@Nullable Bundle savedInstanceState);
/**
* 通过此方法可以使 Fragment 能够与外界做一些交互和通信, 比如说外部的 Activity 想让自己持有的某个 Fragment 对象执行一些方法,
* 建议在有多个需要与外界交互的方法时, 统一传 {@link Message}, 通过 what 字段来区分不同的方法, 在 {@link #setData(Object)}
* 方法中就可以 {@code switch} 做不同的操作, 这样就可以用统一的入口方法做多个不同的操作, 可以起到分发的作用
*
* 调用此方法时请注意调用时 Fragment 的生命周期, 如果调用 {@link #setData(Object)} 方法时 {@link Fragment#onCreate(Bundle)} 还没执行
* 但在 {@link #setData(Object)} 里却调用了 Presenter 的方法, 是会报空的, 因为 Dagger 注入是在 {@link Fragment#onCreate(Bundle)} 方法中执行的
* 然后才创建的 Presenter, 如果要做一些初始化操作,可以不必让外部调用 {@link #setData(Object)}, 在 {@link #initData(Bundle)} 中初始化就可以了
*
* Example usage:
*
* public void setData(@Nullable Object data) {
* if (data != null && data instanceof Message) {
* switch (((Message) data).what) {
* case 0:
* loadData(((Message) data).arg1);
* break;
* case 1:
* refreshUI();
* break;
* default:
* //do something
* break;
* }
* }
* }
*
* // call setData(Object):
* Message data = new Message();
* data.what = 0;
* data.arg1 = 1;
* fragment.setData(data);
*
*
* {@link #setData(Object)} 框架是不会调用的, 是拿给开发者自己去调用的, 让 {@link Activity} 或者其他类可以和 {@link Fragment} 通信,
* 并且因为 {@link #setData(Object)} 是 {@link IFragment} 的方法, 所以你可以通过多态, 持有父类,
* 不持有具体子类的方式就可以和子类 {@link Fragment} 通信, 这样如果需要替换子类, 就不会影响到其他地方,
* 并且 {@link #setData(Object)} 可以通过传入 {@link Message} 作为参数, 使外部统一调用 {@link #setData(Object)},
* 方法内部再通过 {@code switch(message.what)} 的方式, 从而在外部调用方式不变的情况下, 却可以扩展更多的方法,
* 让方法扩展更多的参数, 这样不管 {@link Fragment} 子类怎么变, 它内部的方法以及方法的参数怎么变, 却不会影响到外部调用的任何一行代码
*
* @param data 当不需要参数时 {@code data} 可以为 {@code null}
*/
void setData(@Nullable Object data);
}
================================================
FILE: arms/src/main/java/com/jess/arms/di/component/AppComponent.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.di.component;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import com.google.gson.Gson;
import com.jess.arms.base.delegate.AppDelegate;
import com.jess.arms.di.module.AppModule;
import com.jess.arms.di.module.ClientModule;
import com.jess.arms.di.module.GlobalConfigModule;
import com.jess.arms.http.imageloader.BaseImageLoaderStrategy;
import com.jess.arms.http.imageloader.ImageLoader;
import com.jess.arms.integration.AppManager;
import com.jess.arms.integration.ConfigModule;
import com.jess.arms.integration.IRepositoryManager;
import com.jess.arms.integration.cache.Cache;
import com.jess.arms.utils.ArmsUtils;
import java.io.File;
import java.util.concurrent.ExecutorService;
import javax.inject.Singleton;
import dagger.BindsInstance;
import dagger.Component;
import me.jessyan.rxerrorhandler.core.RxErrorHandler;
import okhttp3.OkHttpClient;
/**
* ================================================
* 可通过 {@link ArmsUtils#obtainAppComponentFromContext(Context)} 拿到此接口的实现类
* 拥有此接口的实现类即可调用对应的方法拿到 Dagger 提供的对应实例
*
* @see AppComponent wiki 官方文档
* Created by JessYan on 8/4/2016
* Contact me
* Follow me
* ================================================
*/
@Singleton
@Component(modules = {AppModule.class, ClientModule.class, GlobalConfigModule.class})
public interface AppComponent {
Application application();
/**
* 用于管理所有 {@link Activity}
* 之前 {@link AppManager} 使用 Dagger 保证单例, 只能使用 {@link AppComponent#appManager()} 访问
* 现在直接将 AppManager 独立为单例类, 可以直接通过静态方法 {@link AppManager#getAppManager()} 访问, 更加方便
* 但为了不影响之前使用 {@link AppComponent#appManager()} 获取 {@link AppManager} 的项目, 所以暂时保留这种访问方式
*
* @return {@link AppManager}
* @deprecated Use {@link AppManager#getAppManager()} instead
*/
@Deprecated
AppManager appManager();
/**
* 用于管理网络请求层, 以及数据缓存层
*
* @return {@link IRepositoryManager}
*/
IRepositoryManager repositoryManager();
/**
* RxJava 错误处理管理类
*
* @return {@link RxErrorHandler}
*/
RxErrorHandler rxErrorHandler();
/**
* 图片加载管理器, 用于加载图片的管理类, 使用策略者模式, 可在运行时动态替换任何图片加载框架
* arms-imageloader-glide 提供 Glide 的策略实现类, 也可以自行实现
* 需要在 {@link ConfigModule#applyOptions(Context, GlobalConfigModule.Builder)} 中
* 手动注册 {@link BaseImageLoaderStrategy}, {@link ImageLoader} 才能正常使用
*
* @return
*/
ImageLoader imageLoader();
/**
* 网络请求框架
*
* @return {@link OkHttpClient}
*/
OkHttpClient okHttpClient();
/**
* Json 序列化库
*
* @return {@link Gson}
*/
Gson gson();
/**
* 缓存文件根目录 (RxCache 和 Glide 的缓存都已经作为子文件夹放在这个根目录下), 应该将所有缓存都统一放到这个根目录下
* 便于管理和清理, 可在 {@link ConfigModule#applyOptions(Context, GlobalConfigModule.Builder)} 种配置
*
* @return {@link File}
*/
File cacheFile();
/**
* 用来存取一些整个 App 公用的数据, 切勿大量存放大容量数据, 这里的存放的数据和 {@link Application} 的生命周期一致
*
* @return {@link Cache}
*/
Cache extras();
/**
* 用于创建框架所需缓存对象的工厂
*
* @return {@link Cache.Factory}
*/
Cache.Factory cacheFactory();
/**
* 返回一个全局公用的线程池,适用于大多数异步需求。
* 避免多个线程池创建带来的资源消耗。
*
* @return {@link ExecutorService}
*/
ExecutorService executorService();
void inject(AppDelegate delegate);
@Component.Builder
interface Builder {
@BindsInstance
Builder application(Application application);
Builder globalConfigModule(GlobalConfigModule globalConfigModule);
AppComponent build();
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/di/module/AppModule.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.di.module;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.jess.arms.di.component.AppComponent;
import com.jess.arms.integration.ActivityLifecycle;
import com.jess.arms.integration.AppManager;
import com.jess.arms.integration.FragmentLifecycle;
import com.jess.arms.integration.IRepositoryManager;
import com.jess.arms.integration.RepositoryManager;
import com.jess.arms.integration.cache.Cache;
import com.jess.arms.integration.cache.CacheType;
import com.jess.arms.integration.lifecycle.ActivityLifecycleForRxLifecycle;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
/**
* ================================================
* 提供一些框架必须的实例的 {@link Module}
*
* Created by JessYan on 8/4/2016.
* Contact me
* Follow me
* ================================================
*/
@Module
public abstract class AppModule {
@Singleton
@Provides
static Gson provideGson(Application application, @Nullable GsonConfiguration configuration) {
GsonBuilder builder = new GsonBuilder();
if (configuration != null) {
configuration.configGson(application, builder);
}
return builder.create();
}
/**
* 之前 {@link AppManager} 使用 Dagger 保证单例, 只能使用 {@link AppComponent#appManager()} 访问
* 现在直接将 AppManager 独立为单例类, 可以直接通过静态方法 {@link AppManager#getAppManager()} 访问, 更加方便
* 但为了不影响之前使用 {@link AppComponent#appManager()} 获取 {@link AppManager} 的项目, 所以暂时保留这种访问方式
*
* @param application
* @return
*/
@Singleton
@Provides
static AppManager provideAppManager(Application application) {
return AppManager.getAppManager().init(application);
}
@Singleton
@Provides
static Cache provideExtras(Cache.Factory cacheFactory) {
//noinspection unchecked
return cacheFactory.build(CacheType.EXTRAS);
}
@Singleton
@Provides
static List provideFragmentLifecycles() {
return new ArrayList<>();
}
@Binds
abstract IRepositoryManager bindRepositoryManager(RepositoryManager repositoryManager);
@Binds
@Named("ActivityLifecycle")
abstract Application.ActivityLifecycleCallbacks bindActivityLifecycle(ActivityLifecycle activityLifecycle);
@Binds
@Named("ActivityLifecycleForRxLifecycle")
abstract Application.ActivityLifecycleCallbacks bindActivityLifecycleForRxLifecycle(ActivityLifecycleForRxLifecycle activityLifecycleForRxLifecycle);
@Binds
abstract FragmentManager.FragmentLifecycleCallbacks bindFragmentLifecycle(FragmentLifecycle fragmentLifecycle);
public interface GsonConfiguration {
void configGson(@NonNull Context context, @NonNull GsonBuilder builder);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/di/module/ClientModule.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.di.module;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.Gson;
import com.jess.arms.http.GlobalHttpHandler;
import com.jess.arms.http.log.RequestInterceptor;
import com.jess.arms.utils.DataHelper;
import java.io.File;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import io.rx_cache2.internal.RxCache;
import io.victoralbertos.jolyglot.GsonSpeaker;
import me.jessyan.rxerrorhandler.core.RxErrorHandler;
import me.jessyan.rxerrorhandler.handler.listener.ResponseErrorListener;
import okhttp3.Dispatcher;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* ================================================
* 提供一些三方库客户端实例的 {@link Module}
*
* Created by JessYan on 2016/3/14.
* Contact me
* Follow me
* ================================================
*/
@Module
public abstract class ClientModule {
private static final int TIME_OUT = 10;
/**
* 提供 {@link Retrofit}
*
* @param application {@link Application}
* @param configuration {@link RetrofitConfiguration}
* @param builder {@link Retrofit.Builder}
* @param client {@link OkHttpClient}
* @param httpUrl {@link HttpUrl}
* @param gson {@link Gson}
* @return {@link Retrofit}
*/
@Singleton
@Provides
static Retrofit provideRetrofit(Application application, @Nullable RetrofitConfiguration configuration, Retrofit.Builder builder, OkHttpClient client
, HttpUrl httpUrl, Gson gson) {
builder
.baseUrl(httpUrl)//域名
.client(client);//设置 OkHttp
if (configuration != null) {
configuration.configRetrofit(application, builder);
}
builder
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//使用 RxJava
.addConverterFactory(GsonConverterFactory.create(gson));//使用 Gson
return builder.build();
}
/**
* 提供 {@link OkHttpClient}
*
* @param application {@link Application}
* @param configuration {@link OkhttpConfiguration}
* @param builder {@link OkHttpClient.Builder}
* @param intercept {@link Interceptor}
* @param interceptors {@link List}
* @param handler {@link GlobalHttpHandler}
* @param executorService {@link ExecutorService}
* @return {@link OkHttpClient}
*/
@Singleton
@Provides
static OkHttpClient provideClient(Application application, @Nullable OkhttpConfiguration configuration, OkHttpClient.Builder builder, Interceptor intercept
, @Nullable List interceptors, @Nullable GlobalHttpHandler handler, ExecutorService executorService) {
builder
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.addNetworkInterceptor(intercept);
if (handler != null) {
builder.addInterceptor(chain -> chain.proceed(handler.onHttpRequestBefore(chain, chain.request())));
}
//如果外部提供了 Interceptor 的集合则遍历添加
if (interceptors != null) {
for (Interceptor interceptor : interceptors) {
builder.addInterceptor(interceptor);
}
}
//为 OkHttp 设置默认的线程池
builder.dispatcher(new Dispatcher(executorService));
if (configuration != null) {
configuration.configOkhttp(application, builder);
}
return builder.build();
}
@Singleton
@Provides
static Retrofit.Builder provideRetrofitBuilder() {
return new Retrofit.Builder();
}
@Singleton
@Provides
static OkHttpClient.Builder provideClientBuilder() {
return new OkHttpClient.Builder();
}
/**
* 提供 {@link RxCache}
*
* @param application {@link Application}
* @param configuration {@link RxCacheConfiguration}
* @param cacheDirectory RxCache 缓存路径
* @param gson {@link Gson}
* @return {@link RxCache}
*/
@Singleton
@Provides
static RxCache provideRxCache(Application application, @Nullable RxCacheConfiguration configuration
, @Named("RxCacheDirectory") File cacheDirectory, Gson gson) {
RxCache.Builder builder = new RxCache.Builder();
RxCache rxCache = null;
if (configuration != null) {
rxCache = configuration.configRxCache(application, builder);
}
if (rxCache != null) {
return rxCache;
}
return builder
.persistence(cacheDirectory, new GsonSpeaker(gson));
}
/**
* 需要单独给 {@link RxCache} 提供子缓存文件
*
* @param cacheDir 框架缓存文件
* @return {@link File}
*/
@Singleton
@Provides
@Named("RxCacheDirectory")
static File provideRxCacheDirectory(File cacheDir) {
File cacheDirectory = new File(cacheDir, "RxCache");
return DataHelper.makeDirs(cacheDirectory);
}
/**
* 提供处理 RxJava 错误的管理器
*
* @param application {@link Application}
* @param listener {@link ResponseErrorListener}
* @return {@link RxErrorHandler}
*/
@Singleton
@Provides
static RxErrorHandler proRxErrorHandler(Application application, ResponseErrorListener listener) {
return RxErrorHandler
.builder()
.with(application)
.responseErrorListener(listener)
.build();
}
@Binds
abstract Interceptor bindInterceptor(RequestInterceptor interceptor);
/**
* {@link Retrofit} 自定义配置接口
*/
public interface RetrofitConfiguration {
void configRetrofit(@NonNull Context context, @NonNull Retrofit.Builder builder);
}
/**
* {@link OkHttpClient} 自定义配置接口
*/
public interface OkhttpConfiguration {
void configOkhttp(@NonNull Context context, @NonNull OkHttpClient.Builder builder);
}
/**
* {@link RxCache} 自定义配置接口
*/
public interface RxCacheConfiguration {
/**
* 若想自定义 RxCache 的缓存文件夹或者解析方式, 如改成 FastJson
* 请 {@code return rxCacheBuilder.persistence(cacheDirectory, new FastJsonSpeaker());}, 否则请 {@code return null;}
*
* @param context {@link Context}
* @param builder {@link RxCache.Builder}
* @return {@link RxCache}
*/
RxCache configRxCache(@NonNull Context context, @NonNull RxCache.Builder builder);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/di/module/GlobalConfigModule.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.di.module;
import android.app.Application;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.jess.arms.http.BaseUrl;
import com.jess.arms.http.GlobalHttpHandler;
import com.jess.arms.http.imageloader.BaseImageLoaderStrategy;
import com.jess.arms.http.log.DefaultFormatPrinter;
import com.jess.arms.http.log.FormatPrinter;
import com.jess.arms.http.log.RequestInterceptor;
import com.jess.arms.integration.IRepositoryManager;
import com.jess.arms.integration.cache.Cache;
import com.jess.arms.integration.cache.CacheType;
import com.jess.arms.integration.cache.IntelligentCache;
import com.jess.arms.integration.cache.LruCache;
import com.jess.arms.utils.DataHelper;
import com.jess.arms.utils.Preconditions;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import me.jessyan.rxerrorhandler.handler.listener.ResponseErrorListener;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.internal.Util;
/**
* ================================================
* 框架独创的建造者模式 {@link Module},可向框架中注入外部配置的自定义参数
*
* @see GlobalConfigModule Wiki 官方文档
* Created by JessYan on 2016/3/14.
* Contact me
* Follow me
* ================================================
*/
@Module
public class GlobalConfigModule {
private HttpUrl mApiUrl;
private BaseUrl mBaseUrl;
private BaseImageLoaderStrategy mLoaderStrategy;
private GlobalHttpHandler mHandler;
private List mInterceptors;
private ResponseErrorListener mErrorListener;
private File mCacheFile;
private ClientModule.RetrofitConfiguration mRetrofitConfiguration;
private ClientModule.OkhttpConfiguration mOkhttpConfiguration;
private ClientModule.RxCacheConfiguration mRxCacheConfiguration;
private AppModule.GsonConfiguration mGsonConfiguration;
private RequestInterceptor.Level mPrintHttpLogLevel;
private FormatPrinter mFormatPrinter;
private Cache.Factory mCacheFactory;
private ExecutorService mExecutorService;
private IRepositoryManager.ObtainServiceDelegate mObtainServiceDelegate;
private GlobalConfigModule(Builder builder) {
this.mApiUrl = builder.apiUrl;
this.mBaseUrl = builder.baseUrl;
this.mLoaderStrategy = builder.loaderStrategy;
this.mHandler = builder.handler;
this.mInterceptors = builder.interceptors;
this.mErrorListener = builder.responseErrorListener;
this.mCacheFile = builder.cacheFile;
this.mRetrofitConfiguration = builder.retrofitConfiguration;
this.mOkhttpConfiguration = builder.okhttpConfiguration;
this.mRxCacheConfiguration = builder.rxCacheConfiguration;
this.mGsonConfiguration = builder.gsonConfiguration;
this.mPrintHttpLogLevel = builder.printHttpLogLevel;
this.mFormatPrinter = builder.formatPrinter;
this.mCacheFactory = builder.cacheFactory;
this.mExecutorService = builder.executorService;
this.mObtainServiceDelegate = builder.obtainServiceDelegate;
}
public static Builder builder() {
return new Builder();
}
@Singleton
@Provides
@Nullable
List provideInterceptors() {
return mInterceptors;
}
/**
* 提供 BaseUrl,默认使用 <"https://api.github.com/">
*
* @return
*/
@Singleton
@Provides
HttpUrl provideBaseUrl() {
if (mBaseUrl != null) {
HttpUrl httpUrl = mBaseUrl.url();
if (httpUrl != null) {
return httpUrl;
}
}
return mApiUrl == null ? HttpUrl.parse("https://api.github.com/") : mApiUrl;
}
/**
* 提供图片加载框架,默认使用 {@link Glide}
*
* @return
*/
@Singleton
@Provides
@Nullable
BaseImageLoaderStrategy provideImageLoaderStrategy() {
return mLoaderStrategy;
}
/**
* 提供处理 Http 请求和响应结果的处理类
*
* @return
*/
@Singleton
@Provides
@Nullable
GlobalHttpHandler provideGlobalHttpHandler() {
return mHandler;
}
/**
* 提供缓存文件
*/
@Singleton
@Provides
File provideCacheFile(Application application) {
return mCacheFile == null ? DataHelper.getCacheFile(application) : mCacheFile;
}
/**
* 提供处理 RxJava 错误的管理器的回调
*
* @return
*/
@Singleton
@Provides
ResponseErrorListener provideResponseErrorListener() {
return mErrorListener == null ? ResponseErrorListener.EMPTY : mErrorListener;
}
@Singleton
@Provides
@Nullable
ClientModule.RetrofitConfiguration provideRetrofitConfiguration() {
return mRetrofitConfiguration;
}
@Singleton
@Provides
@Nullable
ClientModule.OkhttpConfiguration provideOkhttpConfiguration() {
return mOkhttpConfiguration;
}
@Singleton
@Provides
@Nullable
ClientModule.RxCacheConfiguration provideRxCacheConfiguration() {
return mRxCacheConfiguration;
}
@Singleton
@Provides
@Nullable
AppModule.GsonConfiguration provideGsonConfiguration() {
return mGsonConfiguration;
}
@Singleton
@Provides
RequestInterceptor.Level providePrintHttpLogLevel() {
return mPrintHttpLogLevel == null ? RequestInterceptor.Level.ALL : mPrintHttpLogLevel;
}
@Singleton
@Provides
FormatPrinter provideFormatPrinter() {
return mFormatPrinter == null ? new DefaultFormatPrinter() : mFormatPrinter;
}
@Singleton
@Provides
Cache.Factory provideCacheFactory(Application application) {
return mCacheFactory == null ? type -> {
//若想自定义 LruCache 的 size, 或者不想使用 LruCache, 想使用自己自定义的策略
//使用 GlobalConfigModule.Builder#cacheFactory() 即可扩展
switch (type.getCacheTypeId()) {
//Activity、Fragment 以及 Extras 使用 IntelligentCache (具有 LruCache 和 可永久存储数据的 Map)
case CacheType.EXTRAS_TYPE_ID:
case CacheType.ACTIVITY_CACHE_TYPE_ID:
case CacheType.FRAGMENT_CACHE_TYPE_ID:
return new IntelligentCache(type.calculateCacheSize(application));
//其余使用 LruCache (当达到最大容量时可根据 LRU 算法抛弃不合规数据)
default:
return new LruCache(type.calculateCacheSize(application));
}
} : mCacheFactory;
}
/**
* 返回一个全局公用的线程池,适用于大多数异步需求。
* 避免多个线程池创建带来的资源消耗。
*
* @return {@link Executor}
*/
@Singleton
@Provides
ExecutorService provideExecutorService() {
return mExecutorService == null ? new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("Arms Executor", false)) : mExecutorService;
}
@Singleton
@Provides
@Nullable
IRepositoryManager.ObtainServiceDelegate provideObtainServiceDelegate() {
return mObtainServiceDelegate;
}
public static final class Builder {
private HttpUrl apiUrl;
private BaseUrl baseUrl;
private BaseImageLoaderStrategy loaderStrategy;
private GlobalHttpHandler handler;
private List interceptors;
private ResponseErrorListener responseErrorListener;
private File cacheFile;
private ClientModule.RetrofitConfiguration retrofitConfiguration;
private ClientModule.OkhttpConfiguration okhttpConfiguration;
private ClientModule.RxCacheConfiguration rxCacheConfiguration;
private AppModule.GsonConfiguration gsonConfiguration;
private RequestInterceptor.Level printHttpLogLevel;
private FormatPrinter formatPrinter;
private Cache.Factory cacheFactory;
private ExecutorService executorService;
private IRepositoryManager.ObtainServiceDelegate obtainServiceDelegate;
private Builder() {
}
public Builder baseurl(String baseUrl) {//基础url
if (TextUtils.isEmpty(baseUrl)) {
throw new NullPointerException("BaseUrl can not be empty");
}
this.apiUrl = HttpUrl.parse(baseUrl);
return this;
}
public Builder baseurl(BaseUrl baseUrl) {
this.baseUrl = Preconditions.checkNotNull(baseUrl, BaseUrl.class.getCanonicalName() + "can not be null.");
return this;
}
public Builder imageLoaderStrategy(BaseImageLoaderStrategy loaderStrategy) {//用来请求网络图片
this.loaderStrategy = loaderStrategy;
return this;
}
public Builder globalHttpHandler(GlobalHttpHandler handler) {//用来处理http响应结果
this.handler = handler;
return this;
}
public Builder addInterceptor(Interceptor interceptor) {//动态添加任意个interceptor
if (interceptors == null) {
interceptors = new ArrayList<>();
}
this.interceptors.add(interceptor);
return this;
}
public Builder responseErrorListener(ResponseErrorListener listener) {//处理所有RxJava的onError逻辑
this.responseErrorListener = listener;
return this;
}
public Builder cacheFile(File cacheFile) {
this.cacheFile = cacheFile;
return this;
}
public Builder retrofitConfiguration(ClientModule.RetrofitConfiguration retrofitConfiguration) {
this.retrofitConfiguration = retrofitConfiguration;
return this;
}
public Builder okhttpConfiguration(ClientModule.OkhttpConfiguration okhttpConfiguration) {
this.okhttpConfiguration = okhttpConfiguration;
return this;
}
public Builder rxCacheConfiguration(ClientModule.RxCacheConfiguration rxCacheConfiguration) {
this.rxCacheConfiguration = rxCacheConfiguration;
return this;
}
public Builder gsonConfiguration(AppModule.GsonConfiguration gsonConfiguration) {
this.gsonConfiguration = gsonConfiguration;
return this;
}
public Builder printHttpLogLevel(RequestInterceptor.Level printHttpLogLevel) {//是否让框架打印 Http 的请求和响应信息
this.printHttpLogLevel = Preconditions.checkNotNull(printHttpLogLevel, "The printHttpLogLevel can not be null, use RequestInterceptor.Level.NONE instead.");
return this;
}
public Builder formatPrinter(FormatPrinter formatPrinter) {
this.formatPrinter = Preconditions.checkNotNull(formatPrinter, FormatPrinter.class.getCanonicalName() + "can not be null.");
return this;
}
public Builder cacheFactory(Cache.Factory cacheFactory) {
this.cacheFactory = cacheFactory;
return this;
}
public Builder executorService(ExecutorService executorService) {
this.executorService = executorService;
return this;
}
public Builder obtainServiceDelegate(IRepositoryManager.ObtainServiceDelegate obtainServiceDelegate) {
this.obtainServiceDelegate = obtainServiceDelegate;
return this;
}
public GlobalConfigModule build() {
return new GlobalConfigModule(this);
}
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/di/scope/ActivityScope.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.di.scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Scope;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* A scoping annotation to permit objects whose lifetime should
* conform to the life of the activity to be memorized in the
* correct component.
*/
@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivityScope {
}
================================================
FILE: arms/src/main/java/com/jess/arms/di/scope/FragmentScope.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.di.scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Scope;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* A scoping annotation to permit objects whose lifetime should
* conform to the life of the fragment to be memorized in the
* correct component.
*/
@Scope
@Documented
@Retention(RUNTIME)
public @interface FragmentScope {
}
================================================
FILE: arms/src/main/java/com/jess/arms/http/BaseUrl.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http;
import androidx.annotation.NonNull;
import okhttp3.HttpUrl;
/**
* ================================================
* 针对于 BaseUrl 在 App 启动时不能确定,需要请求服务器接口动态获取的应用场景
*
* Created by JessYan on 11/07/2017 14:58
* Contact me
* Follow me
* ================================================
*/
public interface BaseUrl {
/**
* 在调用 Retrofit API 接口之前,使用 Okhttp 或其他方式,请求到正确的 BaseUrl 并通过此方法返回
*
* @return
*/
@NonNull
HttpUrl url();
}
================================================
FILE: arms/src/main/java/com/jess/arms/http/GlobalHttpHandler.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.jess.arms.di.module.GlobalConfigModule;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
/**
* ================================================
* 处理 Http 请求和响应结果的处理类
* 使用 {@link GlobalConfigModule.Builder#globalHttpHandler(GlobalHttpHandler)} 方法配置
*
* @see GlobalHttpHandler Wiki 官方文档
* Created by JessYan on 8/30/16 17:47
* Contact me
* Follow me
* ================================================
*/
public interface GlobalHttpHandler {
/**
* 空实现
*/
GlobalHttpHandler EMPTY = new GlobalHttpHandler() {
@NonNull
@Override
public Response onHttpResultResponse(@Nullable String httpResult, @NonNull Interceptor.Chain chain, @NonNull Response response) {
//不管是否处理, 都必须将 response 返回出去
return response;
}
@NonNull
@Override
public Request onHttpRequestBefore(@NonNull Interceptor.Chain chain, @NonNull Request request) {
//不管是否处理, 都必须将 request 返回出去
return request;
}
};
/**
* 这里可以先客户端一步拿到每一次 Http 请求的结果, 可以先解析成 Json, 再做一些操作, 如检测到 token 过期后
* 重新请求 token, 并重新执行请求
*
* @param httpResult 服务器返回的结果 (已被框架自动转换为字符串)
* @param chain {@link okhttp3.Interceptor.Chain}
* @param response {@link Response}
* @return {@link Response}
*/
@NonNull
Response onHttpResultResponse(@Nullable String httpResult, @NonNull Interceptor.Chain chain, @NonNull Response response);
/**
* 这里可以在请求服务器之前拿到 {@link Request}, 做一些操作比如给 {@link Request} 统一添加 token 或者 header 以及参数加密等操作
*
* @param chain {@link okhttp3.Interceptor.Chain}
* @param request {@link Request}
* @return {@link Request}
*/
@NonNull
Request onHttpRequestBefore(@NonNull Interceptor.Chain chain, @NonNull Request request);
}
================================================
FILE: arms/src/main/java/com/jess/arms/http/OkHttpStreamFetcher.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http;
import android.util.Log;
import androidx.annotation.NonNull;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.HttpException;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.util.ContentLengthInputStream;
import com.bumptech.glide.util.Preconditions;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import okhttp3.Call;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
/**
* Fetches an {@link InputStream} using the okhttp library.
*/
public class OkHttpStreamFetcher implements DataFetcher, okhttp3.Callback {
private static final String TAG = "OkHttpFetcher";
private final Call.Factory client;
private final GlideUrl url;
private InputStream stream;
private ResponseBody responseBody;
private DataCallback super InputStream> callback;
// call may be accessed on the main thread while the object is in use on other threads. All other
// accesses to variables may occur on different threads, but only one at a time.
private volatile Call call;
// Public API.
@SuppressWarnings("WeakerAccess")
public OkHttpStreamFetcher(Call.Factory client, GlideUrl url) {
this.client = client;
this.url = url;
}
@Override
public void loadData(@NonNull Priority priority,
@NonNull final DataCallback super InputStream> callback) {
Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
for (Map.Entry headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
Request request = requestBuilder.build();
this.callback = callback;
call = client.newCall(request);
call.enqueue(this);
}
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "OkHttp failed to obtain result", e);
}
callback.onLoadFailed(e);
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
responseBody = response.body();
if (response.isSuccessful()) {
long contentLength = Preconditions.checkNotNull(responseBody).contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
callback.onDataReady(stream);
} else {
callback.onLoadFailed(new HttpException(response.message(), response.code()));
}
}
@Override
public void cleanup() {
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
// Ignored
}
if (responseBody != null) {
responseBody.close();
}
callback = null;
}
@Override
public void cancel() {
Call local = call;
if (local != null) {
local.cancel();
}
}
@NonNull
@Override
public Class getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/http/OkHttpUrlLoader.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http;
import androidx.annotation.NonNull;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import org.jetbrains.annotations.NotNull;
import java.io.InputStream;
import okhttp3.Call;
import okhttp3.OkHttpClient;
/**
* A simple model loader for fetching media over http/https using OkHttp.
*/
public class OkHttpUrlLoader implements ModelLoader {
private final Call.Factory client;
// Public API.
@SuppressWarnings("WeakerAccess")
public OkHttpUrlLoader(@NonNull Call.Factory client) {
this.client = client;
}
@Override
public boolean handles(@NonNull GlideUrl url) {
return true;
}
@Override
public LoadData buildLoadData(@NonNull GlideUrl model, int width, int height,
@NonNull Options options) {
return new LoadData<>(model, new OkHttpStreamFetcher(client, model));
}
/**
* The default factory for {@link OkHttpUrlLoader}s.
*/
// Public API.
@SuppressWarnings("WeakerAccess")
public static class Factory implements ModelLoaderFactory {
private static volatile Call.Factory internalClient;
private final Call.Factory client;
/**
* Constructor for a new Factory that runs requests using a static singleton client.
*/
public Factory() {
this(getInternalClient());
}
/**
* Constructor for a new Factory that runs requests using given client.
*
* @param client this is typically an instance of {@code OkHttpClient}.
*/
public Factory(@NonNull Call.Factory client) {
this.client = client;
}
private static Call.Factory getInternalClient() {
if (internalClient == null) {
synchronized (Factory.class) {
if (internalClient == null) {
internalClient = new OkHttpClient();
}
}
}
return internalClient;
}
@NonNull
@Override
public ModelLoader build(@NotNull MultiModelLoaderFactory multiFactory) {
return new OkHttpUrlLoader(client);
}
@Override
public void teardown() {
// Do nothing, this instance doesn't own the client.
}
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/http/imageloader/BaseImageLoaderStrategy.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http.imageloader;
import android.content.Context;
import androidx.annotation.Nullable;
/**
* ================================================
* 图片加载策略,实现 {@link BaseImageLoaderStrategy}
* 并通过 {@link ImageLoader#setLoadImgStrategy(BaseImageLoaderStrategy)} 配置后,才可进行图片请求
*
* Created by JessYan on 8/5/2016 15:50
* Contact me
* Follow me
* ================================================
*/
public interface BaseImageLoaderStrategy {
/**
* 加载图片
*
* @param ctx {@link Context}
* @param config 图片加载配置信息
*/
void loadImage(@Nullable Context ctx, @Nullable T config);
/**
* 停止加载
*
* @param ctx {@link Context}
* @param config 图片加载配置信息
*/
void clear(@Nullable Context ctx, @Nullable T config);
}
================================================
FILE: arms/src/main/java/com/jess/arms/http/imageloader/ImageConfig.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http.imageloader;
import android.widget.ImageView;
/**
* ================================================
* 这里是图片加载配置信息的基类,定义一些所有图片加载框架都可以用的通用参数
* 每个 {@link BaseImageLoaderStrategy} 应该对应一个 {@link ImageConfig} 实现类
*
* Created by JessYan on 8/5/16 15:19
* Contact me
* Follow me
* ================================================
*/
public class ImageConfig {
protected String url;
protected ImageView imageView;
protected int placeholder;//占位符
protected int errorPic;//错误占位符
public String getUrl() {
return url;
}
public ImageView getImageView() {
return imageView;
}
public int getPlaceholder() {
return placeholder;
}
public int getErrorPic() {
return errorPic;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/http/imageloader/ImageLoader.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http.imageloader;
import android.content.Context;
import androidx.annotation.Nullable;
import com.jess.arms.utils.Preconditions;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* ================================================
* {@link ImageLoader} 使用策略模式和建造者模式,可以动态切换图片请求框架(比如说切换成 Picasso )
* 当需要切换图片请求框架或图片请求框架升级后变更了 Api 时
* 这里可以将影响范围降到最低,所以封装 {@link ImageLoader} 是为了屏蔽这个风险
*
* @see ImageLoader wiki 文档
* Created by JessYan on 8/5/16 15:57
* Contact me
* Follow me
* ================================================
*/
@Singleton
public final class ImageLoader {
@Inject
@Nullable
BaseImageLoaderStrategy mStrategy;
@Inject
public ImageLoader() {
}
/**
* 加载图片
*
* @param context
* @param config
* @param
*/
public void loadImage(Context context, T config) {
Preconditions.checkNotNull(mStrategy, "Please implement BaseImageLoaderStrategy and call GlobalConfigModule.Builder#imageLoaderStrategy(BaseImageLoaderStrategy) in the applyOptions method of ConfigModule");
//noinspection unchecked
this.mStrategy.loadImage(context, config);
}
/**
* 停止加载或清理缓存
*
* @param context
* @param config
* @param
*/
public void clear(Context context, T config) {
Preconditions.checkNotNull(mStrategy, "Please implement BaseImageLoaderStrategy and call GlobalConfigModule.Builder#imageLoaderStrategy(BaseImageLoaderStrategy) in the applyOptions method of ConfigModule");
//noinspection unchecked
this.mStrategy.clear(context, config);
}
@Nullable
public BaseImageLoaderStrategy getLoadImgStrategy() {
return mStrategy;
}
/**
* 可在运行时随意切换 {@link BaseImageLoaderStrategy}
*
* @param strategy
*/
public void setLoadImgStrategy(BaseImageLoaderStrategy strategy) {
Preconditions.checkNotNull(strategy, "strategy == null");
this.mStrategy = strategy;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/http/imageloader/glide/GlideAppliesOptions.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http.imageloader.glide;
import android.content.Context;
import androidx.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.jess.arms.http.imageloader.BaseImageLoaderStrategy;
/**
* ================================================
* 如果你想具有配置 @{@link Glide} 的权利,则需要让 {@link BaseImageLoaderStrategy}
* 的实现类也必须实现 {@link GlideAppliesOptions}
*
* Created by JessYan on 13/08/2017 22:02
* Contact me
* Follow me
* ================================================
*/
public interface GlideAppliesOptions {
/**
* 配置 @{@link Glide} 的自定义参数,此方法在 @{@link Glide} 初始化时执行(@{@link Glide} 在第一次被调用时初始化),只会执行一次
*
* @param context
* @param builder {@link GlideBuilder} 此类被用来创建 Glide
*/
void applyGlideOptions(@NonNull Context context, @NonNull GlideBuilder builder);
/**
* 注册{@link Glide}的组件,参考{@link com.bumptech.glide.module.LibraryGlideModule}
*
* @param context Android context
* @param glide {@link Glide}
* @param registry {@link Registry}
*/
void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry);
}
================================================
FILE: arms/src/main/java/com/jess/arms/http/imageloader/glide/GlideConfiguration.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http.imageloader.glide;
import android.content.Context;
import androidx.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;
import com.bumptech.glide.load.engine.cache.DiskLruCacheWrapper;
import com.bumptech.glide.load.engine.cache.LruResourceCache;
import com.bumptech.glide.load.engine.cache.MemorySizeCalculator;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.AppGlideModule;
import com.jess.arms.di.component.AppComponent;
import com.jess.arms.http.OkHttpUrlLoader;
import com.jess.arms.http.imageloader.BaseImageLoaderStrategy;
import com.jess.arms.utils.ArmsUtils;
import com.jess.arms.utils.DataHelper;
import java.io.File;
import java.io.InputStream;
/**
* ================================================
* {@link AppGlideModule} 的默认实现类
* 用于配置缓存文件夹,切换图片请求框架等操作
*
* Created by JessYan on 16/4/15.
* Contact me
* Follow me
* ================================================
*/
@GlideModule(glideName = "GlideArms")
public class GlideConfiguration extends AppGlideModule {
public static final int IMAGE_DISK_CACHE_MAX_SIZE = 100 * 1024 * 1024;//图片缓存文件最大值为100Mb
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
final AppComponent appComponent = ArmsUtils.obtainAppComponentFromContext(context);
builder.setDiskCache(() -> {
// Careful: the external cache directory doesn't enforce permissions
return DiskLruCacheWrapper.create(DataHelper.makeDirs(new File(appComponent.cacheFile(), "Glide")), IMAGE_DISK_CACHE_MAX_SIZE);
});
MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context).build();
int defaultMemoryCacheSize = calculator.getMemoryCacheSize();
int defaultBitmapPoolSize = calculator.getBitmapPoolSize();
int customMemoryCacheSize = (int) (1.2 * defaultMemoryCacheSize);
int customBitmapPoolSize = (int) (1.2 * defaultBitmapPoolSize);
builder.setMemoryCache(new LruResourceCache(customMemoryCacheSize));
builder.setBitmapPool(new LruBitmapPool(customBitmapPoolSize));
//将配置 Glide 的机会转交给 GlideImageLoaderStrategy,如你觉得框架提供的 GlideImageLoaderStrategy
//并不能满足自己的需求,想自定义 BaseImageLoaderStrategy,那请你最好实现 GlideAppliesOptions
//因为只有成为 GlideAppliesOptions 的实现类,这里才能调用 applyGlideOptions(),让你具有配置 Glide 的权利
BaseImageLoaderStrategy loadImgStrategy = appComponent.imageLoader().getLoadImgStrategy();
if (loadImgStrategy instanceof GlideAppliesOptions) {
((GlideAppliesOptions) loadImgStrategy).applyGlideOptions(context, builder);
}
}
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
//Glide 默认使用 HttpURLConnection 做网络请求,在这切换成 Okhttp 请求
AppComponent appComponent = ArmsUtils.obtainAppComponentFromContext(context);
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(appComponent.okHttpClient()));
BaseImageLoaderStrategy loadImgStrategy = appComponent.imageLoader().getLoadImgStrategy();
if (loadImgStrategy instanceof GlideAppliesOptions) {
((GlideAppliesOptions) loadImgStrategy).registerComponents(context, glide, registry);
}
}
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/http/log/DefaultFormatPrinter.java
================================================
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http.log;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.jess.arms.di.module.GlobalConfigModule;
import com.jess.arms.utils.CharacterHandler;
import com.jess.arms.utils.LogUtils;
import java.util.List;
import okhttp3.MediaType;
import okhttp3.Request;
/**
* ================================================
* 对 OkHttp 的请求和响应信息进行更规范和清晰的打印, 此类为框架默认实现, 以默认格式打印信息, 若觉得默认打印格式
* 并不能满足自己的需求, 可自行扩展自己理想的打印格式
*
* @see GlobalConfigModule.Builder#formatPrinter(FormatPrinter)
* Created by JessYan on 25/01/2018 14:51
* Contact me
* Follow me
* ================================================
*/
public class DefaultFormatPrinter implements FormatPrinter {
private static final String TAG = "ArmsHttpLog";
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static final String DOUBLE_SEPARATOR = LINE_SEPARATOR + LINE_SEPARATOR;
private static final String[] OMITTED_RESPONSE = {LINE_SEPARATOR, "Omitted response body"};
private static final String[] OMITTED_REQUEST = {LINE_SEPARATOR, "Omitted request body"};
private static final String N = "\n";
private static final String T = "\t";
private static final String REQUEST_UP_LINE = " ┌────── Request ────────────────────────────────────────────────────────────────────────";
private static final String END_LINE = " └───────────────────────────────────────────────────────────────────────────────────────";
private static final String RESPONSE_UP_LINE = " ┌────── Response ───────────────────────────────────────────────────────────────────────";
private static final String BODY_TAG = "Body:";
private static final String URL_TAG = "URL: ";
private static final String METHOD_TAG = "Method: @";
private static final String HEADERS_TAG = "Headers:";
private static final String STATUS_CODE_TAG = "Status Code: ";
private static final String RECEIVED_TAG = "Received in: ";
private static final String CORNER_UP = "┌ ";
private static final String CORNER_BOTTOM = "└ ";
private static final String CENTER_LINE = "├ ";
private static final String DEFAULT_LINE = "│ ";
private static final String[] ARMS = new String[]{"-A-", "-R-", "-M-", "-S-"};
private static ThreadLocal last = new ThreadLocal() {
@Override
protected Integer initialValue() {
return 0;
}
};
private static boolean isEmpty(String line) {
return TextUtils.isEmpty(line) || N.equals(line) || T.equals(line) || TextUtils.isEmpty(line.trim());
}
/**
* 对 {@code lines} 中的信息进行逐行打印
*
* @param tag
* @param lines
* @param withLineSize 为 {@code true} 时, 每行的信息长度不会超过110, 超过则自动换行
*/
private static void logLines(String tag, String[] lines, boolean withLineSize) {
for (String line : lines) {
int lineLength = line.length();
int maxLongSize = withLineSize ? 110 : lineLength;
for (int i = 0; i <= lineLength / maxLongSize; i++) {
int start = i * maxLongSize;
int end = (i + 1) * maxLongSize;
end = end > line.length() ? line.length() : end;
LogUtils.debugInfo(resolveTag(tag), DEFAULT_LINE + line.substring(start, end));
}
}
}
private static String computeKey() {
if (last.get() >= 4) {
last.set(0);
}
String s = ARMS[last.get()];
last.set(last.get() + 1);
return s;
}
/**
* 此方法是为了解决在 AndroidStudio v3.1 以上 Logcat 输出的日志无法对齐的问题
*
* 此问题引起的原因, 据 JessYan 猜测, 可能是因为 AndroidStudio v3.1 以上将极短时间内以相同 tag 输出多次的 log 自动合并为一次输出
* 导致本来对称的输出日志, 出现不对称的问题
* AndroidStudio v3.1 此次对输出日志的优化, 不小心使市面上所有具有日志格式化输出功能的日志框架无法正常工作
* 现在暂时能想到的解决方案有两个: 1. 改变每行的 tag (每行 tag 都加一个可变化的 token) 2. 延迟每行日志打印的间隔时间
*
* {@link #resolveTag(String)} 使用第一种解决方案
*
* @param tag
*/
private static String resolveTag(String tag) {
return computeKey() + tag;
}
private static String[] getRequest(Request request) {
String log;
String header = request.headers().toString();
log = METHOD_TAG + request.method() + DOUBLE_SEPARATOR +
(isEmpty(header) ? "" : HEADERS_TAG + LINE_SEPARATOR + dotHeaders(header));
return log.split(LINE_SEPARATOR);
}
private static String[] getResponse(String header, long tookMs, int code, boolean isSuccessful,
List segments, String message) {
String log;
String segmentString = slashSegments(segments);
log = ((!TextUtils.isEmpty(segmentString) ? segmentString + " - " : "") + "is success : "
+ isSuccessful + " - " + RECEIVED_TAG + tookMs + "ms" + DOUBLE_SEPARATOR + STATUS_CODE_TAG +
code + " / " + message + DOUBLE_SEPARATOR + (isEmpty(header) ? "" : HEADERS_TAG + LINE_SEPARATOR +
dotHeaders(header)));
return log.split(LINE_SEPARATOR);
}
private static String slashSegments(List segments) {
StringBuilder segmentString = new StringBuilder();
for (String segment : segments) {
segmentString.append("/").append(segment);
}
return segmentString.toString();
}
/**
* 对 {@code header} 按规定的格式进行处理
*
* @param header
* @return
*/
private static String dotHeaders(String header) {
String[] headers = header.split(LINE_SEPARATOR);
StringBuilder builder = new StringBuilder();
String tag = "─ ";
if (headers.length > 1) {
for (int i = 0; i < headers.length; i++) {
if (i == 0) {
tag = CORNER_UP;
} else if (i == headers.length - 1) {
tag = CORNER_BOTTOM;
} else {
tag = CENTER_LINE;
}
builder.append(tag).append(headers[i]).append("\n");
}
} else {
for (String item : headers) {
builder.append(tag).append(item).append("\n");
}
}
return builder.toString();
}
private static String getTag(boolean isRequest) {
if (isRequest) {
return TAG + "-Request";
} else {
return TAG + "-Response";
}
}
/**
* 打印网络请求信息, 当网络请求时 {{@link okhttp3.RequestBody}} 可以解析的情况
*
* @param request
* @param bodyString
*/
@Override
public void printJsonRequest(@NonNull Request request, @NonNull String bodyString) {
final String requestBody = LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + bodyString;
final String tag = getTag(true);
LogUtils.debugInfo(tag, REQUEST_UP_LINE);
logLines(tag, new String[]{URL_TAG + request.url()}, false);
logLines(tag, getRequest(request), true);
logLines(tag, requestBody.split(LINE_SEPARATOR), true);
LogUtils.debugInfo(tag, END_LINE);
}
/**
* 打印网络请求信息, 当网络请求时 {{@link okhttp3.RequestBody}} 为 {@code null} 或不可解析的情况
*
* @param request
*/
@Override
public void printFileRequest(@NonNull Request request) {
final String tag = getTag(true);
LogUtils.debugInfo(tag, REQUEST_UP_LINE);
logLines(tag, new String[]{URL_TAG + request.url()}, false);
logLines(tag, getRequest(request), true);
logLines(tag, OMITTED_REQUEST, true);
LogUtils.debugInfo(tag, END_LINE);
}
/**
* 打印网络响应信息, 当网络响应时 {{@link okhttp3.ResponseBody}} 可以解析的情况
*
* @param chainMs 服务器响应耗时(单位毫秒)
* @param isSuccessful 请求是否成功
* @param code 响应码
* @param headers 请求头
* @param contentType 服务器返回数据的数据类型
* @param bodyString 服务器返回的数据(已解析)
* @param segments 域名后面的资源地址
* @param message 响应信息
* @param responseUrl 请求地址
*/
@Override
public void printJsonResponse(long chainMs, boolean isSuccessful, int code, @NonNull String headers, @Nullable MediaType contentType,
@Nullable String bodyString, @NonNull List segments, @NonNull String message, @NonNull final String responseUrl) {
bodyString = RequestInterceptor.isJson(contentType) ? CharacterHandler.jsonFormat(bodyString)
: RequestInterceptor.isXml(contentType) ? CharacterHandler.xmlFormat(bodyString) : bodyString;
final String responseBody = LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + bodyString;
final String tag = getTag(false);
final String[] urlLine = {URL_TAG + responseUrl, N};
LogUtils.debugInfo(tag, RESPONSE_UP_LINE);
logLines(tag, urlLine, true);
logLines(tag, getResponse(headers, chainMs, code, isSuccessful, segments, message), true);
logLines(tag, responseBody.split(LINE_SEPARATOR), true);
LogUtils.debugInfo(tag, END_LINE);
}
/**
* 打印网络响应信息, 当网络响应时 {{@link okhttp3.ResponseBody}} 为 {@code null} 或不可解析的情况
*
* @param chainMs 服务器响应耗时(单位毫秒)
* @param isSuccessful 请求是否成功
* @param code 响应码
* @param headers 请求头
* @param segments 域名后面的资源地址
* @param message 响应信息
* @param responseUrl 请求地址
*/
@Override
public void printFileResponse(long chainMs, boolean isSuccessful, int code, @NonNull String headers,
@NonNull List segments, @NonNull String message, @NonNull final String responseUrl) {
final String tag = getTag(false);
final String[] urlLine = {URL_TAG + responseUrl, N};
LogUtils.debugInfo(tag, RESPONSE_UP_LINE);
logLines(tag, urlLine, true);
logLines(tag, getResponse(headers, chainMs, code, isSuccessful, segments, message), true);
logLines(tag, OMITTED_RESPONSE, true);
LogUtils.debugInfo(tag, END_LINE);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/http/log/FormatPrinter.java
================================================
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http.log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.jess.arms.di.module.GlobalConfigModule;
import java.util.List;
import okhttp3.MediaType;
import okhttp3.Request;
/**
* ================================================
* 对 OkHttp 的请求和响应信息进行更规范和清晰的打印, 开发者可更根据自己的需求自行扩展打印格式
*
* @see DefaultFormatPrinter
* @see GlobalConfigModule.Builder#formatPrinter(FormatPrinter)
* Created by JessYan on 31/01/2018 17:36
* Contact me
* Follow me
* ================================================
*/
public interface FormatPrinter {
/**
* 打印网络请求信息, 当网络请求时 {{@link okhttp3.RequestBody}} 可以解析的情况
*
* @param request
* @param bodyString 发送给服务器的请求体中的数据(已解析)
*/
void printJsonRequest(@NonNull Request request, @NonNull String bodyString);
/**
* 打印网络请求信息, 当网络请求时 {{@link okhttp3.RequestBody}} 为 {@code null} 或不可解析的情况
*
* @param request
*/
void printFileRequest(@NonNull Request request);
/**
* 打印网络响应信息, 当网络响应时 {{@link okhttp3.ResponseBody}} 可以解析的情况
*
* @param chainMs 服务器响应耗时(单位毫秒)
* @param isSuccessful 请求是否成功
* @param code 响应码
* @param headers 请求头
* @param contentType 服务器返回数据的数据类型
* @param bodyString 服务器返回的数据(已解析)
* @param segments 域名后面的资源地址
* @param message 响应信息
* @param responseUrl 请求地址
*/
void printJsonResponse(long chainMs, boolean isSuccessful, int code, @NonNull String headers, @Nullable MediaType contentType,
@Nullable String bodyString, @NonNull List segments, @NonNull String message, @NonNull String responseUrl);
/**
* 打印网络响应信息, 当网络响应时 {{@link okhttp3.ResponseBody}} 为 {@code null} 或不可解析的情况
*
* @param chainMs 服务器响应耗时(单位毫秒)
* @param isSuccessful 请求是否成功
* @param code 响应码
* @param headers 请求头
* @param segments 域名后面的资源地址
* @param message 响应信息
* @param responseUrl 请求地址
*/
void printFileResponse(long chainMs, boolean isSuccessful, int code, @NonNull String headers,
@NonNull List segments, @NonNull String message, @NonNull String responseUrl);
}
================================================
FILE: arms/src/main/java/com/jess/arms/http/log/RequestInterceptor.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http.log;
import androidx.annotation.Nullable;
import com.jess.arms.di.module.GlobalConfigModule;
import com.jess.arms.http.GlobalHttpHandler;
import com.jess.arms.utils.CharacterHandler;
import com.jess.arms.utils.UrlEncoderUtils;
import com.jess.arms.utils.ZipHelper;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import timber.log.Timber;
/**
* ================================================
* 解析框架中的网络请求和响应结果,并以日志形式输出,调试神器
* 可使用 {@link GlobalConfigModule.Builder#printHttpLogLevel(Level)} 控制或关闭日志
*
* Created by JessYan on 7/1/2016.
* Contact me
* Follow me
* ================================================
*/
@Singleton
public class RequestInterceptor implements Interceptor {
@Inject
@Nullable
GlobalHttpHandler mHandler;
@Inject
FormatPrinter mPrinter;
@Inject
Level printLevel;
@Inject
public RequestInterceptor() {
}
/**
* 解析请求服务器的请求参数
*
* @param request {@link Request}
* @return 解析后的请求信息
* @throws UnsupportedEncodingException
*/
public static String parseParams(Request request) throws UnsupportedEncodingException {
try {
RequestBody body = request.newBuilder().build().body();
if (body == null) {
return "";
}
Buffer requestbuffer = new Buffer();
body.writeTo(requestbuffer);
Charset charset = Charset.forName("UTF-8");
MediaType contentType = body.contentType();
if (contentType != null) {
charset = contentType.charset(charset);
}
String json = requestbuffer.readString(charset);
if (UrlEncoderUtils.hasUrlEncoded(json)) {
json = URLDecoder.decode(json, convertCharset(charset));
}
return CharacterHandler.jsonFormat(json);
} catch (IOException e) {
e.printStackTrace();
return "{\"error\": \"" + e.getMessage() + "\"}";
}
}
/**
* 是否可以解析
*
* @param mediaType {@link MediaType}
* @return {@code true} 为可以解析
*/
public static boolean isParseable(MediaType mediaType) {
if (mediaType == null || mediaType.type() == null) {
return false;
}
return isText(mediaType) || isPlain(mediaType)
|| isJson(mediaType) || isForm(mediaType)
|| isHtml(mediaType) || isXml(mediaType);
}
public static boolean isText(MediaType mediaType) {
if (mediaType == null || mediaType.type() == null) {
return false;
}
return "text".equals(mediaType.type());
}
public static boolean isPlain(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) {
return false;
}
return mediaType.subtype().toLowerCase().contains("plain");
}
public static boolean isJson(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) {
return false;
}
return mediaType.subtype().toLowerCase().contains("json");
}
public static boolean isXml(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) {
return false;
}
return mediaType.subtype().toLowerCase().contains("xml");
}
public static boolean isHtml(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) {
return false;
}
return mediaType.subtype().toLowerCase().contains("html");
}
public static boolean isForm(MediaType mediaType) {
if (mediaType == null || mediaType.subtype() == null) {
return false;
}
return mediaType.subtype().toLowerCase().contains("x-www-form-urlencoded");
}
public static String convertCharset(Charset charset) {
String s = charset.toString();
int i = s.indexOf("[");
if (i == -1) {
return s;
}
return s.substring(i + 1, s.length() - 1);
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
boolean logRequest = printLevel == Level.ALL || (printLevel != Level.NONE && printLevel == Level.REQUEST);
if (logRequest) {
//打印请求信息
if (request.body() != null && isParseable(request.body().contentType())) {
mPrinter.printJsonRequest(request, parseParams(request));
} else {
mPrinter.printFileRequest(request);
}
}
boolean logResponse = printLevel == Level.ALL || (printLevel != Level.NONE && printLevel == Level.RESPONSE);
long t1 = logResponse ? System.nanoTime() : 0;
Response originalResponse;
try {
originalResponse = chain.proceed(request);
} catch (Exception e) {
Timber.w("Http Error: %s", e);
throw e;
}
long t2 = logResponse ? System.nanoTime() : 0;
ResponseBody responseBody = originalResponse.body();
//打印响应结果
String bodyString = null;
if (responseBody != null && isParseable(responseBody.contentType())) {
bodyString = printResult(request, originalResponse, logResponse);
}
if (logResponse) {
final List segmentList = request.url().encodedPathSegments();
final String header = originalResponse.headers().toString();
final int code = originalResponse.code();
final boolean isSuccessful = originalResponse.isSuccessful();
final String message = originalResponse.message();
final String url = originalResponse.request().url().toString();
if (responseBody != null && isParseable(responseBody.contentType())) {
mPrinter.printJsonResponse(TimeUnit.NANOSECONDS.toMillis(t2 - t1), isSuccessful,
code, header, responseBody.contentType(), bodyString, segmentList, message, url);
} else {
mPrinter.printFileResponse(TimeUnit.NANOSECONDS.toMillis(t2 - t1),
isSuccessful, code, header, segmentList, message, url);
}
}
if (mHandler != null)//这里可以比客户端提前一步拿到服务器返回的结果,可以做一些操作,比如token超时,重新获取
{
return mHandler.onHttpResultResponse(bodyString, chain, originalResponse);
}
return originalResponse;
}
/**
* 打印响应结果
*
* @param request {@link Request}
* @param response {@link Response}
* @param logResponse 是否打印响应结果
* @return 解析后的响应结果
* @throws IOException
*/
@Nullable
private String printResult(Request request, Response response, boolean logResponse) throws IOException {
try {
//读取服务器返回的结果
ResponseBody responseBody = response.newBuilder().build().body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
//获取content的压缩类型
String encoding = response
.headers()
.get("Content-Encoding");
Buffer clone = buffer.clone();
//解析response content
return parseContent(responseBody, encoding, clone);
} catch (IOException e) {
e.printStackTrace();
return "{\"error\": \"" + e.getMessage() + "\"}";
}
}
/**
* 解析服务器响应的内容
*
* @param responseBody {@link ResponseBody}
* @param encoding 编码类型
* @param clone 克隆后的服务器响应内容
* @return 解析后的响应结果
*/
private String parseContent(ResponseBody responseBody, String encoding, Buffer clone) {
Charset charset = Charset.forName("UTF-8");
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(charset);
}
//content 使用 gzip 压缩
if ("gzip".equalsIgnoreCase(encoding)) {
//解压
return ZipHelper.decompressForGzip(clone.readByteArray(), convertCharset(charset));
} else if ("zlib".equalsIgnoreCase(encoding)) {
//content 使用 zlib 压缩
return ZipHelper.decompressToStringForZlib(clone.readByteArray(), convertCharset(charset));
} else {
//content 没有被压缩, 或者使用其他未知压缩方式
return clone.readString(charset);
}
}
public enum Level {
/**
* 不打印log
*/
NONE,
/**
* 只打印请求信息
*/
REQUEST,
/**
* 只打印响应信息
*/
RESPONSE,
/**
* 所有数据全部打印
*/
ALL
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/ActivityLifecycle.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.jess.arms.base.BaseFragment;
import com.jess.arms.base.delegate.ActivityDelegate;
import com.jess.arms.base.delegate.ActivityDelegateImpl;
import com.jess.arms.base.delegate.FragmentDelegate;
import com.jess.arms.base.delegate.IActivity;
import com.jess.arms.integration.cache.Cache;
import com.jess.arms.integration.cache.IntelligentCache;
import com.jess.arms.utils.Preconditions;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Lazy;
/**
* ================================================
* {@link Application.ActivityLifecycleCallbacks} 默认实现类
* 通过 {@link ActivityDelegate} 管理 {@link Activity}
*
* @see ActivityLifecycleCallbacks 分析文章
* Created by JessYan on 21/02/2017 14:23
* Contact me
* Follow me
* ================================================
*/
@Singleton
public class ActivityLifecycle implements Application.ActivityLifecycleCallbacks {
@Inject
AppManager mAppManager;
@Inject
Application mApplication;
@Inject
Cache mExtras;
@Inject
Lazy mFragmentLifecycle;
@Inject
Lazy> mFragmentLifecycles;
@Inject
public ActivityLifecycle() {
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
//如果 intent 包含了此字段,并且为 true 说明不加入到 list 进行统一管理
boolean isNotAdd = false;
if (activity.getIntent() != null) {
isNotAdd = activity.getIntent().getBooleanExtra(AppManager.IS_NOT_ADD_ACTIVITY_LIST, false);
}
if (!isNotAdd) {
mAppManager.addActivity(activity);
}
//配置ActivityDelegate
if (activity instanceof IActivity) {
ActivityDelegate activityDelegate = fetchActivityDelegate(activity);
if (activityDelegate == null) {
Cache cache = getCacheFromActivity((IActivity) activity);
activityDelegate = new ActivityDelegateImpl(activity);
//使用 IntelligentCache.KEY_KEEP 作为 key 的前缀, 可以使储存的数据永久存储在内存中
//否则存储在 LRU 算法的存储空间中, 前提是 Activity 使用的是 IntelligentCache (框架默认使用)
cache.put(IntelligentCache.getKeyOfKeep(ActivityDelegate.ACTIVITY_DELEGATE), activityDelegate);
}
activityDelegate.onCreate(savedInstanceState);
}
registerFragmentCallbacks(activity);
}
@Override
public void onActivityStarted(Activity activity) {
ActivityDelegate activityDelegate = fetchActivityDelegate(activity);
if (activityDelegate != null) {
activityDelegate.onStart();
}
}
@Override
public void onActivityResumed(Activity activity) {
mAppManager.setCurrentActivity(activity);
ActivityDelegate activityDelegate = fetchActivityDelegate(activity);
if (activityDelegate != null) {
activityDelegate.onResume();
}
}
@Override
public void onActivityPaused(Activity activity) {
ActivityDelegate activityDelegate = fetchActivityDelegate(activity);
if (activityDelegate != null) {
activityDelegate.onPause();
}
}
@Override
public void onActivityStopped(Activity activity) {
if (mAppManager.getCurrentActivity() == activity) {
mAppManager.setCurrentActivity(null);
}
ActivityDelegate activityDelegate = fetchActivityDelegate(activity);
if (activityDelegate != null) {
activityDelegate.onStop();
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
ActivityDelegate activityDelegate = fetchActivityDelegate(activity);
if (activityDelegate != null) {
activityDelegate.onSaveInstanceState(outState);
}
}
@Override
public void onActivityDestroyed(Activity activity) {
mAppManager.removeActivity(activity);
ActivityDelegate activityDelegate = fetchActivityDelegate(activity);
if (activityDelegate != null) {
activityDelegate.onDestroy();
getCacheFromActivity((IActivity) activity).clear();
}
}
/**
* 给每个 Activity 的所有 Fragment 设置监听其生命周期, Activity 可以通过 {@link IActivity#useFragment()}
* 设置是否使用监听,如果这个 Activity 返回 false 的话,这个 Activity 下面的所有 Fragment 将不能使用 {@link FragmentDelegate}
* 意味着 {@link BaseFragment} 也不能使用
*
* @param activity
*/
private void registerFragmentCallbacks(Activity activity) {
boolean useFragment = !(activity instanceof IActivity) || ((IActivity) activity).useFragment();
if (activity instanceof FragmentActivity && useFragment) {
//mFragmentLifecycle 为 Fragment 生命周期实现类, 用于框架内部对每个 Fragment 的必要操作, 如给每个 Fragment 配置 FragmentDelegate
//注册框架内部已实现的 Fragment 生命周期逻辑
((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycle.get(), true);
if (mExtras.containsKey(IntelligentCache.getKeyOfKeep(ConfigModule.class.getName()))) {
@SuppressWarnings("unchecked")
List modules = (List) mExtras.get(IntelligentCache.getKeyOfKeep(ConfigModule.class.getName()));
if (modules != null) {
for (ConfigModule module : modules) {
module.injectFragmentLifecycle(mApplication, mFragmentLifecycles.get());
}
}
mExtras.remove(IntelligentCache.getKeyOfKeep(ConfigModule.class.getName()));
}
//注册框架外部, 开发者扩展的 Fragment 生命周期逻辑
for (FragmentManager.FragmentLifecycleCallbacks fragmentLifecycle : mFragmentLifecycles.get()) {
((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(fragmentLifecycle, true);
}
}
}
private ActivityDelegate fetchActivityDelegate(Activity activity) {
ActivityDelegate activityDelegate = null;
if (activity instanceof IActivity) {
Cache cache = getCacheFromActivity((IActivity) activity);
activityDelegate = (ActivityDelegate) cache.get(IntelligentCache.getKeyOfKeep(ActivityDelegate.ACTIVITY_DELEGATE));
}
return activityDelegate;
}
@NonNull
private Cache getCacheFromActivity(IActivity activity) {
Cache cache = activity.provideCache();
Preconditions.checkNotNull(cache, "%s cannot be null on Activity", Cache.class.getName());
return cache;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/AppManager.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration;
import android.app.Activity;
import android.app.Application;
import android.app.Dialog;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Message;
import android.view.View;
import com.google.android.material.snackbar.Snackbar;
import com.jess.arms.base.delegate.AppLifecycles;
import com.jess.arms.utils.ArmsUtils;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import androidx.annotation.Nullable;
import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import timber.log.Timber;
import static com.jess.arms.base.Platform.DEPENDENCY_SUPPORT_DESIGN;
/**
* ================================================
* 用于管理所有 {@link Activity}, 和在前台的 {@link Activity}
*
* @see AppManager wiki 官方文档
* Created by JessYan on 14/12/2016 13:50
* Contact me
* Follow me
* ================================================
*/
public final class AppManager {
/**
* true 为不需要加入到 Activity 容器进行统一管理,默认为 false
*/
public static final String IS_NOT_ADD_ACTIVITY_LIST = "is_not_add_activity_list";
private static volatile AppManager sAppManager;
protected final String TAG = this.getClass().getSimpleName();
private Application mApplication;
/**
* 管理所有存活的 Activity, 容器中的顺序仅仅是 Activity 的创建顺序, 并不能保证和 Activity 任务栈顺序一致
*/
private List mActivityList;
/**
* 当前在前台的 Activity
*/
private Activity mCurrentActivity;
/**
* 此方法作废, 现在可通过 {@link AppManager#getAppManager()} 直接访问 {@link AppManager}
*
* 提供给外部扩展 {@link AppManager} 的 {@link #onReceive(Message)} 方法
*/
private HandleListener mHandleListener;
private AppManager() {
}
public static AppManager getAppManager() {
if (sAppManager == null) {
synchronized (AppManager.class) {
if (sAppManager == null) {
sAppManager = new AppManager();
}
}
}
return sAppManager;
}
/**
* 此方法作废, 现在可通过 {@link AppManager#getAppManager()} 直接访问 {@link AppManager}
*
* 通过此方法远程遥控 {@link AppManager}, 使 {@link #onReceive(Message)} 执行对应方法
*
* @param msg {@link Message}
*/
@Deprecated
public static void post(Message msg) {
getAppManager().onReceive(msg);
}
public AppManager init(Application application) {
this.mApplication = application;
return sAppManager;
}
/**
* 此方法作废, 现在可通过 {@link AppManager#getAppManager()} 直接访问 {@link AppManager}
*
* 可通过 {@link #setHandleListener(HandleListener)}, 让外部可扩展新的事件
*
* @param message
*/
@Deprecated
public void onReceive(Message message) {
if (mHandleListener != null) {
mHandleListener.handleMessage(this, message);
}
}
@Deprecated
public HandleListener getHandleListener() {
return mHandleListener;
}
/**
* 此方法作废, 现在可通过 {@link AppManager#getAppManager()} 直接访问 {@link AppManager}
*
* 提供给外部扩展 {@link AppManager} 的 {@link #onReceive} 方法(远程遥控 {@link AppManager} 的功能)
* 建议在 {@link ConfigModule#injectAppLifecycle(Context, List)} 中
* 通过 {@link AppLifecycles#onCreate(Application)} 在 App 初始化时,使用此方法传入自定义的 {@link HandleListener}
*
* @param handleListener
*/
@Deprecated
public void setHandleListener(HandleListener handleListener) {
this.mHandleListener = handleListener;
}
/**
* 让在前台的 {@link Activity}, 使用 {@link Snackbar} 显示文本内容
*
* @param message
* @param isLong
*/
public void showSnackbar(String message, boolean isLong) {
if (getCurrentActivity() == null && getTopActivity() == null) {
Timber.tag(TAG).w("mCurrentActivity == null when showSnackbar(String,boolean)");
return;
}
Completable.fromAction(() -> {
//Arms 已将 com.google.android.material:material 从依赖中移除 (目的是减小 Arms 体积, design 库中含有太多 View)
//因为 Snackbar 在 com.google.android.material:material 库中, 所以如果框架使用者没有自行依赖 com.google.android.material:material
//Arms 则会使用 Toast 替代 Snackbar 显示信息, 如果框架使用者依赖了 arms-autolayout 库就不用依赖 com.google.android.material:material 了
//因为在 arms-autolayout 库中已经依赖有 com.google.android.material:material
if (DEPENDENCY_SUPPORT_DESIGN) {
Activity activity = getCurrentActivity() == null ? getTopActivity() : getCurrentActivity();
View view = activity.getWindow().getDecorView().findViewById(android.R.id.content);
Snackbar.make(view, message, isLong ? Snackbar.LENGTH_LONG : Snackbar.LENGTH_SHORT).show();
} else {
ArmsUtils.makeText(mApplication, message);
}
}).subscribeOn(AndroidSchedulers.mainThread()).subscribe();
}
/**
* 让在栈顶的 {@link Activity} ,打开指定的 {@link Activity}
*
* @param intent
*/
public void startActivity(Intent intent) {
if (getTopActivity() == null) {
Timber.tag(TAG).w("mCurrentActivity == null when startActivity(Intent)");
//如果没有前台的activity就使用new_task模式启动activity
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mApplication.startActivity(intent);
return;
}
getTopActivity().startActivity(intent);
}
/**
* 让在栈顶的 {@link Activity} ,打开指定的 {@link Activity}
*
* @param activityClass
*/
public void startActivity(Class activityClass) {
startActivity(new Intent(mApplication, activityClass));
}
/**
* 释放资源
*/
public void release() {
mActivityList.clear();
mHandleListener = null;
mActivityList = null;
mCurrentActivity = null;
mApplication = null;
}
/**
* 获取在前台的 {@link Activity} (保证获取到的 {@link Activity} 正处于可见状态, 即未调用 {@link Activity#onStop()}), 获取的 {@link Activity} 存续时间
* 是在 {@link Activity#onStop()} 之前, 所以如果当此 {@link Activity} 调用 {@link Activity#onStop()} 方法之后, 没有其他的 {@link Activity} 回到前台(用户返回桌面或者打开了其他 App 会出现此状况)
* 这时调用 {@link #getCurrentActivity()} 有可能返回 {@code null}, 所以请注意使用场景和 {@link #getTopActivity()} 不一样
*
* Example usage:
* 使用场景比较适合, 只需要在可见状态的 {@link Activity} 上执行的操作
* 如当后台 {@link Service} 执行某个任务时, 需要让前台 {@link Activity} ,做出某种响应操作或其他操作,如弹出 {@link Dialog}, 这时在 {@link Service} 中就可以使用 {@link #getCurrentActivity()}
* 如果返回为 {@code null}, 说明没有前台 {@link Activity} (用户返回桌面或者打开了其他 App 会出现此状况), 则不做任何操作, 不为 {@code null}, 则弹出 {@link Dialog}
*
* @return
*/
@Nullable
public Activity getCurrentActivity() {
return mCurrentActivity;
}
/**
* 将在前台的 {@link Activity} 赋值给 {@code currentActivity}, 注意此方法是在 {@link Activity#onResume} 方法执行时将栈顶的 {@link Activity} 赋值给 {@code currentActivity}
* 所以在栈顶的 {@link Activity} 执行 {@link Activity#onCreate} 方法时使用 {@link #getCurrentActivity()} 获取的就不是当前栈顶的 {@link Activity}, 可能是上一个 {@link Activity}
* 如果在 App 启动第一个 {@link Activity} 执行 {@link Activity#onCreate} 方法时使用 {@link #getCurrentActivity()} 则会出现返回为 {@code null} 的情况
* 想避免这种情况请使用 {@link #getTopActivity()}
*
* @param currentActivity
*/
public void setCurrentActivity(Activity currentActivity) {
this.mCurrentActivity = currentActivity;
}
/**
* 获取最近启动的一个 {@link Activity}, 此方法不保证获取到的 {@link Activity} 正处于前台可见状态
* 即使 App 进入后台或在这个 {@link Activity} 中打开一个之前已经存在的 {@link Activity}, 这时调用此方法
* 还是会返回这个最近启动的 {@link Activity}, 因此基本不会出现 {@code null} 的情况
* 比较适合大部分的使用场景, 如 startActivity
*
* Tips: mActivityList 容器中的顺序仅仅是 Activity 的创建顺序, 并不能保证和 Activity 任务栈顺序一致
*
* @return
*/
@Nullable
public Activity getTopActivity() {
if (mActivityList == null) {
Timber.tag(TAG).w("mActivityList == null when getTopActivity()");
return null;
}
return mActivityList.size() > 0 ? mActivityList.get(mActivityList.size() - 1) : null;
}
/**
* 返回一个存储所有未销毁的 {@link Activity} 的集合
*
* @return
*/
public List getActivityList() {
if (mActivityList == null) {
mActivityList = new LinkedList<>();
}
return mActivityList;
}
/**
* 添加 {@link Activity} 到集合
*/
public void addActivity(Activity activity) {
synchronized (AppManager.class) {
List activities = getActivityList();
if (!activities.contains(activity)) {
activities.add(activity);
}
}
}
/**
* 删除集合里的指定的 {@link Activity} 实例
*
* @param {@link Activity}
*/
public void removeActivity(Activity activity) {
if (mActivityList == null) {
Timber.tag(TAG).w("mActivityList == null when removeActivity(Activity)");
return;
}
synchronized (AppManager.class) {
mActivityList.remove(activity);
}
}
/**
* 删除集合里的指定位置的 {@link Activity}
*
* @param location
*/
public Activity removeActivity(int location) {
if (mActivityList == null) {
Timber.tag(TAG).w("mActivityList == null when removeActivity(int)");
return null;
}
synchronized (AppManager.class) {
if (location > 0 && location < mActivityList.size()) {
return mActivityList.remove(location);
}
}
return null;
}
/**
* 关闭指定的 {@link Activity} class 的所有的实例
*
* @param activityClass
*/
public void killActivity(Class> activityClass) {
if (mActivityList == null) {
Timber.tag(TAG).w("mActivityList == null when killActivity(Class)");
return;
}
synchronized (AppManager.class) {
Iterator iterator = getActivityList().iterator();
while (iterator.hasNext()) {
Activity next = iterator.next();
if (next.getClass().equals(activityClass)) {
iterator.remove();
next.finish();
}
}
}
}
/**
* 指定的 {@link Activity} 实例是否存活
*
* @param {@link Activity}
* @return
*/
public boolean activityInstanceIsLive(Activity activity) {
if (mActivityList == null) {
Timber.tag(TAG).w("mActivityList == null when activityInstanceIsLive(Activity)");
return false;
}
return mActivityList.contains(activity);
}
/**
* 指定的 {@link Activity} class 是否存活(同一个 {@link Activity} class 可能有多个实例)
*
* @param activityClass
* @return
*/
public boolean activityClassIsLive(Class> activityClass) {
if (mActivityList == null) {
Timber.tag(TAG).w("mActivityList == null when activityClassIsLive(Class)");
return false;
}
for (Activity activity : mActivityList) {
if (activity.getClass().equals(activityClass)) {
return true;
}
}
return false;
}
/**
* 获取指定 {@link Activity} class 的实例,没有则返回 null(同一个 {@link Activity} class 有多个实例,则返回最早创建的实例)
*
* @param activityClass
* @return
*/
public Activity findActivity(Class> activityClass) {
if (mActivityList == null) {
Timber.tag(TAG).w("mActivityList == null when findActivity(Class)");
return null;
}
for (Activity activity : mActivityList) {
if (activity.getClass().equals(activityClass)) {
return activity;
}
}
return null;
}
/**
* 关闭所有 {@link Activity}
*/
public void killAll() {
// while (getActivityList().size() != 0) { //此方法只能兼容LinkedList
// getActivityList().remove(0).finish();
// }
synchronized (AppManager.class) {
Iterator iterator = getActivityList().iterator();
while (iterator.hasNext()) {
Activity next = iterator.next();
iterator.remove();
next.finish();
}
}
}
/**
* 关闭所有 {@link Activity},排除指定的 {@link Activity}
*
* @param excludeActivityClasses activity class
*/
public void killAll(Class>... excludeActivityClasses) {
List> excludeList = Arrays.asList(excludeActivityClasses);
synchronized (AppManager.class) {
Iterator iterator = getActivityList().iterator();
while (iterator.hasNext()) {
Activity next = iterator.next();
if (excludeList.contains(next.getClass())) {
continue;
}
iterator.remove();
next.finish();
}
}
}
/**
* 关闭所有 {@link Activity},排除指定的 {@link Activity}
*
* @param excludeActivityName {@link Activity} 的完整全路径
*/
public void killAll(String... excludeActivityName) {
List excludeList = Arrays.asList(excludeActivityName);
synchronized (AppManager.class) {
Iterator iterator = getActivityList().iterator();
while (iterator.hasNext()) {
Activity next = iterator.next();
if (excludeList.contains(next.getClass().getName())) {
continue;
}
iterator.remove();
next.finish();
}
}
}
/**
* 退出应用程序
*
* 此方法经测试在某些机型上并不能完全杀死 App 进程, 几乎试过市面上大部分杀死进程的方式, 但都发现没卵用, 所以此
* 方法如果不能百分之百保证能杀死进程, 就不能贸然调用 {@link #release()} 释放资源, 否则会造成其他问题, 如果您
* 有测试通过的并能适用于绝大多数机型的杀死进程的方式, 望告知
*/
public void appExit() {
try {
killAll();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
} catch (Exception e) {
e.printStackTrace();
}
}
@Deprecated
public interface HandleListener {
void handleMessage(AppManager appManager, Message message);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/ConfigModule.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.jess.arms.base.delegate.AppLifecycles;
import com.jess.arms.di.module.GlobalConfigModule;
import java.util.List;
/**
* ================================================
* {@link ConfigModule} 可以给框架配置一些参数,需要实现 {@link ConfigModule} 后,在 AndroidManifest 中声明该实现类
*
* @see ConfigModule wiki 官方文档
* Created by JessYan on 12/04/2017 11:37
* Contact me
* Follow me
* ================================================
*/
public interface ConfigModule {
/**
* 使用 {@link GlobalConfigModule.Builder} 给框架配置一些配置参数
*
* @param context {@link Context}
* @param builder {@link GlobalConfigModule.Builder}
*/
void applyOptions(@NonNull Context context, @NonNull GlobalConfigModule.Builder builder);
/**
* 使用 {@link AppLifecycles} 在 {@link Application} 的生命周期中注入一些操作
*
* @param context {@link Context}
* @param lifecycles {@link Application} 的生命周期容器, 可向框架中添加多个 {@link Application} 的生命周期类
*/
void injectAppLifecycle(@NonNull Context context, @NonNull List lifecycles);
/**
* 使用 {@link Application.ActivityLifecycleCallbacks} 在 {@link Activity} 的生命周期中注入一些操作
*
* @param context {@link Context}
* @param lifecycles {@link Activity} 的生命周期容器, 可向框架中添加多个 {@link Activity} 的生命周期类
*/
void injectActivityLifecycle(@NonNull Context context, @NonNull List lifecycles);
/**
* 使用 {@link FragmentManager.FragmentLifecycleCallbacks} 在 {@link Fragment} 的生命周期中注入一些操作
*
* @param context {@link Context}
* @param lifecycles {@link Fragment} 的生命周期容器, 可向框架中添加多个 {@link Fragment} 的生命周期类
*/
void injectFragmentLifecycle(@NonNull Context context, @NonNull List lifecycles);
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/EventBusManager.java
================================================
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration;
import org.greenrobot.eventbus.Subscribe;
import java.lang.reflect.Method;
import static com.jess.arms.base.Platform.DEPENDENCY_ANDROID_EVENTBUS;
import static com.jess.arms.base.Platform.DEPENDENCY_EVENTBUS;
/**
* ================================================
* EventBus 的管理类, Arms 核心库并不会依赖某个 EventBus, 如果您想使用 EventBus, 则请在项目中自行依赖对应的 EventBus, 如果不想使用则不依赖
* 支持 greenrobot 的 EventBus 和畅销书 《Android源码设计模式解析与实战》的作者 何红辉 所作的 AndroidEventBus
* 这个类并不能完全做到 EventBus 对外界的零耦合, 只能降低耦合, 因为两个 EventBus 的部分功能使用方法差别太大, 做到完全解耦代价太大
* 允许同时使用两个 EventBus 但不建议这样做, 建议使用 AndroidEventBus, 特别是组件化项目, 原因请看 https://github.com/hehonghui/AndroidEventBus/issues/49
*
* Created by JessYan on 2018/8/1 15:28
* Contact me
* Follow me
* ================================================
*/
public final class EventBusManager {
private static volatile EventBusManager sInstance;
private EventBusManager() {
}
public static EventBusManager getInstance() {
if (sInstance == null) {
synchronized (EventBusManager.class) {
if (sInstance == null) {
sInstance = new EventBusManager();
}
}
}
return sInstance;
}
/**
* 注册订阅者, 允许在项目中同时依赖两个 EventBus, 只要您喜欢
*
* @param subscriber 订阅者
*/
public void register(Object subscriber) {
if (DEPENDENCY_ANDROID_EVENTBUS) {
org.simple.eventbus.EventBus.getDefault().register(subscriber);
}
if (DEPENDENCY_EVENTBUS) {
if (haveAnnotation(subscriber)) {
org.greenrobot.eventbus.EventBus.getDefault().register(subscriber);
}
}
}
/**
* 注销订阅者, 允许在项目中同时依赖两个 EventBus, 只要您喜欢
*
* @param subscriber 订阅者
*/
public void unregister(Object subscriber) {
if (DEPENDENCY_ANDROID_EVENTBUS) {
org.simple.eventbus.EventBus.getDefault().unregister(subscriber);
}
if (DEPENDENCY_EVENTBUS) {
if (haveAnnotation(subscriber)) {
org.greenrobot.eventbus.EventBus.getDefault().unregister(subscriber);
}
}
}
/**
* 发送事件, 如果您在项目中同时依赖了两个 EventBus, 请自己使用想使用的 EventBus 的 Api 发送事件
*
* @param event 事件
*/
public void post(Object event) {
if (DEPENDENCY_ANDROID_EVENTBUS) {
org.simple.eventbus.EventBus.getDefault().post(event);
} else if (DEPENDENCY_EVENTBUS) {
org.greenrobot.eventbus.EventBus.getDefault().post(event);
}
}
/**
* 发送黏性事件, 如果您在项目中同时依赖了两个 EventBus, 请自己使用想使用的 EventBus 的 Api 发送黏性事件
*
* @param event 事件
*/
public void postSticky(Object event) {
if (DEPENDENCY_ANDROID_EVENTBUS) {
org.simple.eventbus.EventBus.getDefault().postSticky(event);
} else if (DEPENDENCY_EVENTBUS) {
org.greenrobot.eventbus.EventBus.getDefault().postSticky(event);
}
}
/**
* 注销黏性事件, 如果您在项目中同时依赖了两个 EventBus, 请自己使用想使用的 EventBus 的 Api 注销黏性事件
*
* @param eventType
* @param
* @return
*/
public T removeStickyEvent(Class eventType) {
if (DEPENDENCY_ANDROID_EVENTBUS) {
org.simple.eventbus.EventBus.getDefault().removeStickyEvent(eventType);
return null;
} else if (DEPENDENCY_EVENTBUS) {
return org.greenrobot.eventbus.EventBus.getDefault().removeStickyEvent(eventType);
}
return null;
}
/**
* 清除订阅者和事件的缓存, 如果您在项目中同时依赖了两个 EventBus, 请自己使用想使用的 EventBus 的 Api 清除订阅者和事件的缓存
*/
public void clear() {
if (DEPENDENCY_ANDROID_EVENTBUS) {
org.simple.eventbus.EventBus.getDefault().clear();
} else if (DEPENDENCY_EVENTBUS) {
org.greenrobot.eventbus.EventBus.clearCaches();
}
}
/**
* {@link org.greenrobot.eventbus.EventBus} 要求注册之前, 订阅者必须含有一个或以上声明 {@link org.greenrobot.eventbus.Subscribe}
* 注解的方法, 否则会报错, 所以如果要想完成在基类中自动注册, 避免报错就要先检查是否符合注册资格
*
* @param subscriber 订阅者
* @return 返回 {@code true} 则表示含有 {@link org.greenrobot.eventbus.Subscribe} 注解, {@code false} 为不含有
*/
private boolean haveAnnotation(Object subscriber) {
boolean skipSuperClasses = false;
Class> clazz = subscriber.getClass();
//查找类中符合注册要求的方法, 直到Object类
while (clazz != null && !isSystemCalss(clazz.getName()) && !skipSuperClasses) {
Method[] allMethods;
try {
allMethods = clazz.getDeclaredMethods();
} catch (Throwable th) {
try {
allMethods = clazz.getMethods();
} catch (Throwable th2) {
continue;
} finally {
skipSuperClasses = true;
}
}
for (Method method : allMethods) {
Class>[] parameterTypes = method.getParameterTypes();
//查看该方法是否含有 Subscribe 注解
if (method.isAnnotationPresent(Subscribe.class) && parameterTypes.length == 1) {
return true;
}
} //end for
//获取父类, 以继续查找父类中符合要求的方法
clazz = clazz.getSuperclass();
}
return false;
}
private boolean isSystemCalss(String name) {
return name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.");
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/FragmentLifecycle.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.jess.arms.base.delegate.FragmentDelegate;
import com.jess.arms.base.delegate.FragmentDelegateImpl;
import com.jess.arms.base.delegate.IFragment;
import com.jess.arms.integration.cache.Cache;
import com.jess.arms.integration.cache.IntelligentCache;
import com.jess.arms.utils.Preconditions;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* ================================================
* {@link FragmentManager.FragmentLifecycleCallbacks} 默认实现类
* 通过 {@link FragmentDelegate} 管理 {@link Fragment}
*
* Created by JessYan on 04/09/2017 16:04
* Contact me
* Follow me
* ================================================
*/
@Singleton
public class FragmentLifecycle extends FragmentManager.FragmentLifecycleCallbacks {
@Inject
public FragmentLifecycle() {
}
@Override
public void onFragmentAttached(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull Context context) {
if (f instanceof IFragment) {
FragmentDelegate fragmentDelegate = fetchFragmentDelegate(f);
if (fragmentDelegate == null || !fragmentDelegate.isAdded()) {
Cache cache = getCacheFromFragment((IFragment) f);
fragmentDelegate = new FragmentDelegateImpl(fm, f);
//使用 IntelligentCache.KEY_KEEP 作为 key 的前缀, 可以使储存的数据永久存储在内存中
//否则存储在 LRU 算法的存储空间中, 前提是 Fragment 使用的是 IntelligentCache (框架默认使用)
cache.put(IntelligentCache.getKeyOfKeep(FragmentDelegate.FRAGMENT_DELEGATE), fragmentDelegate);
}
fragmentDelegate.onAttach(context);
}
}
@Override
public void onFragmentCreated(@NonNull FragmentManager fm, @NonNull Fragment f, Bundle savedInstanceState) {
FragmentDelegate fragmentDelegate = fetchFragmentDelegate(f);
if (fragmentDelegate != null) {
fragmentDelegate.onCreate(savedInstanceState);
}
}
@Override
public void onFragmentViewCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull View v, Bundle savedInstanceState) {
FragmentDelegate fragmentDelegate = fetchFragmentDelegate(f);
if (fragmentDelegate != null) {
fragmentDelegate.onCreateView(v, savedInstanceState);
}
}
@Override
public void onFragmentActivityCreated(@NonNull FragmentManager fm, @NonNull Fragment f, Bundle savedInstanceState) {
FragmentDelegate fragmentDelegate = fetchFragmentDelegate(f);
if (fragmentDelegate != null) {
fragmentDelegate.onActivityCreate(savedInstanceState);
}
}
@Override
public void onFragmentStarted(@NonNull FragmentManager fm, @NonNull Fragment f) {
FragmentDelegate fragmentDelegate = fetchFragmentDelegate(f);
if (fragmentDelegate != null) {
fragmentDelegate.onStart();
}
}
@Override
public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) {
FragmentDelegate fragmentDelegate = fetchFragmentDelegate(f);
if (fragmentDelegate != null) {
fragmentDelegate.onResume();
}
}
@Override
public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) {
FragmentDelegate fragmentDelegate = fetchFragmentDelegate(f);
if (fragmentDelegate != null) {
fragmentDelegate.onPause();
}
}
@Override
public void onFragmentStopped(@NonNull FragmentManager fm, @NonNull Fragment f) {
FragmentDelegate fragmentDelegate = fetchFragmentDelegate(f);
if (fragmentDelegate != null) {
fragmentDelegate.onStop();
}
}
@Override
public void onFragmentSaveInstanceState(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull Bundle outState) {
FragmentDelegate fragmentDelegate = fetchFragmentDelegate(f);
if (fragmentDelegate != null) {
fragmentDelegate.onSaveInstanceState(outState);
}
}
@Override
public void onFragmentViewDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {
FragmentDelegate fragmentDelegate = fetchFragmentDelegate(f);
if (fragmentDelegate != null) {
fragmentDelegate.onDestroyView();
}
}
@Override
public void onFragmentDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {
FragmentDelegate fragmentDelegate = fetchFragmentDelegate(f);
if (fragmentDelegate != null) {
fragmentDelegate.onDestroy();
}
}
@Override
public void onFragmentDetached(@NonNull FragmentManager fm, @NonNull Fragment f) {
FragmentDelegate fragmentDelegate = fetchFragmentDelegate(f);
if (fragmentDelegate != null) {
fragmentDelegate.onDetach();
}
}
private FragmentDelegate fetchFragmentDelegate(Fragment fragment) {
if (fragment instanceof IFragment) {
Cache cache = getCacheFromFragment((IFragment) fragment);
return (FragmentDelegate) cache.get(IntelligentCache.getKeyOfKeep(FragmentDelegate.FRAGMENT_DELEGATE));
}
return null;
}
@NonNull
private Cache getCacheFromFragment(IFragment fragment) {
Cache cache = fragment.provideCache();
Preconditions.checkNotNull(cache, "%s cannot be null on Fragment", Cache.class.getName());
return cache;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/IRepositoryManager.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.jess.arms.mvp.IModel;
import retrofit2.Retrofit;
/**
* ================================================
* 用来管理网络请求层,以及数据缓存层,以后可能添加数据库请求层
* 提供给 {@link IModel} 必要的 Api 做数据处理
*
* @see RepositoryManager wiki 官方文档
* Created by JessYan on 17/03/2017 11:15
* Contact me
* Follow me
* ================================================
*/
public interface IRepositoryManager {
/**
* 根据传入的 Class 获取对应的 Retrofit service
*
* @param service Retrofit service class
* @param Retrofit service 类型
* @return Retrofit service
*/
@NonNull
T obtainRetrofitService(@NonNull Class service);
/**
* 根据传入的 Class 获取对应的 RxCache service
*
* @param cache RxCache service class
* @param RxCache service 类型
* @return RxCache service
*/
@NonNull
T obtainCacheService(@NonNull Class cache);
/**
* 清理所有缓存
*/
void clearAllCache();
/**
* 获取 {@link Context}
*
* @return {@link Context}
*/
@NonNull
Context getContext();
interface ObtainServiceDelegate {
@Nullable
T createRetrofitService(Retrofit retrofit, Class serviceClass);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/ManifestParser.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import java.util.ArrayList;
import java.util.List;
/**
* ================================================
* 用于解析 AndroidManifest 中的 Meta 属性
* 配合 {@link ConfigModule} 使用
*
* Created by JessYan on 12/04/2017 14:41
* Contact me
* Follow me
* ================================================
*/
public final class ManifestParser {
private static final String MODULE_VALUE = "ConfigModule";
private final Context context;
public ManifestParser(Context context) {
this.context = context;
}
private static ConfigModule parseModule(String className) {
Class> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unable to find ConfigModule implementation", e);
}
Object module;
try {
module = clazz.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Unable to instantiate ConfigModule implementation for " + clazz, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to instantiate ConfigModule implementation for " + clazz, e);
}
if (!(module instanceof ConfigModule)) {
throw new RuntimeException("Expected instanceof ConfigModule, but found: " + module);
}
return (ConfigModule) module;
}
public List parse() {
List modules = new ArrayList<>();
try {
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
context.getPackageName(), PackageManager.GET_META_DATA);
if (appInfo.metaData != null) {
for (String key : appInfo.metaData.keySet()) {
if (MODULE_VALUE.equals(appInfo.metaData.get(key))) {
modules.add(parseModule(key));
}
}
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException("Unable to find metadata to parse ConfigModule", e);
}
return modules;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/RepositoryManager.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.jess.arms.integration.cache.Cache;
import com.jess.arms.integration.cache.CacheType;
import com.jess.arms.mvp.IModel;
import com.jess.arms.utils.Preconditions;
import java.lang.reflect.Proxy;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Lazy;
import io.rx_cache2.internal.RxCache;
import retrofit2.Retrofit;
/**
* ================================================
* 用来管理网络请求层,以及数据缓存层,以后可能添加数据库请求层
* 提供给 {@link IModel} 层必要的 Api 做数据处理
*
* @see RepositoryManager wiki 官方文档
* Created by JessYan on 13/04/2017 09:52
* Contact me
* Follow me
* ================================================
*/
@SuppressWarnings("unchecked")
@Singleton
public class RepositoryManager implements IRepositoryManager {
@Inject
Lazy mRetrofit;
@Inject
Lazy mRxCache;
@Inject
Application mApplication;
@Inject
Cache.Factory mCacheFactory;
@Inject
@Nullable
ObtainServiceDelegate mObtainServiceDelegate;
private Cache mRetrofitServiceCache;
private Cache mCacheServiceCache;
@Inject
public RepositoryManager() {
}
/**
* 根据传入的 Class 获取对应的 Retrofit service
*
* @param serviceClass ApiService class
* @param ApiService class
* @return ApiService
*/
@NonNull
@Override
public synchronized T obtainRetrofitService(@NonNull Class serviceClass) {
if (mRetrofitServiceCache == null) {
mRetrofitServiceCache = mCacheFactory.build(CacheType.RETROFIT_SERVICE_CACHE);
}
Preconditions.checkNotNull(mRetrofitServiceCache,
"Cannot return null from a Cache.Factory#build(int) method");
T retrofitService = (T) mRetrofitServiceCache.get(serviceClass.getCanonicalName());
if (retrofitService == null) {
if (mObtainServiceDelegate != null) {
retrofitService = mObtainServiceDelegate.createRetrofitService(
mRetrofit.get(), serviceClass);
}
if (retrofitService == null) {
retrofitService = (T) Proxy.newProxyInstance(
serviceClass.getClassLoader(),
new Class[]{serviceClass},
new RetrofitServiceProxyHandler(mRetrofit.get(), serviceClass));
}
mRetrofitServiceCache.put(serviceClass.getCanonicalName(), retrofitService);
}
return retrofitService;
}
/**
* 根据传入的 Class 获取对应的 RxCache service
*
* @param cacheClass Cache class
* @param Cache class
* @return Cache
*/
@NonNull
@Override
public synchronized T obtainCacheService(@NonNull Class cacheClass) {
Preconditions.checkNotNull(cacheClass, "cacheClass == null");
if (mCacheServiceCache == null) {
mCacheServiceCache = mCacheFactory.build(CacheType.CACHE_SERVICE_CACHE);
}
Preconditions.checkNotNull(mCacheServiceCache,
"Cannot return null from a Cache.Factory#build(int) method");
T cacheService = (T) mCacheServiceCache.get(cacheClass.getCanonicalName());
if (cacheService == null) {
cacheService = mRxCache.get().using(cacheClass);
mCacheServiceCache.put(cacheClass.getCanonicalName(), cacheService);
}
return cacheService;
}
/**
* 清理所有缓存
*/
@Override
public void clearAllCache() {
mRxCache.get().evictAll().subscribe();
}
@NonNull
@Override
public Context getContext() {
return mApplication;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/RetrofitServiceProxyHandler.java
================================================
package com.jess.arms.integration;
import androidx.annotation.Nullable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import io.reactivex.Observable;
import io.reactivex.Single;
import retrofit2.Retrofit;
public class RetrofitServiceProxyHandler implements InvocationHandler {
private Retrofit mRetrofit;
private Class> mServiceClass;
private Object mRetrofitService;
public RetrofitServiceProxyHandler(Retrofit retrofit, Class> serviceClass) {
mRetrofit = retrofit;
mServiceClass = serviceClass;
}
@Override
public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
// 根据 https://zhuanlan.zhihu.com/p/40097338 对 Retrofit 进行的优化
if (method.getReturnType() == Observable.class) {
// 如果方法返回值是 Observable 的话,则包一层再返回,
// 只包一层 defer 由外部去控制耗时方法以及网络请求所处线程,
// 如此对原项目的影响为 0,且更可控。
return Observable.defer(() -> {
// 执行真正的 Retrofit 动态代理的方法
return (Observable) method.invoke(getRetrofitService(), args);
});
} else if (method.getReturnType() == Single.class) {
// 如果方法返回值是 Single 的话,则包一层再返回。
return Single.defer(() -> {
// 执行真正的 Retrofit 动态代理的方法
return (Single) method.invoke(getRetrofitService(), args);
});
}
// 返回值不是 Observable 或 Single 的话不处理。
return method.invoke(getRetrofitService(), args);
}
private Object getRetrofitService() {
if (mRetrofitService == null) {
mRetrofitService = mRetrofit.create(mServiceClass);
}
return mRetrofitService;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/cache/Cache.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration.cache;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.jess.arms.di.module.GlobalConfigModule;
import java.util.Set;
/**
* ================================================
* 用于缓存框架中所必需的组件,开发者可通过 {@link GlobalConfigModule.Builder#cacheFactory(Factory)} 为框架提供缓存策略
* 开发者也可以用于自己日常中的使用
*
* @see GlobalConfigModule#provideCacheFactory(Application)
* @see LruCache
* Created by JessYan on 25/09/2017 16:36
* Contact me
* Follow me
* ================================================
*/
public interface Cache {
/**
* 返回当前缓存已占用的总 size
*
* @return {@code size}
*/
int size();
/**
* 返回当前缓存所能允许的最大 size
*
* @return {@code maxSize}
*/
int getMaxSize();
/**
* 返回这个 {@code key} 在缓存中对应的 {@code value}, 如果返回 {@code null} 说明这个 {@code key} 没有对应的 {@code value}
*
* @param key {@code key}
* @return {@code value}
*/
@Nullable
V get(K key);
/**
* 将 {@code key} 和 {@code value} 以条目的形式加入缓存,如果这个 {@code key} 在缓存中已经有对应的 {@code value}
* 则此 {@code value} 被新的 {@code value} 替换并返回,如果为 {@code null} 说明是一个新条目
*
* @param key {@code key}
* @param value {@code value}
* @return 如果这个 {@code key} 在容器中已经储存有 {@code value}, 则返回之前的 {@code value} 否则返回 {@code null}
*/
@Nullable
V put(K key, V value);
/**
* 移除缓存中这个 {@code key} 所对应的条目,并返回所移除条目的 value
* 如果返回为 {@code null} 则有可能时因为这个 {@code key} 对应的 value 为 {@code null} 或条目不存在
*
* @param key {@code key}
* @return 如果这个 {@code key} 在容器中已经储存有 {@code value} 并且删除成功则返回删除的 {@code value}, 否则返回 {@code null}
*/
@Nullable
V remove(K key);
/**
* 如果这个 {@code key} 在缓存中有对应的 value 并且不为 {@code null}, 则返回 {@code true}
*
* @param key {@code key}
* @return {@code true} 为在容器中含有这个 {@code key}, 否则为 {@code false}
*/
boolean containsKey(K key);
/**
* 返回当前缓存中含有的所有 {@code key}
*
* @return {@code keySet}
*/
Set keySet();
/**
* 清除缓存中所有的内容
*/
void clear();
interface Factory {
/**
* Returns a new cache
*
* @param type 框架中需要缓存的模块类型
* @return {@link Cache}
*/
@NonNull
Cache build(CacheType type);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/cache/CacheType.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration.cache;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import androidx.fragment.app.Fragment;
import com.jess.arms.di.component.AppComponent;
import com.jess.arms.integration.RepositoryManager;
/**
* ================================================
* 构建 {@link Cache} 时,使用 {@link CacheType} 中声明的类型,来区分不同的模块
* 从而为不同的模块构建不同的缓存策略
*
* @see Cache.Factory#build(CacheType)
* Created by JessYan on 25/09/2017 18:05
* Contact me
* Follow me
* ================================================
*/
public interface CacheType {
int RETROFIT_SERVICE_CACHE_TYPE_ID = 0;
int CACHE_SERVICE_CACHE_TYPE_ID = 1;
int EXTRAS_TYPE_ID = 2;
int ACTIVITY_CACHE_TYPE_ID = 3;
int FRAGMENT_CACHE_TYPE_ID = 4;
/**
* {@link RepositoryManager}中存储 Retrofit Service 的容器
*/
CacheType RETROFIT_SERVICE_CACHE = new CacheType() {
private static final int MAX_SIZE = 150;
private static final float MAX_SIZE_MULTIPLIER = 0.002f;
@Override
public int getCacheTypeId() {
return RETROFIT_SERVICE_CACHE_TYPE_ID;
}
@Override
public int calculateCacheSize(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int targetMemoryCacheSize = (int) (activityManager.getMemoryClass() * MAX_SIZE_MULTIPLIER * 1024);
if (targetMemoryCacheSize >= MAX_SIZE) {
return MAX_SIZE;
}
return targetMemoryCacheSize;
}
};
/**
* {@link RepositoryManager} 中储存 Cache Service 的容器
*/
CacheType CACHE_SERVICE_CACHE = new CacheType() {
private static final int MAX_SIZE = 150;
private static final float MAX_SIZE_MULTIPLIER = 0.002f;
@Override
public int getCacheTypeId() {
return CACHE_SERVICE_CACHE_TYPE_ID;
}
@Override
public int calculateCacheSize(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int targetMemoryCacheSize = (int) (activityManager.getMemoryClass() * MAX_SIZE_MULTIPLIER * 1024);
if (targetMemoryCacheSize >= MAX_SIZE) {
return MAX_SIZE;
}
return targetMemoryCacheSize;
}
};
/**
* {@link AppComponent} 中的 extras
*/
CacheType EXTRAS = new CacheType() {
private static final int MAX_SIZE = 500;
private static final float MAX_SIZE_MULTIPLIER = 0.005f;
@Override
public int getCacheTypeId() {
return EXTRAS_TYPE_ID;
}
@Override
public int calculateCacheSize(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int targetMemoryCacheSize = (int) (activityManager.getMemoryClass() * MAX_SIZE_MULTIPLIER * 1024);
if (targetMemoryCacheSize >= MAX_SIZE) {
return MAX_SIZE;
}
return targetMemoryCacheSize;
}
};
/**
* {@link Activity} 中存储数据的容器
*/
CacheType ACTIVITY_CACHE = new CacheType() {
private static final int MAX_SIZE = 80;
private static final float MAX_SIZE_MULTIPLIER = 0.0008f;
@Override
public int getCacheTypeId() {
return ACTIVITY_CACHE_TYPE_ID;
}
@Override
public int calculateCacheSize(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int targetMemoryCacheSize = (int) (activityManager.getMemoryClass() * MAX_SIZE_MULTIPLIER * 1024);
if (targetMemoryCacheSize >= MAX_SIZE) {
return MAX_SIZE;
}
return targetMemoryCacheSize;
}
};
/**
* {@link Fragment} 中存储数据的容器
*/
CacheType FRAGMENT_CACHE = new CacheType() {
private static final int MAX_SIZE = 80;
private static final float MAX_SIZE_MULTIPLIER = 0.0008f;
@Override
public int getCacheTypeId() {
return FRAGMENT_CACHE_TYPE_ID;
}
@Override
public int calculateCacheSize(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int targetMemoryCacheSize = (int) (activityManager.getMemoryClass() * MAX_SIZE_MULTIPLIER * 1024);
if (targetMemoryCacheSize >= MAX_SIZE) {
return MAX_SIZE;
}
return targetMemoryCacheSize;
}
};
/**
* 返回框架内需要缓存的模块对应的 {@code id}
*
* @return
*/
int getCacheTypeId();
/**
* 计算对应模块需要的缓存大小
*
* @return
*/
int calculateCacheSize(Context context);
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/cache/IntelligentCache.java
================================================
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration.cache;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.jess.arms.utils.Preconditions;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* ================================================
* {@link IntelligentCache} 含有可将数据永久存储至内存中的存储容器 {@link #mMap}, 和当达到最大容量时可根据 LRU
* 算法抛弃不合规数据的存储容器 {@link #mCache}
*
* {@link IntelligentCache} 可根据您传入的 {@code key} 智能的判断您需要将数据存储至哪个存储容器, 从而针对数据
* 的不同特性进行不同的存储优化
*
* 调用 {@link IntelligentCache#put(Object, Object)} 方法, 使用 {@link #KEY_KEEP} + {@code key} 作为 key 传入的
* {@code value} 可存储至 {@link #mMap} (数据永久存储至内存中, 适合比较重要的数据) 中, 否则储存至 {@link #mCache}
*
* Created by JessYan on 12/04/2018 16:06
* Contact me
* Follow me
* ================================================
*/
public class IntelligentCache implements Cache {
public static final String KEY_KEEP = "Keep=";
private final Map mMap;//可将数据永久存储至内存中的存储容器
private final Cache mCache;//当达到最大容量时可根据 LRU 算法抛弃不合规数据的存储容器
public IntelligentCache(int size) {
this.mMap = new HashMap<>();
this.mCache = new LruCache<>(size);
}
/**
* 使用此方法返回的值作为 key, 可以将数据永久存储至内存中
*
* @param key {@code key}
* @return Keep= + {@code key}
*/
@NonNull
public static String getKeyOfKeep(@NonNull String key) {
Preconditions.checkNotNull(key, "key == null");
return IntelligentCache.KEY_KEEP + key;
}
/**
* 将 {@link #mMap} 和 {@link #mCache} 的 {@code size} 相加后返回
*
* @return 相加后的 {@code size}
*/
@Override
public synchronized int size() {
return mMap.size() + mCache.size();
}
/**
* 将 {@link #mMap} 和 {@link #mCache} 的 {@code maxSize} 相加后返回
*
* @return 相加后的 {@code maxSize}
*/
@Override
public synchronized int getMaxSize() {
return mMap.size() + mCache.getMaxSize();
}
/**
* 如果在 {@code key} 中使用 {@link #KEY_KEEP} 作为其前缀, 则操作 {@link #mMap}, 否则操作 {@link #mCache}
*
* @param key {@code key}
* @return {@code value}
*/
@Nullable
@Override
public synchronized V get(String key) {
if (key.startsWith(KEY_KEEP)) {
return mMap.get(key);
}
return mCache.get(key);
}
/**
* 如果在 {@code key} 中使用 {@link #KEY_KEEP} 作为其前缀, 则操作 {@link #mMap}, 否则操作 {@link #mCache}
*
* @param key {@code key}
* @param value {@code value}
* @return 如果这个 {@code key} 在容器中已经储存有 {@code value}, 则返回之前的 {@code value} 否则返回 {@code null}
*/
@Nullable
@Override
public synchronized V put(String key, V value) {
if (key.startsWith(KEY_KEEP)) {
return mMap.put(key, value);
}
return mCache.put(key, value);
}
/**
* 如果在 {@code key} 中使用 {@link #KEY_KEEP} 作为其前缀, 则操作 {@link #mMap}, 否则操作 {@link #mCache}
*
* @param key {@code key}
* @return 如果这个 {@code key} 在容器中已经储存有 {@code value} 并且删除成功则返回删除的 {@code value}, 否则返回 {@code null}
*/
@Nullable
@Override
public synchronized V remove(String key) {
if (key.startsWith(KEY_KEEP)) {
return mMap.remove(key);
}
return mCache.remove(key);
}
/**
* 如果在 {@code key} 中使用 {@link #KEY_KEEP} 作为其前缀, 则操作 {@link #mMap}, 否则操作 {@link #mCache}
*
* @param key {@code key}
* @return {@code true} 为在容器中含有这个 {@code key}, 否则为 {@code false}
*/
@Override
public synchronized boolean containsKey(String key) {
if (key.startsWith(KEY_KEEP)) {
return mMap.containsKey(key);
}
return mCache.containsKey(key);
}
/**
* 将 {@link #mMap} 和 {@link #mCache} 的 {@code keySet} 合并返回
*
* @return 合并后的 {@code keySet}
*/
@Override
public synchronized Set keySet() {
Set set = mCache.keySet();
set.addAll(mMap.keySet());
return set;
}
/**
* 清空 {@link #mMap} 和 {@link #mCache} 容器
*/
@Override
public void clear() {
mCache.clear();
mMap.clear();
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/cache/LruCache.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration.cache;
import android.app.Application;
import androidx.annotation.Nullable;
import com.jess.arms.di.module.GlobalConfigModule;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* ================================================
* LRU 即 Least Recently Used,最近最少使用,也就是说,当缓存满了,会优先淘汰那些最近最不常访问的数据
* 此种缓存策略为框架默认提供,可自行实现其他缓存策略,如磁盘缓存,为框架或开发者提供缓存的功能
*
* @see GlobalConfigModule#provideCacheFactory(Application)
* @see Cache
* Created by JessYan on 25/09/2017 16:57
* Contact me
* Follow me
* ================================================
*/
public class LruCache implements Cache {
private final LinkedHashMap cache = new LinkedHashMap<>(100, 0.75f, true);
private final int initialMaxSize;
private int maxSize;
private int currentSize = 0;
/**
* Constructor for LruCache.
*
* @param size 这个缓存的最大 size,这个 size 所使用的单位必须和 {@link #getItemSize(Object)} 所使用的单位一致.
*/
public LruCache(int size) {
this.initialMaxSize = size;
this.maxSize = size;
}
/**
* 设置一个系数应用于当时构造函数中所传入的 size, 从而得到一个新的 {@link #maxSize}
* 并会立即调用 {@link #evict} 开始清除满足条件的条目
*
* @param multiplier 系数
*/
public synchronized void setSizeMultiplier(float multiplier) {
if (multiplier < 0) {
throw new IllegalArgumentException("Multiplier must be >= 0");
}
maxSize = Math.round(initialMaxSize * multiplier);
evict();
}
/**
* 返回每个 {@code item} 所占用的 size,默认为1,这个 size 的单位必须和构造函数所传入的 size 一致
* 子类可以重写这个方法以适应不同的单位,比如说 bytes
*
* @param item 每个 {@code item} 所占用的 size
* @return 单个 item 的 {@code size}
*/
protected int getItemSize(V item) {
return 1;
}
/**
* 当缓存中有被驱逐的条目时,会回调此方法,默认空实现,子类可以重写这个方法
*
* @param key 被驱逐条目的 {@code key}
* @param value 被驱逐条目的 {@code value}
*/
protected void onItemEvicted(K key, V value) {
// optional override
}
/**
* 返回当前缓存所能允许的最大 size
*
* @return {@code maxSize}
*/
@Override
public synchronized int getMaxSize() {
return maxSize;
}
/**
* 返回当前缓存已占用的总 size
*
* @return {@code size}
*/
@Override
public synchronized int size() {
return currentSize;
}
/**
* 如果这个 {@code key} 在缓存中有对应的 {@code value} 并且不为 {@code null},则返回 true
*
* @param key 用来映射的 {@code key}
* @return {@code true} 为在容器中含有这个 {@code key}, 否则为 {@code false}
*/
@Override
public synchronized boolean containsKey(K key) {
return cache.containsKey(key);
}
/**
* 返回当前缓存中含有的所有 {@code key}
*
* @return {@code keySet}
*/
@Override
public synchronized Set keySet() {
return cache.keySet();
}
/**
* 返回这个 {@code key} 在缓存中对应的 {@code value}, 如果返回 {@code null} 说明这个 {@code key} 没有对应的 {@code value}
*
* @param key 用来映射的 {@code key}
* @return {@code value}
*/
@Override
@Nullable
public synchronized V get(K key) {
return cache.get(key);
}
/**
* 将 {@code key} 和 {@code value} 以条目的形式加入缓存,如果这个 {@code key} 在缓存中已经有对应的 {@code value}
* 则此 {@code value} 被新的 {@code value} 替换并返回,如果为 {@code null} 说明是一个新条目
*
* 如果 {@link #getItemSize} 返回的 size 大于或等于缓存所能允许的最大 size, 则不能向缓存中添加此条目
* 此时会回调 {@link #onItemEvicted(Object, Object)} 通知此方法当前被驱逐的条目
*
* @param key 通过这个 {@code key} 添加条目
* @param value 需要添加的 {@code value}
* @return 如果这个 {@code key} 在容器中已经储存有 {@code value}, 则返回之前的 {@code value} 否则返回 {@code null}
*/
@Override
@Nullable
public synchronized V put(K key, V value) {
final int itemSize = getItemSize(value);
if (itemSize >= maxSize) {
onItemEvicted(key, value);
return null;
}
final V result = cache.put(key, value);
if (value != null) {
currentSize += getItemSize(value);
}
if (result != null) {
currentSize -= getItemSize(result);
}
evict();
return result;
}
/**
* 移除缓存中这个 {@code key} 所对应的条目,并返回所移除条目的 {@code value}
* 如果返回为 {@code null} 则有可能时因为这个 {@code key} 对应的 {@code value} 为 {@code null} 或条目不存在
*
* @param key 使用这个 {@code key} 移除对应的条目
* @return 如果这个 {@code key} 在容器中已经储存有 {@code value} 并且删除成功则返回删除的 {@code value}, 否则返回 {@code null}
*/
@Override
@Nullable
public synchronized V remove(K key) {
final V value = cache.remove(key);
if (value != null) {
currentSize -= getItemSize(value);
}
return value;
}
/**
* 清除缓存中所有的内容
*/
@Override
public void clear() {
trimToSize(0);
}
/**
* 当指定的 size 小于当前缓存已占用的总 size 时,会开始清除缓存中最近最少使用的条目
*
* @param size {@code size}
*/
protected synchronized void trimToSize(int size) {
Map.Entry last;
while (currentSize > size) {
last = cache.entrySet().iterator().next();
final V toRemove = last.getValue();
currentSize -= getItemSize(toRemove);
final K key = last.getKey();
cache.remove(key);
onItemEvicted(key, toRemove);
}
}
/**
* 当缓存中已占用的总 size 大于所能允许的最大 size ,会使用 {@link #trimToSize(int)} 开始清除满足条件的条目
*/
private void evict() {
trimToSize(maxSize);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/lifecycle/ActivityLifecycleForRxLifecycle.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration.lifecycle;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import androidx.fragment.app.FragmentActivity;
import com.trello.rxlifecycle2.RxLifecycle;
import com.trello.rxlifecycle2.android.ActivityEvent;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Lazy;
import io.reactivex.subjects.Subject;
/**
* ================================================
* 配合 {@link ActivityLifecycleable} 使用,使 {@link Activity} 具有 {@link RxLifecycle} 的特性
*
* Created by JessYan on 25/08/2017 18:56
* Contact me
* Follow me
* ================================================
*/
@Singleton
public class ActivityLifecycleForRxLifecycle implements Application.ActivityLifecycleCallbacks {
@Inject
Lazy mFragmentLifecycle;
@Inject
public ActivityLifecycleForRxLifecycle() {
}
/**
* 通过桥梁对象 {@code BehaviorSubject mLifecycleSubject}
* 在每个 Activity 的生命周期中发出对应的生命周期事件
*/
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (activity instanceof ActivityLifecycleable) {
obtainSubject(activity).onNext(ActivityEvent.CREATE);
if (activity instanceof FragmentActivity) {
((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycle.get(), true);
}
}
}
@Override
public void onActivityStarted(Activity activity) {
if (activity instanceof ActivityLifecycleable) {
obtainSubject(activity).onNext(ActivityEvent.START);
}
}
@Override
public void onActivityResumed(Activity activity) {
if (activity instanceof ActivityLifecycleable) {
obtainSubject(activity).onNext(ActivityEvent.RESUME);
}
}
@Override
public void onActivityPaused(Activity activity) {
if (activity instanceof ActivityLifecycleable) {
obtainSubject(activity).onNext(ActivityEvent.PAUSE);
}
}
@Override
public void onActivityStopped(Activity activity) {
if (activity instanceof ActivityLifecycleable) {
obtainSubject(activity).onNext(ActivityEvent.STOP);
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
if (activity instanceof ActivityLifecycleable) {
obtainSubject(activity).onNext(ActivityEvent.DESTROY);
}
}
/**
* 从 {@link com.jess.arms.base.BaseActivity} 中获得桥梁对象 {@code BehaviorSubject mLifecycleSubject}
*
* @see BehaviorSubject 官方中文文档
*/
private Subject obtainSubject(Activity activity) {
return ((ActivityLifecycleable) activity).provideLifecycleSubject();
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/lifecycle/ActivityLifecycleable.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration.lifecycle;
import android.app.Activity;
import com.trello.rxlifecycle2.RxLifecycle;
import com.trello.rxlifecycle2.android.ActivityEvent;
/**
* ================================================
* 让 {@link Activity} 实现此接口,即可正常使用 {@link RxLifecycle}
*
* Created by JessYan on 26/08/2017 17:14
* Contact me
* Follow me
* ================================================
*/
public interface ActivityLifecycleable extends Lifecycleable {
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/lifecycle/FragmentLifecycleForRxLifecycle.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration.lifecycle;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.trello.rxlifecycle2.RxLifecycle;
import com.trello.rxlifecycle2.android.FragmentEvent;
import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.subjects.Subject;
/**
* ================================================
* 配合 {@link FragmentLifecycleable} 使用,使 {@link Fragment} 具有 {@link RxLifecycle} 的特性
*
* Created by JessYan on 26/08/2017 16:02
* Contact me
* Follow me
* ================================================
*/
@Singleton
public class FragmentLifecycleForRxLifecycle extends FragmentManager.FragmentLifecycleCallbacks {
@Inject
public FragmentLifecycleForRxLifecycle() {
}
@Override
public void onFragmentAttached(@NotNull @NonNull FragmentManager fm, @NotNull @NonNull Fragment f, @NotNull @NonNull Context context) {
if (f instanceof FragmentLifecycleable) {
obtainSubject(f).onNext(FragmentEvent.ATTACH);
}
}
@Override
public void onFragmentCreated(@NotNull @NonNull FragmentManager fm, @NotNull @NonNull Fragment f, Bundle savedInstanceState) {
if (f instanceof FragmentLifecycleable) {
obtainSubject(f).onNext(FragmentEvent.CREATE);
}
}
@Override
public void onFragmentViewCreated(@NotNull @NonNull FragmentManager fm, @NotNull @NonNull Fragment f, @NotNull @NonNull View v, Bundle savedInstanceState) {
if (f instanceof FragmentLifecycleable) {
obtainSubject(f).onNext(FragmentEvent.CREATE_VIEW);
}
}
@Override
public void onFragmentStarted(@NotNull FragmentManager fm, @NotNull Fragment f) {
if (f instanceof FragmentLifecycleable) {
obtainSubject(f).onNext(FragmentEvent.START);
}
}
@Override
public void onFragmentResumed(@NotNull FragmentManager fm, @NotNull Fragment f) {
if (f instanceof FragmentLifecycleable) {
obtainSubject(f).onNext(FragmentEvent.RESUME);
}
}
@Override
public void onFragmentPaused(@NotNull FragmentManager fm, @NotNull Fragment f) {
if (f instanceof FragmentLifecycleable) {
obtainSubject(f).onNext(FragmentEvent.PAUSE);
}
}
@Override
public void onFragmentStopped(@NotNull FragmentManager fm, @NotNull Fragment f) {
if (f instanceof FragmentLifecycleable) {
obtainSubject(f).onNext(FragmentEvent.STOP);
}
}
@Override
public void onFragmentViewDestroyed(@NotNull FragmentManager fm, @NotNull Fragment f) {
if (f instanceof FragmentLifecycleable) {
obtainSubject(f).onNext(FragmentEvent.DESTROY_VIEW);
}
}
@Override
public void onFragmentDestroyed(@NotNull FragmentManager fm, @NotNull Fragment f) {
if (f instanceof FragmentLifecycleable) {
obtainSubject(f).onNext(FragmentEvent.DESTROY);
}
}
@Override
public void onFragmentDetached(@NotNull FragmentManager fm, @NotNull Fragment f) {
if (f instanceof FragmentLifecycleable) {
obtainSubject(f).onNext(FragmentEvent.DETACH);
}
}
private Subject obtainSubject(Fragment fragment) {
return ((FragmentLifecycleable) fragment).provideLifecycleSubject();
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/lifecycle/FragmentLifecycleable.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration.lifecycle;
import androidx.fragment.app.Fragment;
import com.trello.rxlifecycle2.RxLifecycle;
import com.trello.rxlifecycle2.android.FragmentEvent;
/**
* ================================================
* 让 {@link Fragment} 实现此接口,即可正常使用 {@link RxLifecycle}
*
* Created by JessYan on 26/08/2017 17:14
* Contact me
* Follow me
* ================================================
*/
public interface FragmentLifecycleable extends Lifecycleable {
}
================================================
FILE: arms/src/main/java/com/jess/arms/integration/lifecycle/Lifecycleable.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.integration.lifecycle;
import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.jess.arms.utils.RxLifecycleUtils;
import com.trello.rxlifecycle2.RxLifecycle;
import io.reactivex.subjects.Subject;
/**
* ================================================
* 让 {@link Activity}/{@link Fragment} 实现此接口,即可正常使用 {@link RxLifecycle}
* 无需再继承 {@link RxLifecycle} 提供的 Activity/Fragment ,扩展性极强
*
* @see RxLifecycleUtils 详细用法请查看此类
* Created by JessYan on 25/08/2017 18:39
* Contact me
* Follow me
* ================================================
*/
public interface Lifecycleable {
@NonNull
Subject provideLifecycleSubject();
}
================================================
FILE: arms/src/main/java/com/jess/arms/mvp/BaseModel.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.mvp;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import com.jess.arms.integration.IRepositoryManager;
/**
* ================================================
* 基类 Model
*
* @see Model wiki 官方文档
* Created by JessYan on 08/05/2016 12:55
* Contact me
* Follow me
* ================================================
*/
public class BaseModel implements IModel, LifecycleObserver {
protected IRepositoryManager mRepositoryManager;//用于管理网络请求层, 以及数据缓存层
public BaseModel(IRepositoryManager repositoryManager) {
this.mRepositoryManager = repositoryManager;
}
/**
* 在框架中 {@link BasePresenter#onDestroy()} 时会默认调用 {@link IModel#onDestroy()}
*/
@Override
public void onDestroy() {
mRepositoryManager = null;
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
void onDestroy(LifecycleOwner owner) {
owner.getLifecycle().removeObserver(this);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/mvp/BasePresenter.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.mvp;
import android.app.Activity;
import android.app.Service;
import android.view.View;
import androidx.core.app.ComponentActivity;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import com.jess.arms.integration.EventBusManager;
import com.jess.arms.utils.Preconditions;
import com.trello.rxlifecycle2.RxLifecycle;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Action;
/**
* ================================================
* 基类 Presenter
*
* @see Presenter wiki 官方文档
* Created by JessYan on 4/28/2016
* Contact me
* Follow me
* ================================================
*/
public class BasePresenter implements IPresenter, LifecycleObserver {
protected final String TAG = this.getClass().getSimpleName();
protected CompositeDisposable mCompositeDisposable;
protected M mModel;
protected V mRootView;
/**
* 如果当前页面同时需要 Model 层和 View 层,则使用此构造函数(默认)
*
* @param model
* @param rootView
*/
public BasePresenter(M model, V rootView) {
Preconditions.checkNotNull(model, "%s cannot be null", IModel.class.getName());
Preconditions.checkNotNull(rootView, "%s cannot be null", IView.class.getName());
this.mModel = model;
this.mRootView = rootView;
onStart();
}
/**
* 如果当前页面不需要操作数据,只需要 View 层,则使用此构造函数
*
* @param rootView
*/
public BasePresenter(V rootView) {
Preconditions.checkNotNull(rootView, "%s cannot be null", IView.class.getName());
this.mRootView = rootView;
onStart();
}
public BasePresenter() {
onStart();
}
@Override
public void onStart() {
//将 LifecycleObserver 注册给 LifecycleOwner 后 @OnLifecycleEvent 才可以正常使用
if (mRootView != null && mRootView instanceof LifecycleOwner) {
((LifecycleOwner) mRootView).getLifecycle().addObserver(this);
if (mModel != null && mModel instanceof LifecycleObserver) {
((LifecycleOwner) mRootView).getLifecycle().addObserver((LifecycleObserver) mModel);
}
}
if (useEventBus())//如果要使用 EventBus 请将此方法返回 true
{
EventBusManager.getInstance().register(this);//注册 EventBus
}
}
/**
* 在框架中 {@link Activity#onDestroy()} 时会默认调用 {@link IPresenter#onDestroy()}
*/
@Override
public void onDestroy() {
if (useEventBus())//如果要使用 EventBus 请将此方法返回 true
{
EventBusManager.getInstance().unregister(this);//注销 EventBus
}
unDispose();//解除订阅
if (mModel != null) {
mModel.onDestroy();
}
this.mModel = null;
this.mRootView = null;
this.mCompositeDisposable = null;
}
/**
* 只有当 {@code mRootView} 不为 null, 并且 {@code mRootView} 实现了 {@link LifecycleOwner} 时, 此方法才会被调用
* 所以当您想在 {@link Service} 以及一些自定义 {@link View} 或自定义类中使用 {@code Presenter} 时
* 您也将不能继续使用 {@link OnLifecycleEvent} 绑定生命周期
*
* @param owner link {@link ComponentActivity} and {@link Fragment}
*/
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
void onDestroy(LifecycleOwner owner) {
/**
* 注意, 如果在这里调用了 {@link #onDestroy()} 方法, 会出现某些地方引用 {@code mModel} 或 {@code mRootView} 为 null 的情况
* 比如在 {@link RxLifecycle} 终止 {@link Observable} 时, 在 {@link io.reactivex.Observable#doFinally(Action)} 中却引用了 {@code mRootView} 做一些释放资源的操作, 此时会空指针
* 或者如果你声明了多个 @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) 时在其他 @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
* 中引用了 {@code mModel} 或 {@code mRootView} 也可能会出现此情况
*/
owner.getLifecycle().removeObserver(this);
}
/**
* 是否使用 EventBus
* Arms 核心库现在并不会依赖某个 EventBus, 要想使用 EventBus, 还请在项目中自行依赖对应的 EventBus
* 现在支持两种 EventBus, greenrobot 的 EventBus 和畅销书 《Android源码设计模式解析与实战》的作者 何红辉 所作的 AndroidEventBus
* 确保依赖后, 将此方法返回 true, Arms 会自动检测您依赖的 EventBus, 并自动注册
* 这种做法可以让使用者有自行选择三方库的权利, 并且还可以减轻 Arms 的体积
*
* @return 返回 {@code true} (默认为使用 {@code true}), Arms 会自动注册 EventBus
*/
public boolean useEventBus() {
return true;
}
/**
* 将 {@link Disposable} 添加到 {@link CompositeDisposable} 中统一管理
* 可在 {@link Activity#onDestroy()} 中使用 {@link #unDispose()} 停止正在执行的 RxJava 任务,避免内存泄漏
* 目前框架已使用 {@link RxLifecycle} 避免内存泄漏,此方法作为备用方案
*
* @param disposable
*/
public void addDispose(Disposable disposable) {
if (mCompositeDisposable == null) {
mCompositeDisposable = new CompositeDisposable();
}
mCompositeDisposable.add(disposable);//将所有 Disposable 放入容器集中处理
}
/**
* 停止集合中正在执行的 RxJava 任务
*/
public void unDispose() {
if (mCompositeDisposable != null) {
mCompositeDisposable.clear();//保证 Activity 结束时取消所有正在执行的订阅
}
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/mvp/IModel.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.mvp;
/**
* ================================================
* 框架要求框架中的每个 Model 都需要实现此类,以满足规范
*
* @see BaseModel
* @see Model wiki 官方文档
* Created by JessYan on 15/12/2016 10:45
* Contact me
* Follow me
* ================================================
*/
public interface IModel {
/**
* 在框架中 {@link BasePresenter#onDestroy()} 时会默认调用 {@link IModel#onDestroy()}
*/
void onDestroy();
}
================================================
FILE: arms/src/main/java/com/jess/arms/mvp/IPresenter.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.mvp;
import android.app.Activity;
/**
* ================================================
* 框架要求框架中的每个 Presenter 都需要实现此类,以满足规范
*
* @see BasePresenter
* @see Presenter wiki 官方文档
* Created by JessYan on 4/28/2016
* Contact me
* Follow me
* ================================================
*/
public interface IPresenter {
/**
* 做一些初始化操作
*/
void onStart();
/**
* 在框架中 {@link Activity#onDestroy()} 时会默认调用 {@link IPresenter#onDestroy()}
*/
void onDestroy();
}
================================================
FILE: arms/src/main/java/com/jess/arms/mvp/IView.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.mvp;
import android.app.Activity;
import android.content.Intent;
import androidx.annotation.NonNull;
import com.jess.arms.utils.ArmsUtils;
import static com.jess.arms.utils.Preconditions.checkNotNull;
/**
* ================================================
* 框架要求框架中的每个 View 都需要实现此类, 以满足规范
*
* 为了满足部分人的诉求以及向下兼容, {@link IView} 中的部分方法使用 JAVA 1.8 的默认方法实现, 这样实现类可以按实际需求选择是否实现某些方法
* 不实现则使用默认方法中的逻辑, 不清楚默认方法的请自行学习
*
* @see View wiki 官方文档
* Created by JessYan on 4/22/2016
* Contact me
* Follow me
* ================================================
*/
public interface IView {
/**
* 显示加载
*/
default void showLoading() {
}
/**
* 隐藏加载
*/
default void hideLoading() {
}
/**
* 显示信息
*
* @param message 消息内容, 不能为 {@code null}
*/
void showMessage(@NonNull String message);
/**
* 跳转 {@link Activity}
*
* @param intent {@code intent} 不能为 {@code null}
*/
default void launchActivity(@NonNull Intent intent) {
checkNotNull(intent);
ArmsUtils.startActivity(intent);
}
/**
* 杀死自己
*/
default void killMyself() {
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/ArmsUtils.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.style.AbsoluteSizeSpan;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.snackbar.Snackbar;
import com.jess.arms.base.App;
import com.jess.arms.di.component.AppComponent;
import com.jess.arms.integration.AppManager;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.RecyclerView;
/**
* ================================================
* 一些框架常用的工具
*
* Created by JessYan on 2015/11/23.
* Contact me
* Follow me
* ================================================
*/
public class ArmsUtils {
static public Toast mToast;
private ArmsUtils() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* 设置hint大小
*
* @param size
* @param v
* @param res
*/
public static void setViewHintSize(Context context, int size, TextView v, int res) {
SpannableString ss = new SpannableString(getResources(context).getString(
res));
// 新建一个属性对象,设置文字的大小
AbsoluteSizeSpan ass = new AbsoluteSizeSpan(size, true);
// 附加属性到文本
ss.setSpan(ass, 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置hint
v.setHint(new SpannedString(ss)); // 一定要进行转换,否则属性会消失
}
/**
* dp 转 px
*
* @param context {@link Context}
* @param dpValue {@code dpValue}
* @return {@code pxValue}
*/
public static int dip2px(@NonNull Context context, float dpValue) {
final float scale = getResources(context).getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* px 转 dp
*
* @param context {@link Context}
* @param pxValue {@code pxValue}
* @return {@code dpValue}
*/
public static int pix2dip(@NonNull Context context, int pxValue) {
final float scale = getResources(context).getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
/**
* sp 转 px
*
* @param context {@link Context}
* @param spValue {@code spValue}
* @return {@code pxValue}
*/
public static int sp2px(@NonNull Context context, float spValue) {
final float fontScale = getResources(context).getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
/**
* px 转 sp
*
* @param context {@link Context}
* @param pxValue {@code pxValue}
* @return {@code spValue}
*/
public static int px2sp(@NonNull Context context, float pxValue) {
final float fontScale = getResources(context).getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
/**
* 获得资源
*/
public static Resources getResources(Context context) {
return context.getResources();
}
/**
* 得到字符数组
*/
public static String[] getStringArray(Context context, int id) {
return getResources(context).getStringArray(id);
}
/**
* 从 dimens 中获得尺寸
*
* @param context
* @param id
* @return
*/
public static int getDimens(Context context, int id) {
return (int) getResources(context).getDimension(id);
}
/**
* 从 dimens 中获得尺寸
*
* @param context
* @param dimenName
* @return
*/
public static float getDimens(Context context, String dimenName) {
return getResources(context).getDimension(getResources(context).getIdentifier(dimenName, "dimen", context.getPackageName()));
}
/**
* 从String 中获得字符
*
* @return
*/
public static String getString(Context context, int stringID) {
return getResources(context).getString(stringID);
}
/**
* 从String 中获得字符
*
* @return
*/
public static String getString(Context context, String strName) {
return getString(context, getResources(context).getIdentifier(strName, "string", context.getPackageName()));
}
/**
* findview
*
* @param view
* @param viewName
* @param
* @return
*/
public static T findViewByName(Context context, View view, String viewName) {
int id = getResources(context).getIdentifier(viewName, "id", context.getPackageName());
return view.findViewById(id);
}
/**
* findview
*
* @param activity
* @param viewName
* @param
* @return
*/
public static T findViewByName(Context context, Activity activity, String viewName) {
int id = getResources(context).getIdentifier(viewName, "id", context.getPackageName());
return activity.findViewById(id);
}
/**
* 根据 layout 名字获得 id
*
* @param layoutName
* @return
*/
public static int findLayout(Context context, String layoutName) {
return getResources(context).getIdentifier(layoutName, "layout", context.getPackageName());
}
/**
* 填充view
*
* @param detailScreen
* @return
*/
public static View inflate(Context context, int detailScreen) {
return View.inflate(context, detailScreen, null);
}
/**
* 单例 toast
*
* @param string
*/
@SuppressLint("ShowToast")
public static void makeText(Context context, String string) {
if (mToast == null) {
mToast = Toast.makeText(context, string, Toast.LENGTH_SHORT);
}
mToast.setText(string);
mToast.show();
}
/**
* 使用 {@link Snackbar} 显示文本消息
* Arms 已将 com.google.android.material:material 从依赖中移除 (目的是减小 Arms 体积, design 库中含有太多 View)
* 因为 Snackbar 在 com.google.android.material:material 库中, 所以如果框架使用者没有自行依赖 com.google.android.material:material
* Arms 则会使用 Toast 替代 Snackbar 显示信息, 如果框架使用者依赖了 arms-autolayout 库就不用依赖 com.google.android.material:material 了
* 因为在 arms-autolayout 库中已经依赖有 com.google.android.material:material
*
* @param text
*/
public static void snackbarText(String text) {
AppManager.getAppManager().showSnackbar(text, false);
}
/**
* 使用 {@link Snackbar} 长时间显示文本消息
* Arms 已将 com.google.android.material:material 从依赖中移除 (目的是减小 Arms 体积, design 库中含有太多 View)
* 因为 Snackbar 在 com.google.android.material:material 库中, 所以如果框架使用者没有自行依赖 com.google.android.material:material
* Arms 则会使用 Toast 替代 Snackbar 显示信息, 如果框架使用者依赖了 arms-autolayout 库就不用依赖 com.google.android.material:material 了
* 因为在 arms-autolayout 库中已经依赖有 com.google.android.material:material
*
* @param text
*/
public static void snackbarTextWithLong(String text) {
AppManager.getAppManager().showSnackbar(text, true);
}
/**
* 通过资源id获得drawable
*
* @param rID
* @return
*/
public static Drawable getDrawablebyResource(Context context, int rID) {
return getResources(context).getDrawable(rID);
}
/**
* 跳转界面 1, 通过 {@link AppManager#startActivity(Class)}
*
* @param activityClass
*/
public static void startActivity(Class activityClass) {
AppManager.getAppManager().startActivity(activityClass);
}
/**
* 跳转界面 2, 通过 {@link AppManager#startActivity(Intent)}
*
* @param
*/
public static void startActivity(Intent content) {
AppManager.getAppManager().startActivity(content);
}
/**
* 跳转界面 3
*
* @param activity
* @param homeActivityClass
*/
public static void startActivity(Activity activity, Class homeActivityClass) {
Intent intent = new Intent(activity.getApplicationContext(), homeActivityClass);
activity.startActivity(intent);
}
/**
* 跳转界面 4
*
* @param
*/
public static void startActivity(Activity activity, Intent intent) {
activity.startActivity(intent);
}
/**
* 获得屏幕的宽度
*
* @return
*/
public static int getScreenWidth(Context context) {
return getResources(context).getDisplayMetrics().widthPixels;
}
/**
* 获得屏幕的高度
*
* @return
*/
public static int getScreenHeidth(Context context) {
return getResources(context).getDisplayMetrics().heightPixels;
}
/**
* 获得颜色
*/
public static int getColor(Context context, int rid) {
return getResources(context).getColor(rid);
}
/**
* 获得颜色
*/
public static int getColor(Context context, String colorName) {
return getColor(context, getResources(context).getIdentifier(colorName, "color", context.getPackageName()));
}
/**
* 移除孩子
*
* @param view
*/
public static void removeChild(View view) {
ViewParent parent = view.getParent();
if (parent instanceof ViewGroup) {
ViewGroup group = (ViewGroup) parent;
group.removeView(view);
}
}
public static boolean isEmpty(Object obj) {
return obj == null;
}
/**
* MD5
*
* @param string
* @return
* @throws Exception
*/
public static String encodeToMD5(String string) {
byte[] hash = new byte[0];
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
hash = MessageDigest.getInstance("MD5").digest(
string.getBytes(StandardCharsets.UTF_8));
} else {
hash = MessageDigest.getInstance("MD5").digest(
string.getBytes("UTF-8"));
}
} catch (Exception e) {
e.printStackTrace();
}
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
if ((b & 0xFF) < 0x10) {
hex.append("0");
}
hex.append(Integer.toHexString(b & 0xFF));
}
return hex.toString();
}
/**
* 全屏,并且沉侵式状态栏
*
* @param activity
*/
public static void statuInScreen(Activity activity) {
WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN;
activity.getWindow().setAttributes(attrs);
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
/**
* 配置 RecyclerView
*
* @param recyclerView
* @param layoutManager
* @deprecated Use {@link #configRecyclerView(RecyclerView, RecyclerView.LayoutManager)} instead
*/
@Deprecated
public static void configRecycleView(final RecyclerView recyclerView
, RecyclerView.LayoutManager layoutManager) {
recyclerView.setLayoutManager(layoutManager);
//如果可以确定每个item的高度是固定的,设置这个选项可以提高性能
recyclerView.setHasFixedSize(true);
recyclerView.setItemAnimator(new DefaultItemAnimator());
}
/**
* 配置 RecyclerView
*
* @param recyclerView
* @param layoutManager
*/
public static void configRecyclerView(final RecyclerView recyclerView
, RecyclerView.LayoutManager layoutManager) {
recyclerView.setLayoutManager(layoutManager);
//如果可以确定每个item的高度是固定的,设置这个选项可以提高性能
recyclerView.setHasFixedSize(true);
recyclerView.setItemAnimator(new DefaultItemAnimator());
}
/**
* 执行 {@link AppManager#killAll()}
*/
public static void killAll() {
AppManager.getAppManager().killAll();
}
/**
* 执行 {@link AppManager#appExit()}
*/
public static void exitApp() {
AppManager.getAppManager().appExit();
}
public static AppComponent obtainAppComponentFromContext(Context context) {
Preconditions.checkNotNull(context, "%s cannot be null", Context.class.getName());
Preconditions.checkState(context.getApplicationContext() instanceof App, "%s must be implements %s", context.getApplicationContext().getClass().getName(), App.class.getName());
return ((App) context.getApplicationContext()).getAppComponent();
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/CharacterHandler.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
import android.text.InputFilter;
import android.text.Spanned;
import android.text.TextUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
/**
* ================================================
* 处理字符串的工具类
*
* Created by JessYan on 2016/3/16
* Contact me
* Follow me
* ================================================
*/
public class CharacterHandler {
public static final InputFilter EMOJI_FILTER = new InputFilter() {//emoji过滤器
Pattern emoji = Pattern.compile(
"[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]",
Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE);
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart,
int dend) {
Matcher emojiMatcher = emoji.matcher(source);
if (emojiMatcher.find()) {
return "";
}
return null;
}
};
private CharacterHandler() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* 字符串转换成十六进制字符串
*
* @return String 每个Byte之间空格分隔,如: [61 6C 6B]
*/
public static String str2HexStr(String str) {
char[] chars = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder();
byte[] bs = str.getBytes();
int bit;
for (byte b : bs) {
bit = (b & 0x0f0) >> 4;
sb.append(chars[bit]);
bit = b & 0x0f;
sb.append(chars[bit]);
}
return sb.toString().trim();
}
/**
* json 格式化
*
* @param json
* @return
*/
public static String jsonFormat(String json) {
if (TextUtils.isEmpty(json)) {
return "Empty/Null json content";
}
String message;
try {
json = json.trim();
if (json.startsWith("{")) {
JSONObject jsonObject = new JSONObject(json);
message = jsonObject.toString(4);
} else if (json.startsWith("[")) {
JSONArray jsonArray = new JSONArray(json);
message = jsonArray.toString(4);
} else {
message = json;
}
} catch (JSONException e) {
message = json;
} catch (OutOfMemoryError error) {
message = "Output omitted because of Object size";
}
return message;
}
/**
* xml 格式化
*
* @param xml
* @return
*/
public static String xmlFormat(String xml) {
if (TextUtils.isEmpty(xml)) {
return "Empty/Null xml content";
}
String message;
try {
Source xmlInput = new StreamSource(new StringReader(xml));
StreamResult xmlOutput = new StreamResult(new StringWriter());
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.transform(xmlInput, xmlOutput);
message = xmlOutput.getWriter().toString().replaceFirst(">", ">\n");
} catch (TransformerException e) {
message = xml;
}
return message;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/DataHelper.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
import android.util.Base64;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* ================================================
* 处理数据或本地文件的工具类
*
* Created by JessYan on 2016/3/15
* Contact me
* Follow me
* ================================================
*/
public class DataHelper {
public static final String SP_NAME = "config";
private static SharedPreferences mSharedPreferences;
private DataHelper() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* 存储重要信息到sharedPreferences;
*
* @param key
* @param value
*/
public static void setStringSF(Context context, String key, String value) {
if (mSharedPreferences == null) {
mSharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
}
mSharedPreferences.edit().putString(key, value).apply();
}
/**
* 返回存在sharedPreferences的信息
*
* @param key
* @return
*/
public static String getStringSF(Context context, String key) {
if (mSharedPreferences == null) {
mSharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
}
return mSharedPreferences.getString(key, null);
}
/**
* 存储重要信息到sharedPreferences;
*
* @param key
* @param value
*/
public static void setIntergerSF(Context context, String key, int value) {
if (mSharedPreferences == null) {
mSharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
}
mSharedPreferences.edit().putInt(key, value).apply();
}
/**
* 返回存在sharedPreferences的信息
*
* @param key
* @return
*/
public static int getIntergerSF(Context context, String key) {
if (mSharedPreferences == null) {
mSharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
}
return mSharedPreferences.getInt(key, -1);
}
/**
* 清除某个内容
*/
public static void removeSF(Context context, String key) {
if (mSharedPreferences == null) {
mSharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
}
mSharedPreferences.edit().remove(key).apply();
}
/**
* 清除Shareprefrence
*/
public static void clearShareprefrence(Context context) {
if (mSharedPreferences == null) {
mSharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
}
mSharedPreferences.edit().clear().apply();
}
/**
* 将对象储存到sharepreference
*
* @param key
* @param device
* @param
*/
public static boolean saveDeviceData(Context context, String key, T device) {
if (mSharedPreferences == null) {
mSharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try { //Device为自定义类
// 创建对象输出流,并封装字节流
ObjectOutputStream oos = new ObjectOutputStream(baos);
// 将对象写入字节流
oos.writeObject(device);
// 将字节流编码成base64的字符串
String oAuthBase64 = new String(Base64.encode(baos
.toByteArray(), Base64.DEFAULT));
mSharedPreferences.edit().putString(key, oAuthBase64).apply();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将对象从shareprerence中取出来
*
* @param key
* @param
* @return
*/
public static T getDeviceData(Context context, String key) {
if (mSharedPreferences == null) {
mSharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
}
T device = null;
String productBase64 = mSharedPreferences.getString(key, null);
if (productBase64 == null) {
return null;
}
// 读取字节
byte[] base64 = Base64.decode(productBase64.getBytes(), Base64.DEFAULT);
// 封装到字节流
ByteArrayInputStream bais = new ByteArrayInputStream(base64);
try {
// 再次封装
ObjectInputStream bis = new ObjectInputStream(bais);
// 读取对象
//noinspection unchecked
device = (T) bis.readObject();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return device;
}
/**
* 返回缓存文件夹
*/
public static File getCacheFile(Context context) {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File file;
file = context.getExternalCacheDir();//获取系统管理的sd卡缓存文件
if (file == null) {//如果获取的文件为空,就使用自己定义的缓存文件夹做缓存路径
file = new File(getCacheFilePath(context));
makeDirs(file);
}
return file;
} else {
return context.getCacheDir();
}
}
/**
* 获取自定义缓存文件地址
*
* @param context
* @return
*/
public static String getCacheFilePath(Context context) {
String packageName = context.getPackageName();
return Environment.getExternalStorageDirectory().getPath() + packageName;
}
/**
* 创建未存在的文件夹
*
* @param file
* @return
*/
public static File makeDirs(File file) {
if (!file.exists()) {
file.mkdirs();
}
return file;
}
/**
* 使用递归获取目录文件大小
*
* @param dir
* @return
*/
public static long getDirSize(File dir) {
if (dir == null) {
return 0;
}
if (!dir.isDirectory()) {
return 0;
}
long dirSize = 0;
File[] files = dir.listFiles();
for (File file : files) {
if (file.isFile()) {
dirSize += file.length();
} else if (file.isDirectory()) {
dirSize += file.length();
dirSize += getDirSize(file); // 递归调用继续统计
}
}
return dirSize;
}
/**
* 使用递归删除文件夹
*
* @param dir
* @return
*/
public static boolean deleteDir(File dir) {
if (dir == null) {
return false;
}
if (!dir.isDirectory()) {
return false;
}
File[] files = dir.listFiles();
for (File file : files) {
if (file.isFile()) {
file.delete();
} else if (file.isDirectory()) {
deleteDir(file); // 递归调用继续删除
}
}
return true;
}
public static String bytyToString(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
while (in.read(buf) != -1) {
out.write(buf, 0, buf.length);
}
String result = out.toString();
out.close();
return result;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/DeviceUtils.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Point;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.PowerManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import java.io.File;
import java.lang.reflect.Field;
import java.text.NumberFormat;
import java.util.List;
/**
* ================================================
* 获取设备常用信息和处理设备常用操作的工具类
*
* Created by JessYan on 2016/3/15
* Contact me
* Follow me
* ================================================
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class DeviceUtils {
// 手机网络类型
public static final int NETTYPE_WIFI = 0x01;
public static final int NETTYPE_CMWAP = 0x02;
public static final int NETTYPE_CMNET = 0x03;
public static boolean GTE_HC;
public static boolean GTE_ICS;
public static boolean PRE_HC;
public static float displayDensity = 0.0F;
private static Boolean _hasBigScreen = null;
private static Boolean _hasCamera = null;
private static Boolean _isTablet = null;
private static Integer _loadFactor = null;
static {
GTE_ICS = Build.VERSION.SDK_INT >= 14;
GTE_HC = Build.VERSION.SDK_INT >= 11;
PRE_HC = Build.VERSION.SDK_INT < 11;
}
private DeviceUtils() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* dp转px
*
* @param context
* @param dp
* @return
*/
public static float dpToPixel(Context context, float dp) {
return dp * (getDisplayMetrics(context).densityDpi / 160F);
}
/**
* px转dp
*
* @param context
* @param f
* @return
*/
public static float pixelsToDp(Context context, float f) {
return f / (getDisplayMetrics(context).densityDpi / 160F);
}
public static int getDefaultLoadFactor(Context context) {
if (_loadFactor == null) {
Integer integer = 0xf & context
.getResources().getConfiguration().screenLayout;
_loadFactor = integer;
_loadFactor = Math.max(integer, 1);
}
return _loadFactor;
}
public static float getDensity(Context context) {
if (displayDensity == 0.0) {
displayDensity = getDisplayMetrics(context).density;
}
return displayDensity;
}
public static DisplayMetrics getDisplayMetrics(Context context) {
DisplayMetrics displaymetrics = new DisplayMetrics();
((WindowManager) context.getSystemService(
Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(
displaymetrics);
return displaymetrics;
}
/**
* 屏幕高度
*
* @param context
* @return
*/
public static float getScreenHeight(Context context) {
return getDisplayMetrics(context).heightPixels;
}
/**
* 屏幕宽度
*
* @param context
* @return
*/
public static float getScreenWidth(Context context) {
return getDisplayMetrics(context).widthPixels;
}
/**
* 获取activity尺寸
*
* @param activity
* @return
*/
public static int[] getRealScreenSize(Activity activity) {
int[] size = new int[2];
int screenWidth, screenHeight;
WindowManager w = activity.getWindowManager();
Display d = w.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
d.getMetrics(metrics);
// since SDK_INT = 1;
screenWidth = metrics.widthPixels;
screenHeight = metrics.heightPixels;
// includes window decorations (statusbar bar/menu bar)
if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 17) {
try {
screenWidth = (Integer) Display.class.getMethod("getRawWidth")
.invoke(d);
screenHeight = (Integer) Display.class
.getMethod("getRawHeight").invoke(d);
} catch (Exception ignored) {
}
}
// includes window decorations (statusbar bar/menu bar)
if (Build.VERSION.SDK_INT >= 17) {
try {
Point realSize = new Point();
Display.class.getMethod("getRealSize", Point.class).invoke(d,
realSize);
screenWidth = realSize.x;
screenHeight = realSize.y;
} catch (Exception ignored) {
}
}
size[0] = screenWidth;
size[1] = screenHeight;
return size;
}
/**
* 获取状态栏高度
*
* @param context
* @return
*/
public static int getStatusBarHeight(Context context) {
Class> c;
Object obj;
Field field;
int x;
try {
c = Class.forName("com.android.internal.R$dimen");
obj = c.newInstance();
field = c.getField("status_bar_height");
x = Integer.parseInt(field.get(obj).toString());
return context.getResources()
.getDimensionPixelSize(x);
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public static boolean hasBigScreen(Context context) {
boolean flag = true;
if (_hasBigScreen == null) {
boolean flag1;
if ((0xf & context.getResources()
.getConfiguration().screenLayout) >= 3) {
flag1 = flag;
} else {
flag1 = false;
}
Boolean boolean1 = flag1;
_hasBigScreen = boolean1;
if (!boolean1) {
if (getDensity(context) <= 1.5F) {
flag = false;
}
_hasBigScreen = flag;
}
}
return _hasBigScreen;
}
/**
* 设备是否有相机
*
* @param context
* @return
*/
public static boolean hasCamera(Context context) {
if (_hasCamera == null) {
PackageManager pckMgr = context
.getPackageManager();
boolean flag = pckMgr
.hasSystemFeature("android.hardware.camera.front");
boolean flag1 = pckMgr.hasSystemFeature("android.hardware.camera");
boolean flag2;
flag2 = flag || flag1;
_hasCamera = flag2;
}
return _hasCamera;
}
/**
* 设备是否有实体菜单
*
* @param context
* @return
*/
public static boolean hasHardwareMenuKey(Context context) {
boolean flag;
if (PRE_HC) {
flag = true;
} else if (GTE_ICS) {
flag = ViewConfiguration.get(context).hasPermanentMenuKey();
} else {
flag = false;
}
return flag;
}
/**
* 当前是否有网
*
* @param context
* @return
*/
public static boolean hasInternet(Context context) {
boolean flag;
ConnectivityManager manager = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
flag = manager != null && manager.getActiveNetworkInfo() != null;
return flag;
}
/**
* 当前的包是否存在
*
* @param context
* @param pckName
* @return
*/
public static boolean isPackageExist(Context context, String pckName) {
try {
PackageInfo pckInfo = context.getPackageManager()
.getPackageInfo(pckName, 0);
if (pckInfo != null) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
Log.e("TDvice", e.getMessage());
}
return false;
}
public static void hideAnimatedView(View view) {
if (PRE_HC && view != null) {
view.setPadding(view.getWidth(), 0, 0, 0);
}
}
/**
* 隐藏软键盘
*
* @param context
* @param view
*/
public static void hideSoftKeyboard(Context context, View view) {
if (view == null) {
return;
}
InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(
Context.INPUT_METHOD_SERVICE);
if (inputMethodManager.isActive()) {
inputMethodManager.hideSoftInputFromWindow(
view.getWindowToken(), 0);
}
}
/**
* 是否是横屏
*
* @param context
* @return
*/
public static boolean isLandscape(Context context) {
boolean flag;
flag = context.getResources().getConfiguration().orientation == 2;
return flag;
}
/**
* 是否是竖屏
*
* @param context
* @return
*/
public static boolean isPortrait(Context context) {
boolean flag = true;
if (context.getResources().getConfiguration().orientation != 1) {
flag = false;
}
return flag;
}
public static boolean isTablet(Context context) {
if (_isTablet == null) {
boolean flag;
flag = (0xf & context.getResources()
.getConfiguration().screenLayout) >= 3;
_isTablet = flag;
}
return _isTablet;
}
public static void showAnimatedView(View view) {
if (PRE_HC && view != null) {
view.setPadding(0, 0, 0, 0);
}
}
public static void showSoftKeyboard(Dialog dialog) {
dialog.getWindow().setSoftInputMode(4);
}
public static void showSoftKeyboard(Context context, View view) {
((InputMethodManager) context.getSystemService(
Context.INPUT_METHOD_SERVICE)).showSoftInput(view,
InputMethodManager.SHOW_FORCED);
}
public static void toogleSoftKeyboard(Context context, View view) {
((InputMethodManager) context.getSystemService(
Context.INPUT_METHOD_SERVICE)).toggleSoftInput(0,
InputMethodManager.HIDE_NOT_ALWAYS);
}
public static boolean isSdcardReady() {
return Environment.MEDIA_MOUNTED.equals(Environment
.getExternalStorageState());
}
public static String getCurCountryLan(Context context) {
return context.getResources().getConfiguration().locale
.getLanguage()
+ "-"
+ context.getResources().getConfiguration().locale
.getCountry();
}
public static boolean isZhCN(Context context) {
String lang = context.getResources()
.getConfiguration().locale.getCountry();
return "CN".equalsIgnoreCase(lang);
}
public static String percent(double p1, double p2) {
String str;
double p3 = p1 / p2;
NumberFormat nf = NumberFormat.getPercentInstance();
nf.setMinimumFractionDigits(2);
str = nf.format(p3);
return str;
}
public static String percent2(double p1, double p2) {
String str;
double p3 = p1 / p2;
NumberFormat nf = NumberFormat.getPercentInstance();
nf.setMinimumFractionDigits(0);
str = nf.format(p3);
return str;
}
public static boolean isHaveMarket(Context context) {
Intent intent = new Intent();
intent.setAction("android.intent.action.MAIN");
intent.addCategory("android.intent.category.APP_MARKET");
PackageManager pm = context.getPackageManager();
List infos = pm.queryIntentActivities(intent, 0);
return infos.size() > 0;
}
public static void setFullScreen(Activity activity) {
WindowManager.LayoutParams params = activity.getWindow()
.getAttributes();
params.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
activity.getWindow().setAttributes(params);
activity.getWindow().addFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
public static void cancelFullScreen(Activity activity) {
WindowManager.LayoutParams params = activity.getWindow()
.getAttributes();
params.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
activity.getWindow().setAttributes(params);
activity.getWindow().clearFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
public static PackageInfo getPackageInfo(Context context, String pckName) {
try {
return context.getPackageManager()
.getPackageInfo(pckName, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取版本号
*
* @param context
* @return
*/
public static int getVersionCode(Context context) {
int versionCode;
try {
versionCode = context.getPackageManager()
.getPackageInfo(context.getPackageName(),
0).versionCode;
} catch (PackageManager.NameNotFoundException ex) {
versionCode = 0;
}
return versionCode;
}
/**
* 获取指定包名应用的版本号
*
* @param context
* @param packageName
* @return
*/
public static int getVersionCode(Context context, String packageName) {
int versionCode;
try {
versionCode = context.getPackageManager()
.getPackageInfo(packageName, 0).versionCode;
} catch (PackageManager.NameNotFoundException ex) {
versionCode = 0;
}
return versionCode;
}
/**
* 获取版本名
*
* @param context
* @return
*/
public static String getVersionName(Context context) {
String name;
try {
name = context.getPackageManager()
.getPackageInfo(context.getPackageName(),
0).versionName;
} catch (PackageManager.NameNotFoundException ex) {
name = "";
}
return name;
}
public static boolean isScreenOn(Context context) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
/**
* 安装应用
*
* @param context
* @param file
*/
public static void installAPK(Context context, File file) {
if (file == null || !file.exists()) {
return;
}
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
context.startActivity(intent);
}
public static Intent getInstallApkIntent(File file) {
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
return intent;
}
/**
* 拨打电话
*
* @param context
* @param number
*/
public static void openDial(Context context, String number) {
Uri uri = Uri.parse("tel:" + number);
Intent it = new Intent(Intent.ACTION_DIAL, uri);
context.startActivity(it);
}
public static void openSMS(Context context, String smsBody, String tel) {
Uri uri = Uri.parse("smsto:" + tel);
Intent it = new Intent(Intent.ACTION_SENDTO, uri);
it.putExtra("sms_body", smsBody);
context.startActivity(it);
}
public static void openDail(Context context) {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
public static void openSendMsg(Context context) {
Uri uri = Uri.parse("smsto:");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
public static void openCamera(Context context) {
Intent intent = new Intent(); // 调用照相机
intent.setAction("android.media.action.STILL_IMAGE_CAMERA");
intent.setFlags(0x34c40000);
context.startActivity(intent);
}
public static String getIMEI(Context context) {
TelephonyManager tel = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
return tel.getDeviceId();
}
public static String getPhoneType() {
return Build.MODEL;
}
public static void openApp(Context context, String packageName) {
Intent mainIntent = context.getPackageManager()
.getLaunchIntentForPackage(packageName);
if (mainIntent == null) {
mainIntent = new Intent(packageName);
}
context.startActivity(mainIntent);
}
public static boolean openAppActivity(Context context, String packageName,
String activityName) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
ComponentName cn = new ComponentName(packageName, activityName);
intent.setComponent(cn);
try {
context.startActivity(intent);
return true;
} catch (Exception e) {
return false;
}
}
/**
* wifi是否开启
*
* @param context
* @return
*/
public static boolean isWifiOpen(Context context) {
boolean isWifiConnect = false;
ConnectivityManager cm = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
// check the networkInfos numbers
NetworkInfo[] networkInfos = cm.getAllNetworkInfo();
for (NetworkInfo networkInfo : networkInfos) {
if (networkInfo.getState() == NetworkInfo.State.CONNECTED) {
if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
isWifiConnect = false;
}
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
isWifiConnect = true;
}
}
}
return isWifiConnect;
}
/**
* 卸载软件
*
* @param context
* @param packageName
*/
public static void uninstallApk(Context context, String packageName) {
if (isPackageExist(context, packageName)) {
Uri packageURI = Uri.parse("package:" + packageName);
Intent uninstallIntent = new Intent(Intent.ACTION_DELETE,
packageURI);
context.startActivity(uninstallIntent);
}
}
@SuppressWarnings("deprecation")
public static void copyTextToBoard(Context context, String string) {
if (TextUtils.isEmpty(string)) {
return;
}
ClipboardManager clip = (ClipboardManager) context
.getSystemService(Context.CLIPBOARD_SERVICE);
clip.setText(string);
}
/**
* 发送邮件
*
* @param context
* @param subject 主题
* @param content 内容
* @param emails 邮件地址
*/
public static void sendEmail(Context context, String subject,
String content, String... emails) {
try {
Intent intent = new Intent(Intent.ACTION_SEND);
// 模拟器
// intent.setType("text/plain");
intent.setType("message/rfc822"); // 真机
intent.putExtra(Intent.EXTRA_EMAIL, emails);
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, content);
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
}
public static int getStatuBarHeight(Context context) {
Class> c;
Object obj;
Field field;
int x, sbar = 38;// 默认为38,貌似大部分是这样的
try {
c = Class.forName("com.android.internal.R$dimen");
obj = c.newInstance();
field = c.getField("status_bar_height");
x = Integer.parseInt(field.get(obj).toString());
sbar = context.getResources()
.getDimensionPixelSize(x);
} catch (Exception e1) {
e1.printStackTrace();
}
return sbar;
}
public static boolean hasStatusBar(Activity activity) {
WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
return (attrs.flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != WindowManager.LayoutParams.FLAG_FULLSCREEN;
}
/**
* 调用系统安装了的应用分享
*
* @param context
* @param title
* @param url
*/
public static void showSystemShareOption(Activity context,
final String title, final String url) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, "分享:" + title);
intent.putExtra(Intent.EXTRA_TEXT, title + " " + url);
context.startActivity(Intent.createChooser(intent, "选择分享"));
}
/**
* 获取当前网络类型
*
* @return 0:没有网络 1:WIFI网络 2:WAP网络 3:NET网络
*/
public static int getNetworkType(Context context) {
int netType = 0;
ConnectivityManager connectivityManager = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo == null) {
return netType;
}
int nType = networkInfo.getType();
if (nType == ConnectivityManager.TYPE_MOBILE) {
String extraInfo = networkInfo.getExtraInfo();
if (extraInfo != null && !extraInfo.isEmpty()) {
if ("cmnet".equalsIgnoreCase(extraInfo)) {
netType = NETTYPE_CMNET;
} else {
netType = NETTYPE_CMWAP;
}
}
} else if (nType == ConnectivityManager.TYPE_WIFI) {
netType = NETTYPE_WIFI;
}
return netType;
}
public static boolean netIsConnected(Context context) {
ConnectivityManager connectMgr = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
//手机网络连接状态
NetworkInfo mobNetInfo = connectMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
//WIFI连接状态
NetworkInfo wifiNetInfo = connectMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
//当前无可用的网络
return mobNetInfo.isConnected() || wifiNetInfo.isConnected();
}
/**
* 判断是否存在sd卡
*
* @return
*/
public static boolean isExitsSdcard() {
return Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/DrawableProvider.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.widget.TextView;
import androidx.exifinterface.media.ExifInterface;
import java.io.IOException;
/**
* ================================================
* 处理 {@link Drawable} 和 {@link Bitmap} 的工具类
*
* Created by JessYan on 2015/11/24
* Contact me
* Follow me
* ================================================
*/
public class DrawableProvider {
private DrawableProvider() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* 获得选择器
*
* @param normalDrawable
* @param pressDrawable
* @return
*/
public static Drawable getStateListDrawable(Drawable normalDrawable, Drawable pressDrawable) {
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(new int[]{android.R.attr.state_checked}, pressDrawable);
stateListDrawable.addState(new int[]{}, normalDrawable);
return stateListDrawable;
}
/**
* 将 TextView/RadioButton 中设置的 drawable 动态的缩放
*
* @param percent
* @param tv
* @return
*/
public static Drawable getScaleDrawableForRadioButton(float percent, TextView tv) {
Drawable[] compoundDrawables = tv.getCompoundDrawables();
Drawable drawable = null;
for (Drawable d : compoundDrawables) {
if (d != null) {
drawable = d;
}
}
return getScaleDrawable(percent, drawable);
}
/**
* 将 TextView/RadioButton 中设置的 drawable 动态的缩放
*
* @param tv
* @return
*/
public static Drawable getScaleDrawableForRadioButton2(float width, TextView tv) {
Drawable[] compoundDrawables = tv.getCompoundDrawables();
Drawable drawable = null;
for (Drawable d : compoundDrawables) {
if (d != null) {
drawable = d;
}
}
return getScaleDrawable2(width, drawable);
}
/**
* 传入图片,将图片按传入比例缩放
*
* @param percent
* @return
*/
public static Drawable getScaleDrawable(float percent, Drawable drawable) {
drawable.setBounds(0, 0, (int) (drawable.getIntrinsicWidth() * percent + 0.5f), (int) (drawable.getIntrinsicHeight() * percent + 0.5f));
return drawable;
}
/**
* 传入图片,将图片按传入宽度和原始宽度的比例缩放
*
* @param width
* @return
*/
public static Drawable getScaleDrawable2(float width, Drawable drawable) {
float percent = width * 1.0f / drawable.getIntrinsicWidth();
return getScaleDrawable(percent, drawable);
}
/**
* 设置左边的drawable
*
* @param tv
* @param drawable
*/
public static void setLeftDrawable(TextView tv, Drawable drawable) {
tv.setCompoundDrawables(drawable, null, null, null);
}
/**
* 改变Bitmap的长宽
*
* @param bitmap
* @return
*/
public static Bitmap getReSizeBitmap(Bitmap bitmap, float targetWidth, float targetheight) {
Bitmap returnBm = null;
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Matrix matrix = new Matrix();
matrix.postScale(targetWidth / width, targetheight / height); //长和宽放大缩小的比例
try {
returnBm = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
} catch (Exception e) {
e.printStackTrace();
}
if (returnBm == null) {
returnBm = bitmap;
}
if (bitmap != returnBm) {
bitmap.recycle();
}
return returnBm;
}
/**
* 将图片按照某个角度进行旋转
*
* @param bm 需要旋转的图片
* @param degree 旋转角度
* @return 旋转后的图片
*/
public static Bitmap rotateBitmapByDegree(Bitmap bm, int degree) {
Bitmap returnBm = null;
// 根据旋转角度,生成旋转矩阵
Matrix matrix = new Matrix();
matrix.postRotate(degree);
try {
// 将原始图片按照旋转矩阵进行旋转,并得到新的图片
returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
if (returnBm == null) {
returnBm = bm;
}
if (bm != returnBm) {
bm.recycle();
}
return returnBm;
}
/**
* 读取图片的旋转的角度
*
* @param path 图片绝对路径
* @return 图片的旋转角度
*/
public static int getBitmapDegree(String path) {
int degree = 0;
try {
// 从指定路径下读取图片,并获取其EXIF信息
ExifInterface exifInterface = new ExifInterface(path);
// 获取图片的旋转信息
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
default:
break;
}
} catch (IOException e) {
e.printStackTrace();
}
return degree;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/FastBlur.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import android.view.View;
/**
* ================================================
* 处理高斯模糊的工具类
*
* Created by JessYan on 03/06/2014.
* Contact me
* Follow me
* ================================================
*/
public class FastBlur {
private FastBlur() {
throw new IllegalStateException("you can't instantiate me!");
}
public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {
// Stack Blur v1.0 from
// http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
//
// Java Author: Mario Klingemann
// http://incubator.quasimondo.com
// created Feburary 29, 2004
// Android port : Yahel Bouaziz
// http://www.kayenko.com
// ported april 5th, 2012
// This is a compromise between Gaussian Blur and Box blur
// It creates much better looking blurs than Box Blur, but is
// 7x faster than my Gaussian Blur implementation.
//
// I called it Stack Blur because this describes best how this
// filter works internally: it creates a kind of moving stack
// of colors whilst scanning through the image. Thereby it
// just has to add one new block of color to the right side
// of the stack and remove the leftmost color. The remaining
// colors on the topmost layer of the stack are either added on
// or reduced by one, depending on if they are on the right or
// on the left side of the stack.
//
// If you are using this algorithm in your code please add
// the following line:
//
// Stack Blur Algorithm by Mario Klingemann
Bitmap bitmap;
if (canReuseInBitmap) {
bitmap = sentBitmap;
} else {
bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
}
if (radius < 1) {
return (null);
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] pix = new int[w * h];
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
int[] r = new int[wh];
int[] g = new int[wh];
int[] b = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int[] vmin = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int[] dv = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
yw = yi = 0;
int[][] stack = new int[div][3];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = pix[yi + Math.min(wm, Math.max(i, 0))];
sir = stack[i + radius];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pix[yw + vmin[x]];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[(stackpointer) % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
rbs = r1 - Math.abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi += w;
}
}
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
return (bitmap);
}
/**
* 给 {@link View} 设置高斯模糊背景图片
*
* @param context
* @param bkg
* @param view
*/
public static void blur(Context context, Bitmap bkg, View view) {
long startMs = System.currentTimeMillis();
float radius = 15;
float scaleFactor = 8;
//放大到整个view的大小
bkg = DrawableProvider.getReSizeBitmap(bkg, view.getMeasuredWidth(), view.getMeasuredHeight());
Bitmap overlay = Bitmap.createBitmap((int) (view.getMeasuredWidth() / scaleFactor)
, (int) (view.getMeasuredHeight() / scaleFactor), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(overlay);
canvas.translate(-view.getLeft() / scaleFactor, -view.getTop() / scaleFactor);
canvas.scale(1 / scaleFactor, 1 / scaleFactor);
Paint paint = new Paint();
paint.setFlags(Paint.FILTER_BITMAP_FLAG);
canvas.drawBitmap(bkg, 0, 0, paint);
overlay = FastBlur.doBlur(overlay, (int) radius, true);
view.setBackgroundDrawable(new BitmapDrawable(context.getResources(), overlay));
Log.w("test", "cost " + (System.currentTimeMillis() - startMs) + "ms");
}
/**
* 将 {@link Bitmap} 高斯模糊并返回
*
* @param bkg
* @param width
* @param height
* @return
*/
public static Bitmap blurBitmap(Bitmap bkg, int width, int height) {
long startMs = System.currentTimeMillis();
float radius = 15;//越大模糊效果越大
float scaleFactor = 8;
//放大到整个view的大小
bkg = DrawableProvider.getReSizeBitmap(bkg, width, height);
Bitmap overlay = Bitmap.createBitmap((int) (width / scaleFactor)
, (int) (height / scaleFactor), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(overlay);
canvas.scale(1 / scaleFactor, 1 / scaleFactor);
Paint paint = new Paint();
paint.setFlags(Paint.FILTER_BITMAP_FLAG);
canvas.drawBitmap(bkg, 0, 0, paint);
overlay = FastBlur.doBlur(overlay, (int) radius, true);
Log.w("test", "cost " + (System.currentTimeMillis() - startMs) + "ms");
return overlay;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/LogUtils.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
import android.text.TextUtils;
import android.util.Log;
/**
* ================================================
* 日志工具类
*
* Created by JessYan on 2015/11/23.
* Contact me
* Follow me
* ================================================
*/
public class LogUtils {
private static final String DEFAULT_TAG = "MVPArms";
private static boolean isLog = true;
private LogUtils() {
throw new IllegalStateException("you can't instantiate me!");
}
public static boolean isLog() {
return isLog;
}
public static void setLog(boolean isLog) {
LogUtils.isLog = isLog;
}
public static void debugInfo(String tag, String msg) {
if (!isLog || TextUtils.isEmpty(msg)) {
return;
}
Log.d(tag, msg);
}
public static void debugInfo(String msg) {
debugInfo(DEFAULT_TAG, msg);
}
public static void warnInfo(String tag, String msg) {
if (!isLog || TextUtils.isEmpty(msg)) {
return;
}
Log.w(tag, msg);
}
public static void warnInfo(String msg) {
warnInfo(DEFAULT_TAG, msg);
}
/**
* 这里使用自己分节的方式来输出足够长度的 message
*
* @param tag 标签
* @param msg 日志内容
*/
public static void debugLongInfo(String tag, String msg) {
if (!isLog || TextUtils.isEmpty(msg)) {
return;
}
msg = msg.trim();
int index = 0;
int maxLength = 3500;
String sub;
while (index < msg.length()) {
if (msg.length() <= index + maxLength) {
sub = msg.substring(index);
} else {
sub = msg.substring(index, index + maxLength);
}
index += maxLength;
Log.d(tag, sub.trim());
}
}
public static void debugLongInfo(String msg) {
debugLongInfo(DEFAULT_TAG, msg);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/PermissionUtil.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
import android.Manifest;
import com.tbruyelle.rxpermissions2.Permission;
import com.tbruyelle.rxpermissions2.RxPermissions;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.annotations.NonNull;
import me.jessyan.rxerrorhandler.core.RxErrorHandler;
import me.jessyan.rxerrorhandler.handler.ErrorHandleSubscriber;
import timber.log.Timber;
/**
* ================================================
* 权限请求工具类
*
* @see PermissionUtil wiki 官方文档
* Created by JessYan on 17/10/2016 10:09
* Contact me
* Follow me
* ================================================
*/
public class PermissionUtil {
public static final String TAG = "Permission";
private PermissionUtil() {
throw new IllegalStateException("you can't instantiate me!");
}
public static void requestPermission(final RequestPermission requestPermission, RxPermissions rxPermissions, RxErrorHandler errorHandler, String... permissions) {
if (permissions == null || permissions.length == 0) {
return;
}
List needRequest = new ArrayList<>();
for (String permission : permissions) { //过滤调已经申请过的权限
if (!rxPermissions.isGranted(permission)) {
needRequest.add(permission);
}
}
if (needRequest.isEmpty()) {//全部权限都已经申请过,直接执行操作
requestPermission.onRequestPermissionSuccess();
} else {//没有申请过,则开始申请
rxPermissions
.requestEach(needRequest.toArray(new String[0]))
.buffer(permissions.length)
.subscribe(new ErrorHandleSubscriber>(errorHandler) {
@Override
public void onNext(@NonNull List permissions) {
List failurePermissions = new ArrayList<>();
List askNeverAgainPermissions = new ArrayList<>();
for (Permission p : permissions) {
if (!p.granted) {
if (p.shouldShowRequestPermissionRationale) {
failurePermissions.add(p.name);
} else {
askNeverAgainPermissions.add(p.name);
}
}
}
if (failurePermissions.size() > 0) {
Timber.tag(TAG).d("Request permissions failure");
requestPermission.onRequestPermissionFailure(failurePermissions);
}
if (askNeverAgainPermissions.size() > 0) {
Timber.tag(TAG).d("Request permissions failure with ask never again");
requestPermission.onRequestPermissionFailureWithAskNeverAgain(askNeverAgainPermissions);
}
if (failurePermissions.size() == 0 && askNeverAgainPermissions.size() == 0) {
Timber.tag(TAG).d("Request permissions success");
requestPermission.onRequestPermissionSuccess();
}
}
});
}
}
/**
* 请求摄像头权限
*/
public static void launchCamera(RequestPermission requestPermission, RxPermissions rxPermissions, RxErrorHandler errorHandler) {
requestPermission(requestPermission, rxPermissions, errorHandler, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA);
}
/**
* 请求外部存储的权限
*/
public static void externalStorage(RequestPermission requestPermission, RxPermissions rxPermissions, RxErrorHandler errorHandler) {
requestPermission(requestPermission, rxPermissions, errorHandler, Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
/**
* 请求发送短信权限
*/
public static void sendSms(RequestPermission requestPermission, RxPermissions rxPermissions, RxErrorHandler errorHandler) {
requestPermission(requestPermission, rxPermissions, errorHandler, Manifest.permission.SEND_SMS);
}
/**
* 请求打电话权限
*/
public static void callPhone(RequestPermission requestPermission, RxPermissions rxPermissions, RxErrorHandler errorHandler) {
requestPermission(requestPermission, rxPermissions, errorHandler, Manifest.permission.CALL_PHONE);
}
/**
* 请求获取手机状态的权限
*/
public static void readPhonestate(RequestPermission requestPermission, RxPermissions rxPermissions, RxErrorHandler errorHandler) {
requestPermission(requestPermission, rxPermissions, errorHandler, Manifest.permission.READ_PHONE_STATE);
}
public interface RequestPermission {
/**
* 权限请求成功
*/
void onRequestPermissionSuccess();
/**
* 用户拒绝了权限请求, 权限请求失败, 但还可以继续请求该权限
*
* @param permissions 请求失败的权限名
*/
void onRequestPermissionFailure(List permissions);
/**
* 用户拒绝了权限请求并且用户选择了以后不再询问, 权限请求失败, 这时将不能继续请求该权限, 需要提示用户进入设置页面打开该权限
*
* @param permissions 请求失败的权限名
*/
void onRequestPermissionFailureWithAskNeverAgain(List permissions);
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/Preconditions.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
import androidx.annotation.Nullable;
/**
* ================================================
* Created by JessYan on 26/09/2016 13:59
* Contact me
* Follow me
* ================================================
*/
public final class Preconditions {
private Preconditions() {
throw new IllegalStateException("you can't instantiate me!");
}
public static void checkArgument(boolean expression) {
if (!expression) {
throw new IllegalArgumentException();
}
}
public static void checkArgument(boolean expression, @Nullable Object errorMessage) {
if (!expression) {
throw new IllegalArgumentException(String.valueOf(errorMessage));
}
}
public static void checkArgument(boolean expression, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs) {
if (!expression) {
throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs));
}
}
public static void checkState(boolean expression) {
if (!expression) {
throw new IllegalStateException();
}
}
public static void checkState(boolean expression, @Nullable Object errorMessage) {
if (!expression) {
throw new IllegalStateException(String.valueOf(errorMessage));
}
}
public static void checkState(boolean expression, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs) {
if (!expression) {
throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs));
}
}
public static T checkNotNull(T reference) {
if (reference == null) {
throw new NullPointerException();
} else {
return reference;
}
}
public static T checkNotNull(T reference, @Nullable Object errorMessage) {
if (reference == null) {
throw new NullPointerException(String.valueOf(errorMessage));
} else {
return reference;
}
}
public static T checkNotNull(T reference, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs) {
if (reference == null) {
throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs));
} else {
return reference;
}
}
public static int checkElementIndex(int index, int size) {
return checkElementIndex(index, size, "index");
}
public static int checkElementIndex(int index, int size, @Nullable String desc) {
if (index >= 0 && index < size) {
return index;
} else {
throw new IndexOutOfBoundsException(badElementIndex(index, size, desc));
}
}
private static String badElementIndex(int index, int size, String desc) {
if (index < 0) {
return format("%s (%s) must not be negative", new Object[]{desc, Integer.valueOf(index)});
} else if (size < 0) {
throw new IllegalArgumentException((new StringBuilder(26)).append("negative size: ").append(size).toString());
} else {
return format("%s (%s) must be less than size (%s)", new Object[]{desc, Integer.valueOf(index), Integer.valueOf(size)});
}
}
public static int checkPositionIndex(int index, int size) {
return checkPositionIndex(index, size, "index");
}
public static int checkPositionIndex(int index, int size, @Nullable String desc) {
if (index >= 0 && index <= size) {
return index;
} else {
throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc));
}
}
private static String badPositionIndex(int index, int size, String desc) {
if (index < 0) {
return format("%s (%s) must not be negative", new Object[]{desc, Integer.valueOf(index)});
} else if (size < 0) {
throw new IllegalArgumentException((new StringBuilder(26)).append("negative size: ").append(size).toString());
} else {
return format("%s (%s) must not be greater than size (%s)", new Object[]{desc, Integer.valueOf(index), Integer.valueOf(size)});
}
}
public static void checkPositionIndexes(int start, int end, int size) {
if (start < 0 || end < start || end > size) {
throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size));
}
}
private static String badPositionIndexes(int start, int end, int size) {
return start >= 0 && start <= size ? (end >= 0 && end <= size ? format("end index (%s) must not be less than start index (%s)", new Object[]{Integer.valueOf(end), Integer.valueOf(start)}) : badPositionIndex(end, size, "end index")) : badPositionIndex(start, size, "start index");
}
static String format(String template, @Nullable Object... args) {
template = String.valueOf(template);
StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
int templateStart = 0;
int i;
int placeholderStart;
for (i = 0; i < args.length; templateStart = placeholderStart + 2) {
placeholderStart = template.indexOf("%s", templateStart);
if (placeholderStart == -1) {
break;
}
builder.append(template.substring(templateStart, placeholderStart));
builder.append(args[i++]);
}
builder.append(template.substring(templateStart));
if (i < args.length) {
builder.append(" [");
builder.append(args[i++]);
while (i < args.length) {
builder.append(", ");
builder.append(args[i++]);
}
builder.append(']');
}
return builder.toString();
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/RxLifecycleUtils.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
import com.jess.arms.integration.lifecycle.ActivityLifecycleable;
import com.jess.arms.integration.lifecycle.FragmentLifecycleable;
import com.jess.arms.integration.lifecycle.Lifecycleable;
import com.jess.arms.mvp.IView;
import com.trello.rxlifecycle2.LifecycleTransformer;
import com.trello.rxlifecycle2.RxLifecycle;
import com.trello.rxlifecycle2.android.ActivityEvent;
import com.trello.rxlifecycle2.android.FragmentEvent;
import com.trello.rxlifecycle2.android.RxLifecycleAndroid;
import io.reactivex.annotations.NonNull;
/**
* ================================================
* 使用此类操作 RxLifecycle 的特性
*
* Created by JessYan on 26/08/2017 17:52
* Contact me
* Follow me
* ================================================
*/
public class RxLifecycleUtils {
private RxLifecycleUtils() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* 绑定 Activity 的指定生命周期
*
* @param view
* @param event
* @param
* @return
*/
public static LifecycleTransformer bindUntilEvent(@NonNull final IView view,
final ActivityEvent event) {
Preconditions.checkNotNull(view, "view == null");
if (view instanceof ActivityLifecycleable) {
return bindUntilEvent((ActivityLifecycleable) view, event);
} else {
throw new IllegalArgumentException("view isn't ActivityLifecycleable");
}
}
/**
* 绑定 Fragment 的指定生命周期
*
* @param view
* @param event
* @param
* @return
*/
public static LifecycleTransformer bindUntilEvent(@NonNull final IView view,
final FragmentEvent event) {
Preconditions.checkNotNull(view, "view == null");
if (view instanceof FragmentLifecycleable) {
return bindUntilEvent((FragmentLifecycleable) view, event);
} else {
throw new IllegalArgumentException("view isn't FragmentLifecycleable");
}
}
public static LifecycleTransformer bindUntilEvent(@NonNull final Lifecycleable lifecycleable,
final R event) {
Preconditions.checkNotNull(lifecycleable, "lifecycleable == null");
return RxLifecycle.bindUntilEvent(lifecycleable.provideLifecycleSubject(), event);
}
/**
* 绑定 Activity/Fragment 的生命周期
*
* @param view
* @param
* @return
*/
public static LifecycleTransformer bindToLifecycle(@NonNull IView view) {
Preconditions.checkNotNull(view, "view == null");
if (view instanceof Lifecycleable) {
return bindToLifecycle((Lifecycleable) view);
} else {
throw new IllegalArgumentException("view isn't Lifecycleable");
}
}
public static LifecycleTransformer bindToLifecycle(@NonNull Lifecycleable lifecycleable) {
Preconditions.checkNotNull(lifecycleable, "lifecycleable == null");
if (lifecycleable instanceof ActivityLifecycleable) {
return RxLifecycleAndroid.bindActivity(((ActivityLifecycleable) lifecycleable).provideLifecycleSubject());
} else if (lifecycleable instanceof FragmentLifecycleable) {
return RxLifecycleAndroid.bindFragment(((FragmentLifecycleable) lifecycleable).provideLifecycleSubject());
} else {
throw new IllegalArgumentException("Lifecycleable not match");
}
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/ThirdViewUtil.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import com.zhy.autolayout.AutoFrameLayout;
import com.zhy.autolayout.AutoLinearLayout;
import com.zhy.autolayout.AutoRelativeLayout;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import static com.jess.arms.base.Platform.DEPENDENCY_AUTO_LAYOUT;
import static com.jess.arms.base.delegate.ActivityDelegate.LAYOUT_FRAMELAYOUT;
import static com.jess.arms.base.delegate.ActivityDelegate.LAYOUT_LINEARLAYOUT;
import static com.jess.arms.base.delegate.ActivityDelegate.LAYOUT_RELATIVELAYOUT;
/**
* ================================================
* Created by JessYan on 17/03/2016 13:59
* Contact me
* Follow me
* ================================================
*/
public class ThirdViewUtil {
private static int HAS_AUTO_LAYOUT_META = -1;//0 说明 AndroidManifest 里面没有使用 AutoLauout 的 Meta, 即不使用 AutoLayout, 1 为有 Meta, 即需要使用
private ThirdViewUtil() {
throw new IllegalStateException("you can't instantiate me!");
}
public static boolean isUseAutolayout() {
return DEPENDENCY_AUTO_LAYOUT && HAS_AUTO_LAYOUT_META == 1;
}
public static Unbinder bindTarget(Object target, Object source) {
if (source instanceof Activity) {
return ButterKnife.bind(target, (Activity) source);
} else if (source instanceof View) {
return ButterKnife.bind(target, (View) source);
} else if (source instanceof Dialog) {
return ButterKnife.bind(target, (Dialog) source);
} else {
return Unbinder.EMPTY;
}
}
@Nullable
public static View convertAutoView(String name, Context context, AttributeSet attrs) {
//本框架并不强制您使用 AutoLayout
//如果您不想使用 AutoLayout, 请不要依赖 AutoLayout, 也不要在 AndroidManifest 中声明 AutoLayout 的 Meta 属性 (design_width, design_height)
if (!DEPENDENCY_AUTO_LAYOUT) {
return null;
}
if (HAS_AUTO_LAYOUT_META == -1) {
HAS_AUTO_LAYOUT_META = 1;
PackageManager packageManager = context.getPackageManager();
ApplicationInfo applicationInfo;
try {
applicationInfo = packageManager.getApplicationInfo(context
.getPackageName(), PackageManager.GET_META_DATA);
if (applicationInfo == null || applicationInfo.metaData == null
|| !applicationInfo.metaData.containsKey("design_width")
|| !applicationInfo.metaData.containsKey("design_height")) {
HAS_AUTO_LAYOUT_META = 0;
}
} catch (PackageManager.NameNotFoundException e) {
HAS_AUTO_LAYOUT_META = 0;
}
}
if (HAS_AUTO_LAYOUT_META == 0) {
return null;
}
View view = null;
switch (name) {
case LAYOUT_FRAMELAYOUT:
view = new AutoFrameLayout(context, attrs);
break;
case LAYOUT_LINEARLAYOUT:
view = new AutoLinearLayout(context, attrs);
break;
case LAYOUT_RELATIVELAYOUT:
view = new AutoRelativeLayout(context, attrs);
break;
default:
break;
}
return view;
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/UrlEncoderUtils.java
================================================
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
/**
* ================================================
* Created by JessYan on 2018/9/14 14:10
* Contact me
* Follow me
* ================================================
*/
public class UrlEncoderUtils {
private UrlEncoderUtils() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* 判断 str 是否已经 URLEncoder.encode() 过
* 经常遇到这样的情况, 拿到一个 URL, 但是搞不清楚到底要不要 URLEncoder.encode()
* 不做 URLEncoder.encode() 吧, 担心出错, 做 URLEncoder.encode() 吧, 又怕重复了
*
* @param str 需要判断的内容
* @return 返回 {@code true} 为被 URLEncoder.encode() 过
*/
public static boolean hasUrlEncoded(String str) {
boolean encode = false;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '%' && (i + 2) < str.length()) {
// 判断是否符合urlEncode规范
char c1 = str.charAt(i + 1);
char c2 = str.charAt(i + 2);
if (isValidHexChar(c1) && isValidHexChar(c2)) {
encode = true;
break;
} else {
break;
}
}
}
return encode;
}
/**
* 判断 c 是否是 16 进制的字符
*
* @param c 需要判断的字符
* @return 返回 {@code true} 为 16 进制的字符
*/
private static boolean isValidHexChar(char c) {
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F');
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/utils/ZipHelper.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.utils;
import android.os.Build;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Inflater;
/**
* ================================================
* 处理压缩和解压的工具类
*
* Created by JessYan on 10/05/2016
* Contact me
* Follow me
* ================================================
*/
public class ZipHelper {
private ZipHelper() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* zlib decompress 2 String
*
* @param bytesToDecompress
* @return
*/
public static String decompressToStringForZlib(byte[] bytesToDecompress) {
return decompressToStringForZlib(bytesToDecompress, "UTF-8");
}
/**
* zlib decompress 2 String
*
* @param bytesToDecompress
* @param charsetName
* @return
*/
public static String decompressToStringForZlib(byte[] bytesToDecompress, String charsetName) {
byte[] bytesDecompressed = decompressForZlib
(
bytesToDecompress
);
String returnValue = null;
try {
returnValue = new String
(
bytesDecompressed,
0,
bytesDecompressed.length,
charsetName
);
} catch (UnsupportedEncodingException uee) {
uee.printStackTrace();
}
return returnValue;
}
/**
* zlib decompress 2 byte
*
* @param bytesToDecompress
* @return
*/
public static byte[] decompressForZlib(byte[] bytesToDecompress) {
byte[] returnValues = null;
Inflater inflater = new Inflater();
int numberOfBytesToDecompress = bytesToDecompress.length;
inflater.setInput
(
bytesToDecompress,
0,
numberOfBytesToDecompress
);
int numberOfBytesDecompressedSoFar = 0;
List bytesDecompressedSoFar = new ArrayList<>();
try {
while (!inflater.needsInput()) {
byte[] bytesDecompressedBuffer = new byte[numberOfBytesToDecompress];
int numberOfBytesDecompressedThisTime = inflater.inflate
(
bytesDecompressedBuffer
);
numberOfBytesDecompressedSoFar += numberOfBytesDecompressedThisTime;
for (int b = 0; b < numberOfBytesDecompressedThisTime; b++) {
bytesDecompressedSoFar.add(bytesDecompressedBuffer[b]);
}
}
returnValues = new byte[bytesDecompressedSoFar.size()];
for (int b = 0; b < returnValues.length; b++) {
returnValues[b] = bytesDecompressedSoFar.get(b);
}
} catch (DataFormatException dfe) {
dfe.printStackTrace();
}
inflater.end();
return returnValues;
}
/**
* zlib compress 2 byte
*
* @param bytesToCompress
* @return
*/
public static byte[] compressForZlib(byte[] bytesToCompress) {
Deflater deflater = new Deflater();
deflater.setInput(bytesToCompress);
deflater.finish();
byte[] bytesCompressed = new byte[Short.MAX_VALUE];
int numberOfBytesAfterCompression = deflater.deflate(bytesCompressed);
byte[] returnValues = new byte[numberOfBytesAfterCompression];
System.arraycopy
(
bytesCompressed,
0,
returnValues,
0,
numberOfBytesAfterCompression
);
return returnValues;
}
/**
* zlib compress 2 byte
*
* @param stringToCompress
* @return
*/
public static byte[] compressForZlib(String stringToCompress) {
byte[] returnValues = null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
returnValues = compressForZlib
(
stringToCompress.getBytes(StandardCharsets.UTF_8)
);
} else {
returnValues = compressForZlib
(
stringToCompress.getBytes("UTF-8")
);
}
} catch (Exception e) {
e.printStackTrace();
}
return returnValues;
}
/**
* gzip compress 2 byte
*
* @param string
* @return
* @throws IOException
*/
public static byte[] compressForGzip(String string) {
ByteArrayOutputStream os = null;
GZIPOutputStream gos = null;
try {
os = new ByteArrayOutputStream(string.length());
gos = new GZIPOutputStream(os);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
gos.write(string.getBytes(StandardCharsets.UTF_8));
} else {
gos.write(string.getBytes("UTF-8"));
}
return os.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(gos);
closeQuietly(os);
}
return null;
}
/**
* gzip decompress 2 string
*
* @param compressed
* @return
* @throws IOException
*/
public static String decompressForGzip(byte[] compressed) {
return decompressForGzip(compressed, "UTF-8");
}
/**
* gzip decompress 2 string
*
* @param compressed
* @param charsetName
* @return
*/
public static String decompressForGzip(byte[] compressed, String charsetName) {
final int BUFFER_SIZE = compressed.length;
GZIPInputStream gis = null;
ByteArrayInputStream is = null;
try {
is = new ByteArrayInputStream(compressed);
gis = new GZIPInputStream(is, BUFFER_SIZE);
StringBuilder string = new StringBuilder();
byte[] data = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = gis.read(data)) != -1) {
string.append(new String(data, 0, bytesRead, charsetName));
}
return string.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(gis);
closeQuietly(is);
}
return null;
}
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
}
================================================
FILE: arms/src/main/java/com/jess/arms/widget/CustomPopupWindow.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.widget;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout.LayoutParams;
import android.widget.PopupWindow;
/**
* ================================================
* 因为继承于 {@link PopupWindow} ,所以它本身就是一个 {@link PopupWindow}
* 因此如果此类里封装的功能并不能满足您的需求(不想过多封装 UI 的东西,这里只提供思想,觉得不满足需求可以自己仿照着封装)
* 您可以直接调用 {@link PopupWindow} 的 Api 满足需求
*
* @see CustomPopupWindow wiki 官方文档
* Created by JessYan on 4/22/2016
* Contact me
* Follow me
* ================================================
*/
public class CustomPopupWindow extends PopupWindow {
private View mContentView;
private View mParentView;
private CustomPopupWindowListener mListener;
private boolean isOutsideTouch;
private boolean isFocus;
private Drawable mBackgroundDrawable;
private int mAnimationStyle;
private boolean isWrap;
private CustomPopupWindow(Builder builder) {
this.mContentView = builder.contentView;
this.mParentView = builder.parentView;
this.mListener = builder.listener;
this.isOutsideTouch = builder.isOutsideTouch;
this.isFocus = builder.isFocus;
this.mBackgroundDrawable = builder.backgroundDrawable;
this.mAnimationStyle = builder.animationStyle;
this.isWrap = builder.isWrap;
initLayout();
}
public static Builder builder() {
return new Builder();
}
/**
* 用于填充contentView,必须传ContextThemeWrapper(比如activity)不然popupwindow要报错
*
* @param context
* @param layoutId
* @return
*/
public static View inflateView(ContextThemeWrapper context, int layoutId) {
return LayoutInflater.from(context)
.inflate(layoutId, null);
}
private void initLayout() {
mListener.initPopupView(mContentView);
setWidth(isWrap ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT);
setHeight(isWrap ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT);
setFocusable(isFocus);
setOutsideTouchable(isOutsideTouch);
setBackgroundDrawable(mBackgroundDrawable);
if (mAnimationStyle != -1)//如果设置了动画则使用动画
{
setAnimationStyle(mAnimationStyle);
}
setContentView(mContentView);
}
/**
* 获得用于展示popup内容的view
*
* @return
*/
@Override
public View getContentView() {
return mContentView;
}
public void show() {//默认显示到中间
if (mParentView == null) {
showAtLocation(mContentView, Gravity.CENTER | Gravity.CENTER_HORIZONTAL, 0, 0);
} else {
showAtLocation(mParentView, Gravity.CENTER | Gravity.CENTER_HORIZONTAL, 0, 0);
}
}
public interface CustomPopupWindowListener {
void initPopupView(View contentView);
}
public static final class Builder {
private View contentView;
private View parentView;
private CustomPopupWindowListener listener;
private boolean isOutsideTouch = true;//默认为true
private boolean isFocus = true;//默认为true
private Drawable backgroundDrawable = new ColorDrawable(0x00000000);//默认为透明
private int animationStyle = -1;
private boolean isWrap;
private Builder() {
}
public Builder contentView(View contentView) {
this.contentView = contentView;
return this;
}
public Builder parentView(View parentView) {
this.parentView = parentView;
return this;
}
public Builder isWrap(boolean isWrap) {
this.isWrap = isWrap;
return this;
}
public Builder customListener(CustomPopupWindowListener listener) {
this.listener = listener;
return this;
}
public Builder isOutsideTouch(boolean isOutsideTouch) {
this.isOutsideTouch = isOutsideTouch;
return this;
}
public Builder isFocus(boolean isFocus) {
this.isFocus = isFocus;
return this;
}
public Builder backgroundDrawable(Drawable backgroundDrawable) {
this.backgroundDrawable = backgroundDrawable;
return this;
}
public Builder animationStyle(int animationStyle) {
this.animationStyle = animationStyle;
return this;
}
public CustomPopupWindow build() {
if (contentView == null) {
throw new IllegalStateException("ContentView is required");
}
if (listener == null) {
throw new IllegalStateException("CustomPopupWindowListener is required");
}
return new CustomPopupWindow(this);
}
}
}
================================================
FILE: arms/src/main/res/values/strings.xml
================================================
arms
================================================
FILE: arms-autolayout/.gitignore
================================================
/build
================================================
FILE: arms-autolayout/build.gradle
================================================
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
buildToolsVersion rootProject.ext.android["buildToolsVersion"]
defaultConfig {
minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
versionCode rootProject.ext.android["versionCode"]
versionName rootProject.ext.android["versionName"]
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compileOnly rootProject.ext.dependencies["appcompat"]
compileOnly rootProject.ext.dependencies["annotations"]
api(rootProject.ext.dependencies["design"]) {
exclude module: 'annotation'
exclude module: 'appcompat'
exclude module: 'legacy-support-v4'
exclude module: 'recyclerview'
}
api(rootProject.ext.dependencies["cardview"]) {
exclude module: 'annotation'
}
api(rootProject.ext.dependencies["autolayout"]) {
exclude module: 'recyclerview'
exclude module: 'appcompat'
exclude module: 'vectordrawable-animated'
}
}
apply from: '../bintray.gradle'
================================================
FILE: arms-autolayout/gradle.properties
================================================
POM_NAME=MVPArms-AndroidAutoLayout
================================================
FILE: arms-autolayout/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: arms-autolayout/src/main/AndroidManifest.xml
================================================
================================================
FILE: arms-autolayout/src/main/java/com/jess/arms/widget/autolayout/AutoAppBarLayout.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.widget.autolayout;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.material.appbar.AppBarLayout;
import com.zhy.autolayout.AutoLayoutInfo;
import com.zhy.autolayout.utils.AutoLayoutHelper;
/**
* ================================================
* 实现 AndroidAutoLayout 规范的 {@link AppBarLayout}
* 可使用 MVP_generator_solution 中的 AutoView 模版生成各种符合 AndroidAutoLayout 规范的 {@link View}
*
* @see AutoLayout wiki 官方文档
* Created by JessYan on 4/14/2016
* Contact me
* Follow me
* ================================================
*/
public class AutoAppBarLayout extends AppBarLayout {
private AutoLayoutHelper mHelper = new AutoLayoutHelper(this);
public AutoAppBarLayout(Context context) {
super(context);
}
public AutoAppBarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!isInEditMode()) {
mHelper.adjustChildren();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends AppBarLayout.LayoutParams
implements AutoLayoutHelper.AutoLayoutParams {
private AutoLayoutInfo mAutoLayoutInfo;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
}
@Override
public AutoLayoutInfo getAutoLayoutInfo() {
return mAutoLayoutInfo;
}
}
}
================================================
FILE: arms-autolayout/src/main/java/com/jess/arms/widget/autolayout/AutoCardView.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.widget.autolayout;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.cardview.widget.CardView;
import com.zhy.autolayout.AutoFrameLayout;
import com.zhy.autolayout.utils.AutoLayoutHelper;
/**
* ================================================
* 实现 AndroidAutoLayout 规范的 {@link CardView}
* 可使用 MVP_generator_solution 中的 AutoView 模版生成各种符合 AndroidAutoLayout 规范的 {@link View}
*
* @see AutoLayout wiki 官方文档
* Created by JessYan on 9/3/16 21:40
* Contact me
* Follow me
* ================================================
*/
public class AutoCardView extends CardView {
private final AutoLayoutHelper mHelper = new AutoLayoutHelper(this);
public AutoCardView(Context context) {
super(context);
}
public AutoCardView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AutoCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public AutoFrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new AutoFrameLayout.LayoutParams(getContext(), attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!isInEditMode()) {
mHelper.adjustChildren();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
================================================
FILE: arms-autolayout/src/main/java/com/jess/arms/widget/autolayout/AutoCollapsingToolbarLayout.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.widget.autolayout;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.zhy.autolayout.AutoLayoutInfo;
import com.zhy.autolayout.utils.AutoLayoutHelper;
/**
* ================================================
* 实现 AndroidAutoLayout 规范的 {@link CollapsingToolbarLayout}
* 可使用 MVP_generator_solution 中的 AutoView 模版生成各种符合 AndroidAutoLayout 规范的 {@link View}
*
* @see AutoLayout wiki 官方文档
* Created by JessYan on 4/14/2016
* Contact me
* Follow me
* ================================================
*/
public class AutoCollapsingToolbarLayout extends CollapsingToolbarLayout {
private AutoLayoutHelper mHelper = new AutoLayoutHelper(this);
public AutoCollapsingToolbarLayout(Context context) {
super(context);
}
public AutoCollapsingToolbarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AutoCollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!isInEditMode()) {
mHelper.adjustChildren();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends CollapsingToolbarLayout.LayoutParams
implements AutoLayoutHelper.AutoLayoutParams {
private AutoLayoutInfo mAutoLayoutInfo;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
}
@Override
public AutoLayoutInfo getAutoLayoutInfo() {
return mAutoLayoutInfo;
}
}
}
================================================
FILE: arms-autolayout/src/main/java/com/jess/arms/widget/autolayout/AutoRadioGroup.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.widget.autolayout;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RadioGroup;
import com.zhy.autolayout.AutoLayoutInfo;
import com.zhy.autolayout.utils.AutoLayoutHelper;
/**
* ================================================
* 实现 AndroidAutoLayout 规范的 {@link RadioGroup}
* 可使用 MVP_generator_solution 中的 AutoView 模版生成各种符合 AndroidAutoLayout 规范的 {@link View}
*
* @see AutoLayout wiki 官方文档
* Created by JessYan on 9/3/16 21:40
* Contact me
* Follow me
* ================================================
*/
public class AutoRadioGroup extends RadioGroup {
private AutoLayoutHelper mHelper = new AutoLayoutHelper(this);
public AutoRadioGroup(Context context) {
super(context);
}
public AutoRadioGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!isInEditMode()) {
mHelper.adjustChildren();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends RadioGroup.LayoutParams
implements AutoLayoutHelper.AutoLayoutParams {
private AutoLayoutInfo mAutoLayoutInfo;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
@Override
public AutoLayoutInfo getAutoLayoutInfo() {
return mAutoLayoutInfo;
}
}
}
================================================
FILE: arms-autolayout/src/main/java/com/jess/arms/widget/autolayout/AutoScrollView.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.widget.autolayout;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
import androidx.annotation.Nullable;
import com.zhy.autolayout.AutoLayoutInfo;
import com.zhy.autolayout.utils.AutoLayoutHelper;
/**
* ================================================
* 实现 AndroidAutoLayout 规范的 {@link ScrollView}
* 可使用 MVP_generator_solution 中的 AutoView 模版生成各种符合 AndroidAutoLayout 规范的 {@link View}
*
* @see AutoLayout wiki 官方文档
* Created by JessYan on 4/14/2016
* Contact me
* Follow me
* ================================================
*/
public class AutoScrollView extends ScrollView {
private AutoLayoutHelper mHelper = new AutoLayoutHelper(this);
public AutoScrollView(Context context) {
super(context);
}
public AutoScrollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public AutoScrollView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!isInEditMode()) {
mHelper.adjustChildren();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends ScrollView.LayoutParams
implements AutoLayoutHelper.AutoLayoutParams {
private AutoLayoutInfo mAutoLayoutInfo;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
@Override
public AutoLayoutInfo getAutoLayoutInfo() {
return mAutoLayoutInfo;
}
}
}
================================================
FILE: arms-autolayout/src/main/java/com/jess/arms/widget/autolayout/AutoTabLayout.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.widget.autolayout;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.google.android.material.tabs.TabLayout;
import com.zhy.autolayout.utils.AutoUtils;
import com.zhy.autolayout.utils.DimenUtils;
/**
* ================================================
* 实现 AndroidAutoLayout 规范的 {@link TabLayout}
* 可使用 MVP_generator_solution 中的 AutoView 模版生成各种符合 AndroidAutoLayout 规范的 {@link View}
*
* @see AutoLayout wiki 官方文档
* Created by JessYan on 4/14/2016
* Contact me
* Follow me
* ================================================
*/
public class AutoTabLayout extends TabLayout {
private static final int NO_VALID = -1;
private int mTextSize;
private boolean mTextSizeBaseWidth = false;
public AutoTabLayout(Context context) {
this(context, null);
}
public AutoTabLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initTextSizeBaseWidth(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout,
defStyleAttr, R.style.Widget_Design_TabLayout);
int tabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance,
R.style.TextAppearance_Design_Tab);
mTextSize = loadTextSizeFromTextAppearance(tabTextAppearance);
a.recycle();
}
private void initTextSizeBaseWidth(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoTabLayout);
mTextSizeBaseWidth = a.getBoolean(R.styleable.AutoTabLayout_auto_textSize_base_width, false);
a.recycle();
}
private int loadTextSizeFromTextAppearance(int textAppearanceResId) {
TypedArray a = getContext().obtainStyledAttributes(textAppearanceResId,
R.styleable.TextAppearance);
try {
if (!DimenUtils.isPxVal(a.peekValue(R.styleable.TextAppearance_android_textSize))) {
return NO_VALID;
}
return a.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize, NO_VALID);
} finally {
a.recycle();
}
}
@Override
public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
super.addTab(tab, position, setSelected);
setUpTabTextSize(tab);
}
@Override
public void addTab(@NonNull Tab tab, boolean setSelected) {
super.addTab(tab, setSelected);
setUpTabTextSize(tab);
}
private void setUpTabTextSize(Tab tab) {
if (mTextSize == NO_VALID || tab.getCustomView() != null) {
return;
}
ViewGroup tabGroup = (ViewGroup) getChildAt(0);
ViewGroup tabContainer = (ViewGroup) tabGroup.getChildAt(tab.getPosition());
TextView textView = (TextView) tabContainer.getChildAt(1);
if (AutoUtils.autoed(textView)) {
return;
}
int autoTextSize;
if (mTextSizeBaseWidth) {
autoTextSize = AutoUtils.getPercentWidthSize(mTextSize);
} else {
autoTextSize = AutoUtils.getPercentHeightSize(mTextSize);
}
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, autoTextSize);
}
}
================================================
FILE: arms-autolayout/src/main/java/com/jess/arms/widget/autolayout/AutoToolbar.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.widget.autolayout;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.widget.Toolbar;
import com.zhy.autolayout.AutoLayoutInfo;
import com.zhy.autolayout.utils.AutoLayoutHelper;
import com.zhy.autolayout.utils.AutoUtils;
import com.zhy.autolayout.utils.DimenUtils;
import java.lang.reflect.Field;
/**
* ================================================
* 实现 AndroidAutoLayout 规范的 {@link Toolbar}
* 可使用 MVP_generator_solution 中的 AutoView 模版生成各种符合 AndroidAutoLayout 规范的 {@link View}
*
* @see AutoLayout wiki 官方文档
* Created by JessYan on 4/14/2016
* Contact me
* Follow me
* ================================================
*/
public class AutoToolbar extends Toolbar {
private static final int NO_VALID = -1;
private final AutoLayoutHelper mHelper = new AutoLayoutHelper(this);
private int mTextSize;
private int mSubTextSize;
public AutoToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Toolbar,
defStyleAttr, R.style.Widget_AppCompat_Toolbar);
int titleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance,
R.style.TextAppearance_Widget_AppCompat_Toolbar_Title);
int subtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance,
R.style.TextAppearance_Widget_AppCompat_Toolbar_Subtitle);
mTextSize = loadTextSizeFromTextAppearance(titleTextAppearance);
mSubTextSize = loadTextSizeFromTextAppearance(subtitleTextAppearance);
a.recycle();
}
public AutoToolbar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoToolbar(Context context) {
this(context, null);
}
private int loadTextSizeFromTextAppearance(int textAppearanceResId) {
TypedArray a = getContext().obtainStyledAttributes(textAppearanceResId,
R.styleable.TextAppearance);
try {
if (!DimenUtils.isPxVal(a.peekValue(R.styleable.TextAppearance_android_textSize))) {
return NO_VALID;
}
return a.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize, NO_VALID);
} finally {
a.recycle();
}
}
private void setUpTitleTextSize() {
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title) && mTextSize != NO_VALID) {
setUpTitleTextSize("mTitleTextView", mTextSize);
}
CharSequence subtitle = getSubtitle();
if (!TextUtils.isEmpty(subtitle) && mSubTextSize != NO_VALID) {
setUpTitleTextSize("mSubtitleTextView", mSubTextSize);
}
}
private void setUpTitleTextSize(String name, int val) {
try {
//反射 Toolbar 的 TextView
Field f = getClass().getSuperclass().getDeclaredField(name);
f.setAccessible(true);
TextView textView = (TextView) f.get(this);
if (textView != null) {
int autoTextSize = AutoUtils.getPercentHeightSize(val);
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, autoTextSize);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!this.isInEditMode()) {
setUpTitleTextSize();
this.mHelper.adjustChildren();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(this.getContext(), attrs);
}
public static class LayoutParams extends Toolbar.LayoutParams implements AutoLayoutHelper.AutoLayoutParams {
private AutoLayoutInfo mDimenLayoutInfo;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
this.mDimenLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(android.view.ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
@Override
public AutoLayoutInfo getAutoLayoutInfo() {
return this.mDimenLayoutInfo;
}
}
}
================================================
FILE: arms-autolayout/src/main/res/values/attrs.xml
================================================
================================================
FILE: arms-autolayout/src/main/res/values/strings.xml
================================================
arms-autolayout
================================================
FILE: arms-imageloader-glide/.gitignore
================================================
/build
================================================
FILE: arms-imageloader-glide/build.gradle
================================================
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
buildToolsVersion rootProject.ext.android["buildToolsVersion"]
defaultConfig {
minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
versionCode rootProject.ext.android["versionCode"]
versionName rootProject.ext.android["versionName"]
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
api(rootProject.ext.dependencies["glide"]) {
exclude module: 'annotation'
exclude module: 'fragment'
exclude module: 'vectordrawable-animated'
}
compileOnly project(':arms')
}
apply from: '../bintray.gradle'
================================================
FILE: arms-imageloader-glide/gradle.properties
================================================
POM_NAME=MVPArms-ImageLoader-Glide
================================================
FILE: arms-imageloader-glide/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: arms-imageloader-glide/src/main/AndroidManifest.xml
================================================
================================================
FILE: arms-imageloader-glide/src/main/java/com/jess/arms/http/imageloader/glide/BlurTransformation.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http.imageloader.glide;
import android.graphics.Bitmap;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import com.jess.arms.utils.FastBlur;
import java.security.MessageDigest;
/**
* ================================================
* 高斯模糊
*
* Created by JessYan on 03/04/2018 15:14
* Contact me
* Follow me
* ================================================
*/
public class BlurTransformation extends BitmapTransformation {
public static final int DEFAULT_RADIUS = 15;
private static final String ID = BlurTransformation.class.getName();
private static final byte[] ID_BYTES = ID.getBytes(Key.CHARSET);
private int mRadius;
public BlurTransformation(@IntRange(from = 0) int radius) {
mRadius = radius;
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update(ID_BYTES);
}
@Override
protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
return FastBlur.doBlur(toTransform, mRadius, true);
}
@Override
public boolean equals(Object o) {
return o instanceof BlurTransformation;
}
@Override
public int hashCode() {
return ID.hashCode();
}
}
================================================
FILE: arms-imageloader-glide/src/main/java/com/jess/arms/http/imageloader/glide/CacheStrategy.java
================================================
package com.jess.arms.http.imageloader.glide;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Incremental change is better than ambitious failure.
*
* @author : MysticCoder
* @date : 2019/4/29
* @desc :0对应DiskCacheStrategy.all,1对应DiskCacheStrategy.NONE,2对应DiskCacheStrategy.SOURCE,3对应DiskCacheStrategy.RESULT
* see {@link com.bumptech.glide.load.engine.DiskCacheStrategy}
*/
public interface CacheStrategy {
int ALL = 0;
int NONE = 1;
int RESOURCE = 2;
int DATA = 3;
int AUTOMATIC = 4;
@IntDef({ALL, NONE, RESOURCE, DATA, AUTOMATIC})
@Retention(RetentionPolicy.SOURCE)
@interface Strategy {
}
}
================================================
FILE: arms-imageloader-glide/src/main/java/com/jess/arms/http/imageloader/glide/GlideImageLoaderStrategy.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http.imageloader.glide;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
import com.jess.arms.di.module.GlobalConfigModule;
import com.jess.arms.http.imageloader.BaseImageLoaderStrategy;
import com.jess.arms.http.imageloader.ImageConfig;
import com.jess.arms.utils.Preconditions;
import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Action;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
/**
* ================================================
* 此类只是简单的实现了 Glide 加载的策略,方便快速使用,但大部分情况会需要应对复杂的场景
* 这时可自行实现 {@link BaseImageLoaderStrategy} 和 {@link ImageConfig} 替换现有策略
*
* @see GlobalConfigModule.Builder#imageLoaderStrategy(BaseImageLoaderStrategy)
* Created by JessYan on 8/5/16 16:28
* Contact me
* Follow me
* ================================================
*/
public class GlideImageLoaderStrategy implements BaseImageLoaderStrategy, GlideAppliesOptions {
@Override
public void loadImage(@Nullable Context ctx, @Nullable ImageConfigImpl config) {
Preconditions.checkNotNull(ctx, "Context is required");
Preconditions.checkNotNull(config, "ImageConfigImpl is required");
Preconditions.checkNotNull(config.getImageView(), "ImageView is required");
GlideRequests requests;
requests = GlideArms.with(ctx);//如果context是activity则自动使用Activity的生命周期
GlideRequest glideRequest = requests.load(config.getUrl());
switch (config.getCacheStrategy()) {
//缓存策略
case CacheStrategy.NONE:
glideRequest.diskCacheStrategy(DiskCacheStrategy.NONE);
break;
case CacheStrategy.RESOURCE:
glideRequest.diskCacheStrategy(DiskCacheStrategy.RESOURCE);
break;
case CacheStrategy.DATA:
glideRequest.diskCacheStrategy(DiskCacheStrategy.DATA);
break;
case CacheStrategy.AUTOMATIC:
glideRequest.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC);
break;
default:
glideRequest.diskCacheStrategy(DiskCacheStrategy.ALL);
break;
}
if (config.isCrossFade()) {
glideRequest.transition(DrawableTransitionOptions.withCrossFade());
}
if (config.isCenterCrop()) {
glideRequest.centerCrop();
}
if (config.isCircle()) {
glideRequest.circleCrop();
}
if (config.isImageRadius()) {
glideRequest.transform(new RoundedCorners(config.getImageRadius()));
}
if (config.isBlurImage()) {
glideRequest.transform(new BlurTransformation(config.getBlurValue()));
}
if (config.getTransformation() != null) {//glide用它来改变图形的形状
glideRequest.transform(config.getTransformation());
}
if (config.getPlaceholder() != 0)//设置占位符
{
glideRequest.placeholder(config.getPlaceholder());
}
if (config.getErrorPic() != 0)//设置错误的图片
{
glideRequest.error(config.getErrorPic());
}
if (config.getFallback() != 0)//设置请求 url 为空图片
{
glideRequest.fallback(config.getFallback());
}
glideRequest
.into(config.getImageView());
}
@Override
public void clear(@Nullable final Context ctx, @Nullable ImageConfigImpl config) {
Preconditions.checkNotNull(ctx, "Context is required");
Preconditions.checkNotNull(config, "ImageConfigImpl is required");
if (config.getImageView() != null) {
GlideArms.get(ctx).getRequestManagerRetriever().get(ctx).clear(config.getImageView());
}
if (config.getImageViews() != null && config.getImageViews().length > 0) {//取消在执行的任务并且释放资源
for (ImageView imageView : config.getImageViews()) {
GlideArms.get(ctx).getRequestManagerRetriever().get(ctx).clear(imageView);
}
}
if (config.isClearDiskCache()) {//清除本地缓存
Completable.fromAction(new Action() {
@Override
public void run() throws Exception {
Glide.get(ctx).clearDiskCache();
}
}).subscribeOn(Schedulers.io()).subscribe();
}
if (config.isClearMemory()) {//清除内存缓存
Completable.fromAction(new Action() {
@Override
public void run() throws Exception {
Glide.get(ctx).clearMemory();
}
}).subscribeOn(AndroidSchedulers.mainThread()).subscribe();
}
}
@Override
public void applyGlideOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
Timber.i("applyGlideOptions");
}
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
Timber.i("registerComponents");
}
}
================================================
FILE: arms-imageloader-glide/src/main/java/com/jess/arms/http/imageloader/glide/ImageConfigImpl.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jess.arms.http.imageloader.glide;
import android.widget.ImageView;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import com.jess.arms.http.imageloader.BaseImageLoaderStrategy;
import com.jess.arms.http.imageloader.ImageConfig;
import com.jess.arms.http.imageloader.ImageLoader;
/**
* ================================================
* 这里存放图片请求的配置信息,可以一直扩展字段,如果外部调用时想让图片加载框架
* 做一些操作,比如清除缓存或者切换缓存策略,则可以定义一个 int 类型的变量,内部根据 switch(int) 做不同的操作
* 其他操作同理
*
* Created by JessYan on 8/5/16 15:19
* Contact me
* Follow me
* ================================================
*/
public class ImageConfigImpl extends ImageConfig {
private @CacheStrategy.Strategy
int cacheStrategy;//0对应DiskCacheStrategy.all,1对应DiskCacheStrategy.NONE,2对应DiskCacheStrategy.SOURCE,3对应DiskCacheStrategy.RESULT
private int fallback; //请求 url 为空,则使用此图片作为占位符
private int imageRadius;//图片每个圆角的大小
private int blurValue;//高斯模糊值, 值越大模糊效果越大
/**
* @see {@link Builder#transformation(BitmapTransformation)}
*/
@Deprecated
private BitmapTransformation transformation;//glide用它来改变图形的形状
private ImageView[] imageViews;
private boolean isCrossFade;//是否使用淡入淡出过渡动画
private boolean isCenterCrop;//是否将图片剪切为 CenterCrop
private boolean isCircle;//是否将图片剪切为圆形
private boolean isClearMemory;//清理内存缓存
private boolean isClearDiskCache;//清理本地缓存
private ImageConfigImpl(Builder builder) {
this.url = builder.url;
this.imageView = builder.imageView;
this.placeholder = builder.placeholder;
this.errorPic = builder.errorPic;
this.fallback = builder.fallback;
this.cacheStrategy = builder.cacheStrategy;
this.imageRadius = builder.imageRadius;
this.blurValue = builder.blurValue;
this.transformation = builder.transformation;
this.imageViews = builder.imageViews;
this.isCrossFade = builder.isCrossFade;
this.isCenterCrop = builder.isCenterCrop;
this.isCircle = builder.isCircle;
this.isClearMemory = builder.isClearMemory;
this.isClearDiskCache = builder.isClearDiskCache;
}
public static Builder builder() {
return new Builder();
}
public @CacheStrategy.Strategy
int getCacheStrategy() {
return cacheStrategy;
}
public BitmapTransformation getTransformation() {
return transformation;
}
public ImageView[] getImageViews() {
return imageViews;
}
public boolean isClearMemory() {
return isClearMemory;
}
public boolean isClearDiskCache() {
return isClearDiskCache;
}
public int getFallback() {
return fallback;
}
public int getBlurValue() {
return blurValue;
}
public boolean isBlurImage() {
return blurValue > 0;
}
public int getImageRadius() {
return imageRadius;
}
public boolean isImageRadius() {
return imageRadius > 0;
}
public boolean isCrossFade() {
return isCrossFade;
}
public boolean isCenterCrop() {
return isCenterCrop;
}
public boolean isCircle() {
return isCircle;
}
public static final class Builder {
private String url;
private ImageView imageView;
private int placeholder;
private int errorPic;
private int fallback; //请求 url 为空,则使用此图片作为占位符
private @CacheStrategy.Strategy
int cacheStrategy;//0对应DiskCacheStrategy.all,1对应DiskCacheStrategy.NONE,2对应DiskCacheStrategy.SOURCE,3对应DiskCacheStrategy.RESULT
private int imageRadius;//图片每个圆角的大小
private int blurValue;//高斯模糊值, 值越大模糊效果越大
/**
* @see {@link Builder#transformation(BitmapTransformation)}
*/
@Deprecated
private BitmapTransformation transformation;//glide用它来改变图形的形状
private ImageView[] imageViews;
private boolean isCrossFade;//是否使用淡入淡出过渡动画
private boolean isCenterCrop;//是否将图片剪切为 CenterCrop
private boolean isCircle;//是否将图片剪切为圆形
private boolean isClearMemory;//清理内存缓存
private boolean isClearDiskCache;//清理本地缓存
private Builder() {
}
public Builder url(String url) {
this.url = url;
return this;
}
public Builder placeholder(int placeholder) {
this.placeholder = placeholder;
return this;
}
public Builder errorPic(int errorPic) {
this.errorPic = errorPic;
return this;
}
public Builder fallback(int fallback) {
this.fallback = fallback;
return this;
}
public Builder imageView(ImageView imageView) {
this.imageView = imageView;
return this;
}
public Builder cacheStrategy(@CacheStrategy.Strategy int cacheStrategy) {
this.cacheStrategy = cacheStrategy;
return this;
}
public Builder imageRadius(int imageRadius) {
this.imageRadius = imageRadius;
return this;
}
public Builder blurValue(int blurValue) { //blurValue 建议设置为 15
this.blurValue = blurValue;
return this;
}
/**
* 给图片添加 Glide 独有的 BitmapTransformation
*
* 因为 BitmapTransformation 是 Glide 独有的类, 所以如果 BitmapTransformation 出现在 {@link ImageConfigImpl} 中
* 会使 {@link ImageLoader} 难以切换为其他图片加载框架, 在 {@link ImageConfigImpl} 中只能配置基础类型和 Android 包里的类
* 此 API 会在后面的版本中被删除, 请使用其他 API 替代
*
* @param transformation {@link BitmapTransformation}
* @deprecated 请使用 {@link #isCircle()}, {@link #isCenterCrop()}, {@link #isImageRadius()} 替代
* 如果有其他自定义 BitmapTransformation 的需求, 请自行扩展 {@link BaseImageLoaderStrategy}
*/
@Deprecated
public Builder transformation(BitmapTransformation transformation) {
this.transformation = transformation;
return this;
}
public Builder imageViews(ImageView... imageViews) {
this.imageViews = imageViews;
return this;
}
public Builder isCrossFade(boolean isCrossFade) {
this.isCrossFade = isCrossFade;
return this;
}
public Builder isCenterCrop(boolean isCenterCrop) {
this.isCenterCrop = isCenterCrop;
return this;
}
public Builder isCircle(boolean isCircle) {
this.isCircle = isCircle;
return this;
}
public Builder isClearMemory(boolean isClearMemory) {
this.isClearMemory = isClearMemory;
return this;
}
public Builder isClearDiskCache(boolean isClearDiskCache) {
this.isClearDiskCache = isClearDiskCache;
return this;
}
public ImageConfigImpl build() {
return new ImageConfigImpl(this);
}
}
}
================================================
FILE: arms-imageloader-glide/src/main/res/values/strings.xml
================================================
arms-glide
================================================
FILE: bintray.gradle
================================================
apply plugin: 'com.jfrog.bintray'
apply plugin: 'com.github.dcendents.android-maven'
Properties properties = new Properties()
boolean isHasFile = false
if (project.rootProject.findProject('local.properties') != null) {
isHasFile = true
properties.load(project.rootProject.file('local.properties').newDataInputStream())
}
def gitUrl = 'https://github.com/JessYanCoding/MVPArms.git' // Git仓库的url
def siteUrl = 'https://github.com/JessYanCoding/MVPArms' // 项目的主页
version = rootProject.ext.android["versionName"]
group = "me.jessyan"
bintray {
user = isHasFile ? properties.getProperty("bintray.user") : System.getenv("bintray_user")
key = isHasFile ? properties.getProperty("bintray.apikey") : System.getenv("bintray_apikey")
pkg {
repo = 'maven'
name = POM_NAME
licenses = ["Apache-2.0"]
websiteUrl = siteUrl
vcsUrl = gitUrl
publish = true // 是否是公开项目。
version {
name = rootProject.ext.android["versionName"]
desc = 'A common Architecture for Android Applications developing based on MVP,integrates many Open Source Projects( like Dagger2,RxJava,Retrofit... ),to make your developing quicker and easier.'
released = new Date()
vcsTag = 'v' + rootProject.ext.android["versionName"]
attributes = ['gradle-plugin': 'com.use.less:com.use.less.gradle:gradle-useless-plugin']
}
}
configurations = ['archives']
}
install {
repositories.mavenInstaller {
// This generates POM.xml with proper parameters
pom {
project {
packaging 'aar'
// Add your description here
name POM_NAME
description 'A common Architecture for Android Applications developing based on MVP,integrates many Open Source Projects( like Dagger2,RxJava,Retrofit... ),to make your developing quicker and easier.'
url siteUrl
// Set your license
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id 'JessYanCoding' //填写bintray或者github的用户名
name 'jessyan' //姓名,可以是中文
email 'jess.yan.effort@gmail.com'
}
}
scm {
connection gitUrl
developerConnection gitUrl
url siteUrl
}
}
}
}
}
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
task javadoc(type: Javadoc) {
failOnError false
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
archives sourcesJar
}
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: "config.gradle"
buildscript {
repositories {
google()
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.0'
//Gradle Android Maven plugin
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
//Gradle Bintray Plugin
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0'
}
}
allprojects {
tasks.withType(Test).configureEach {
maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
if (!project.hasProperty("createReports")) {
reports.html.required = false
reports.junitXml.required = false
}
}
repositories {
google()
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url "https://jitpack.io" }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
configurations.all {
resolutionStrategy {
eachDependency { details ->
// Force all of the primary libraries to use the same version.
if (details.requested.name == 'appcompat'
&& details.requested.name == 'annotation'
&& details.requested.name == 'recyclerview') {
details.useVersion rootProject.ext.version["androidXSdkVersion"]
}
}
}
}
================================================
FILE: config.gradle
================================================
ext {
android = [
compileSdkVersion: 29,
buildToolsVersion: "29.0.3",
minSdkVersion : 14,
targetSdkVersion : 29,
versionCode : 186,
versionName : "2.5.2"
]
version = [
androidSupportSdkVersion: "28.0.0",
androidXSdkVersion : "1.1.0",
retrofitSdkVersion : "2.7.2",
dagger2SdkVersion : "2.26",
glideSdkVersion : "4.11.0",
butterknifeSdkVersion : "10.2.1",
rxlifecycleSdkVersion : "1.0",
rxlifecycle2SdkVersion : "2.2.2",
espressoSdkVersion : "3.0.1",
canarySdkVersion : "2.2"
]
dependencies = [
//support
"appcompat-v7" : "com.android.support:appcompat-v7:${version["androidSupportSdkVersion"]}",
"support-design" : "com.android.support:design:${version["androidSupportSdkVersion"]}",
"support-v4" : "com.android.support:support-v4:${version["androidSupportSdkVersion"]}",
"cardview-v7" : "com.android.support:cardview-v7:${version["androidSupportSdkVersion"]}",
"support-annotations" : "com.android.support:support-annotations:${version["androidSupportSdkVersion"]}",
"recyclerview-v7" : "com.android.support:recyclerview-v7:${version["androidSupportSdkVersion"]}",
//androidx
"appcompat" : "androidx.appcompat:appcompat:${version["androidXSdkVersion"]}",
"design" : "com.google.android.material:material:${version["androidXSdkVersion"]}",
"legacy-support-v4" : "androidx.legacy:legacy-support-v4:1.0.0",
"cardview" : "androidx.cardview:cardview:1.0.0",
"annotations" : "androidx.annotation:annotation:${version["androidXSdkVersion"]}",
"recyclerview" : "androidx.recyclerview:recyclerview:${version["androidXSdkVersion"]}",
//network
"retrofit" : "com.squareup.retrofit2:retrofit:${version["retrofitSdkVersion"]}",
"retrofit-converter-gson" : "com.squareup.retrofit2:converter-gson:${version["retrofitSdkVersion"]}",
"retrofit-adapter-rxjava" : "com.squareup.retrofit2:adapter-rxjava:${version["retrofitSdkVersion"]}",
"retrofit-adapter-rxjava2" : "com.squareup.retrofit2:adapter-rxjava2:${version["retrofitSdkVersion"]}",
"okhttp3" : "com.squareup.okhttp3:okhttp:3.12.9",
"okhttp4" : "com.squareup.okhttp3:okhttp:4.0.0",
"okhttp-urlconnection" : "com.squareup.okhttp:okhttp-urlconnection:2.0.0",
"glide" : "com.github.bumptech.glide:glide:${version["glideSdkVersion"]}",
"glide-compiler" : "com.github.bumptech.glide:compiler:${version["glideSdkVersion"]}",
"glide-loader-okhttp3" : "com.github.bumptech.glide:okhttp3-integration:${version["glideSdkVersion"]}",
"picasso" : "com.squareup.picasso:picasso:2.5.2",
//view
"autolayout" : "com.zhy:autolayout:1.4.5",
"butterknife" : "com.jakewharton:butterknife:${version["butterknifeSdkVersion"]}",
"butterknife-compiler" : "com.jakewharton:butterknife-compiler:${version["butterknifeSdkVersion"]}",
"pickerview" : "com.contrarywind:Android-PickerView:3.2.5",
"photoview" : "com.github.chrisbanes.photoview:library:1.2.3",
"numberprogressbar" : "com.daimajia.numberprogressbar:library:1.2@aar",
"nineoldandroids" : "com.nineoldandroids:library:2.4.0",
"paginate" : "com.github.markomilos:paginate:0.5.1",
"vlayout" : "com.alibaba.android:vlayout:1.1.0@aar",
"autosize" : "me.jessyan:autosize:1.2.1",
//rx1
"rxandroid" : "io.reactivex:rxandroid:1.2.1",
"rxjava" : "io.reactivex:rxjava:1.3.0",
"rxlifecycle" : "com.trello:rxlifecycle:${version["rxlifecycleSdkVersion"]}",
"rxlifecycle-components" : "com.trello:rxlifecycle-components:${version["rxlifecycleSdkVersion"]}",
"rxcache" : "com.github.VictorAlbertos.RxCache:runtime:1.7.0-1.x",
"rxcache-jolyglot-gson" : "com.github.VictorAlbertos.Jolyglot:gson:0.0.4",
"rxbinding-recyclerview-v7": "com.jakewharton.rxbinding:rxbinding-recyclerview-v7:1.0.1",
"rxpermissions" : "com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar",
"rxerrorhandler" : "me.jessyan:rxerrorhandler:1.0.1",
//rx2
"rxandroid2" : "io.reactivex.rxjava2:rxandroid:2.1.1",
"rxjava2" : "io.reactivex.rxjava2:rxjava:2.2.18",
"rxlifecycle2" : "com.trello.rxlifecycle2:rxlifecycle:${version["rxlifecycle2SdkVersion"]}",
"rxlifecycle2-android" : "com.trello.rxlifecycle2:rxlifecycle-android:${version["rxlifecycle2SdkVersion"]}",
"rxlifecycle2-components" : "com.trello.rxlifecycle2:rxlifecycle-components:${version["rxlifecycle2SdkVersion"]}",
"rxcache2" : "com.github.VictorAlbertos.RxCache:runtime:1.8.3-2.x",
"rxpermissions2" : "com.github.tbruyelle:rxpermissions:0.10.2",
"rxerrorhandler2" : "me.jessyan:rxerrorhandler:2.1.1",
//tools
"dagger2" : "com.google.dagger:dagger:${version["dagger2SdkVersion"]}",
"dagger2-android" : "com.google.dagger:dagger-android:${version["dagger2SdkVersion"]}",
"dagger2-android-support" : "com.google.dagger:dagger-android-support:${version["dagger2SdkVersion"]}",
"dagger2-compiler" : "com.google.dagger:dagger-compiler:${version["dagger2SdkVersion"]}",
"dagger2-android-processor": "com.google.dagger:dagger-android-processor:${version["dagger2SdkVersion"]}",
"androideventbus" : "org.simple:androideventbus:1.0.5.1",
"eventbus" : "org.greenrobot:eventbus:3.2.0",
"otto" : "com.squareup:otto:1.3.8",
"gson" : "com.google.code.gson:gson:2.8.6",
"multidex" : "com.android.support:multidex:1.0.3",
"javax.annotation" : "javax.annotation:jsr250-api:1.0",
"arouter" : "com.alibaba:arouter-api:1.3.1",
"arouter-compiler" : "com.alibaba:arouter-compiler:1.1.4",
"progressmanager" : "me.jessyan:progressmanager:1.5.0",
"retrofit-url-manager" : "me.jessyan:retrofit-url-manager:1.4.0",
"lifecyclemodel" : "me.jessyan:lifecyclemodel:1.0.1",
//test
"junit" : "junit:junit:4.12",
"androidJUnitRunner" : "androidx.test.runner.AndroidJUnitRunner",
"runner" : "com.android.support.test:runner:1.0.1",
"espresso-core" : "com.android.support.test.espresso:espresso-core:${version["espressoSdkVersion"]}",
"espresso-contrib" : "com.android.support.test.espresso:espresso-contrib:${version["espressoSdkVersion"]}",
"espresso-intents" : "com.android.support.test.espresso:espresso-intents:${version["espressoSdkVersion"]}",
"mockito-core" : "org.mockito:mockito-core:1.+",
"timber" : "com.jakewharton.timber:timber:4.7.1",
"logger" : "com.orhanobut:logger:2.2.0",
"canary-debug" : "com.squareup.leakcanary:leakcanary-android:${version["canarySdkVersion"]}",
"canary-release" : "com.squareup.leakcanary:leakcanary-android-no-op:1.6.3",
"umeng-analytics" : "com.umeng.analytics:analytics:6.0.1"
]
}
================================================
FILE: demo/.gitignore
================================================
/build
================================================
FILE: demo/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
buildToolsVersion rootProject.ext.android["buildToolsVersion"]
useLibrary 'org.apache.http.legacy'
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId "me.jessyan.mvparms.demo"
minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
versionCode rootProject.ext.android["versionCode"]
versionName rootProject.ext.android["versionName"]
testInstrumentationRunner rootProject.ext.dependencies["androidJUnitRunner"]
}
buildTypes {
debug {
buildConfigField "boolean", "LOG_DEBUG", "true"
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
buildConfigField "boolean", "LOG_DEBUG", "false"
minifyEnabled true
shrinkResources true
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
disable 'InvalidPackage'
disable "ResourceType"
abortOnError false
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
//androidx
implementation rootProject.ext.dependencies["cardview"]
//tools
//注意 Arms 核心库现在并不会依赖某个 EventBus, 要想使用 EventBus, 还请在项目中自行依赖对应的 EventBus
//现在支持两种 EventBus, greenrobot 的 EventBus 和畅销书 《Android源码设计模式解析与实战》的作者 何红辉 所作的 AndroidEventBus
//依赖后 Arms 会自动检测您依赖的 EventBus 并自动注册
//建议使用 AndroidEventBus, 特别是组件化项目, 原因请看 https://github.com/hehonghui/AndroidEventBus/issues/49
//这种做法可以让使用者有自行选择三方库的权利, 并且还可以减轻 Arms 的体积
implementation rootProject.ext.dependencies["androideventbus"]
// implementation rootProject.ext.dependencies["eventbus"]
annotationProcessor rootProject.ext.dependencies["dagger2-compiler"]
implementation rootProject.ext.dependencies["progressmanager"]
implementation rootProject.ext.dependencies["retrofit-url-manager"]
implementation rootProject.ext.dependencies["lifecyclemodel"]
//view
annotationProcessor(rootProject.ext.dependencies["butterknife-compiler"]) {
exclude module: 'annotation'
}
implementation(rootProject.ext.dependencies["paginate"]) {
exclude module: 'recyclerview'
}
//arms
implementation project(':arms')
// implementation 'me.jessyan:arms:2.5.2'
//Arms 核心库不再包含 AndroidAutoLayout, 现在可自行选择屏幕适配方案, 不想使用 AndroidAutoLayout 就不要依赖 arms-autolayout
// implementation 'me.jessyan:arms-autolayout:2.5.2'
//现在已经将 Demo 中的屏幕适配框架从 AndroidAutoLayout 替换为 AndroidAutoSize, AndroidAutoLayout 和 AndroidAutoSize 可以在项目中共存
//所以旧项目只要依赖 arms-autolayout 即可兼容之前的旧页面, 新页面可以使用 AndroidAutoSize 进行适配, 等有时间了再将旧页面全部替换为 AndroidAutoSize
implementation rootProject.ext.dependencies["autosize"]
//Arms 核心库不再包含 Glide, 想使用其他图片加载框架或者想自行扩展 ImageLoaderStrategy 就不要依赖 arms-imageloader-glide
//依赖 arms-imageloader-glide 后还需要在 GlobalConfiguration 中手动注册 GlideImageLoaderStrategy
// implementation 'me.jessyan:arms-imageloader-glide:2.5.2'
implementation project(':arms-imageloader-glide')
//test
testImplementation rootProject.ext.dependencies["junit"]
debugImplementation rootProject.ext.dependencies["canary-debug"]
}
================================================
FILE: demo/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/jess/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# 混淆规则在 arms moudule 下的 proguard-rules.pro 中, 混淆前先参阅 https://github.com/JessYanCoding/MVPArms/wiki#1.5
================================================
FILE: demo/src/androidTest/java/me/jessyan/mvparms/demo/ApplicationTest.java
================================================
package me.jessyan.mvparms.demo;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: demo/src/main/AndroidManifest.xml
================================================
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/app/ActivityLifecycleCallbacksImpl.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.app;
import android.app.Activity;
import android.app.Application;
import android.os.Build;
import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Objects;
import me.jessyan.mvparms.demo.R;
import timber.log.Timber;
/**
* ================================================
* 展示 {@link Application.ActivityLifecycleCallbacks} 的用法
*
* Created by JessYan on 04/09/2017 17:14
* Contact me
* Follow me
* ================================================
*/
public class ActivityLifecycleCallbacksImpl implements Application.ActivityLifecycleCallbacks {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
Timber.i("%s - onActivityCreated", activity);
}
@Override
public void onActivityStarted(Activity activity) {
Timber.i("%s - onActivityStarted", activity);
if (!activity.getIntent().getBooleanExtra("isInitToolbar", false)) {
//由于加强框架的兼容性,故将 setContentView 放到 onActivityCreated 之后,onActivityStarted 之前执行
//而 findViewById 必须在 Activity setContentView() 后才有效,所以将以下代码从之前的 onActivityCreated 中移动到 onActivityStarted 中执行
activity.getIntent().putExtra("isInitToolbar", true);
//这里全局给Activity设置toolbar和title,你想象力有多丰富,这里就有多强大,以前放到BaseActivity的操作都可以放到这里
if (activity.findViewById(R.id.toolbar) != null) {
if (activity instanceof AppCompatActivity) {
((AppCompatActivity) activity).setSupportActionBar(activity.findViewById(R.id.toolbar));
Objects.requireNonNull(((AppCompatActivity) activity).getSupportActionBar()).setDisplayShowTitleEnabled(false);
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.setActionBar(activity.findViewById(R.id.toolbar));
Objects.requireNonNull(activity.getActionBar()).setDisplayShowTitleEnabled(false);
}
}
}
if (activity.findViewById(R.id.toolbar_title) != null) {
((TextView) activity.findViewById(R.id.toolbar_title)).setText(activity.getTitle());
}
if (activity.findViewById(R.id.toolbar_back) != null) {
activity.findViewById(R.id.toolbar_back).setOnClickListener(v -> activity.onBackPressed());
}
}
}
@Override
public void onActivityResumed(Activity activity) {
Timber.i("%s - onActivityResumed", activity);
}
@Override
public void onActivityPaused(Activity activity) {
Timber.i("%s - onActivityPaused", activity);
}
@Override
public void onActivityStopped(Activity activity) {
Timber.i("%s - onActivityStopped", activity);
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
Timber.i("%s - onActivitySaveInstanceState", activity);
}
@Override
public void onActivityDestroyed(Activity activity) {
Timber.i("%s - onActivityDestroyed", activity);
//横竖屏切换或配置改变时, Activity 会被重新创建实例, 但 Bundle 中的基础数据会被保存下来,移除该数据是为了保证重新创建的实例可以正常工作
activity.getIntent().removeExtra("isInitToolbar");
}
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/app/AppLifecyclesImpl.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.app;
import android.app.Application;
import android.content.Context;
import com.jess.arms.base.delegate.AppLifecycles;
import androidx.annotation.NonNull;
import butterknife.ButterKnife;
import me.jessyan.mvparms.demo.BuildConfig;
import timber.log.Timber;
/**
* ================================================
* 展示 {@link AppLifecycles} 的用法
*
* Created by JessYan on 04/09/2017 17:12
* Contact me
* Follow me
* ================================================
*/
public class AppLifecyclesImpl implements AppLifecycles {
@Override
public void attachBaseContext(@NonNull Context base) {
// MultiDex.install(base); //这里比 onCreate 先执行,常用于 MultiDex 初始化,插件化框架的初始化
}
@Override
public void onCreate(@NonNull Application application) {
if (BuildConfig.LOG_DEBUG) {//Timber初始化
//Timber 是一个日志框架容器,外部使用统一的Api,内部可以动态的切换成任何日志框架(打印策略)进行日志打印
//并且支持添加多个日志框架(打印策略),做到外部调用一次 Api,内部却可以做到同时使用多个策略
//比如添加三个策略,一个打印日志,一个将日志保存本地,一个将日志上传服务器
Timber.plant(new Timber.DebugTree());
// 如果你想将框架切换为 Logger 来打印日志,请使用下面的代码,如想切换为其他日志框架请根据下面的方式扩展
// Logger.addLogAdapter(new AndroidLogAdapter());
// Timber.plant(new Timber.DebugTree() {
// @Override
// protected void log(int priority, String tag, String message, Throwable t) {
// Logger.log(priority, tag, message, t);
// }
// });
ButterKnife.setDebug(true);
}
//LeakCanary v2.0+ 版本会自动完成框架的初始化, 以及对 Activity#onDestroy、Fragment#onDestroy、Fragment#onDestroyView 的监听
//原理和 AndroidAutoSize 一致, 所以注释掉下面 v1.0 的初始化代码
//使用 IntelligentCache.KEY_KEEP 作为 key 的前缀, 可以使储存的数据永久存储在内存中
//否则存储在 LRU 算法的存储空间中, 前提是 extras 使用的是 IntelligentCache (框架默认使用)
// ArmsUtils.obtainAppComponentFromContext(application).extras()
// .put(IntelligentCache.getKeyOfKeep(RefWatcher.class.getName())
// , BuildConfig.USE_CANARY ? LeakCanary.install(application) : RefWatcher.DISABLED);
}
@Override
public void onTerminate(@NonNull Application application) {
}
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/app/EventBusTags.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.app;
/**
* ================================================
* 放置 AndroidEventBus 的 Tag, 便于检索
* Arms 核心库现在并不会依赖某个 EventBus, 要想使用 EventBus, 还请在项目中自行依赖对应的 EventBus
* 现在支持两种 EventBus, greenrobot 的 EventBus 和畅销书 《Android源码设计模式解析与实战》的作者 何红辉 所作的 AndroidEventBus
*
* @see EventBusTags wiki 官方文档
* Created by JessYan on 8/30/2016 16:39
* Contact me
* Follow me
* ================================================
*/
public interface EventBusTags {
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/app/FragmentLifecycleCallbacksImpl.java
================================================
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.app;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import org.jetbrains.annotations.NotNull;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import timber.log.Timber;
/**
* ================================================
* 展示 {@link FragmentManager.FragmentLifecycleCallbacks} 的用法
*
* Created by JessYan on 23/08/2018 17:14
* Contact me
* Follow me
* ================================================
*/
public class FragmentLifecycleCallbacksImpl extends FragmentManager.FragmentLifecycleCallbacks {
@Override
public void onFragmentAttached(@NotNull @NonNull FragmentManager fm, Fragment f, @NotNull Context context) {
Timber.i("%s - onFragmentAttached", f.toString());
}
@Override
public void onFragmentCreated(@NotNull FragmentManager fm, Fragment f, Bundle savedInstanceState) {
Timber.i("%s - onFragmentCreated", f.toString());
// 在配置变化的时候将这个 Fragment 保存下来,在 Activity 由于配置变化重建时重复利用已经创建的 Fragment。
// https://developer.android.com/reference/android/app/Fragment.html?hl=zh-cn#setRetainInstance(boolean)
// 如果在 XML 中使用 标签,的方式创建 Fragment 请务必在标签中加上 android:id 或者 android:tag 属性,否则 setRetainInstance(true) 无效
// 在 Activity 中绑定少量的 Fragment 建议这样做,如果需要绑定较多的 Fragment 不建议设置此参数,如 ViewPager 需要展示较多 Fragment
f.setRetainInstance(true);
}
@Override
public void onFragmentViewCreated(@NotNull FragmentManager fm, Fragment f, @NotNull View v, Bundle savedInstanceState) {
Timber.i("%s - onFragmentViewCreated", f.toString());
}
@Override
public void onFragmentActivityCreated(@NotNull FragmentManager fm, Fragment f, Bundle savedInstanceState) {
Timber.i("%s - onFragmentActivityCreated", f.toString());
}
@Override
public void onFragmentStarted(@NotNull FragmentManager fm, Fragment f) {
Timber.i("%s - onFragmentStarted", f.toString());
}
@Override
public void onFragmentResumed(@NotNull FragmentManager fm, Fragment f) {
Timber.i("%s - onFragmentResumed", f.toString());
}
@Override
public void onFragmentPaused(@NotNull FragmentManager fm, Fragment f) {
Timber.i("%s - onFragmentPaused", f.toString());
}
@Override
public void onFragmentStopped(@NotNull FragmentManager fm, Fragment f) {
Timber.i("%s - onFragmentStopped", f.toString());
}
@Override
public void onFragmentSaveInstanceState(@NotNull FragmentManager fm, Fragment f, @NotNull Bundle outState) {
Timber.i("%s - onFragmentSaveInstanceState", f.toString());
}
@Override
public void onFragmentViewDestroyed(@NotNull FragmentManager fm, Fragment f) {
Timber.i("%s - onFragmentViewDestroyed", f.toString());
}
@Override
public void onFragmentDestroyed(@NotNull FragmentManager fm, Fragment f) {
Timber.i("%s - onFragmentDestroyed", f.toString());
}
@Override
public void onFragmentDetached(@NotNull FragmentManager fm, Fragment f) {
Timber.i("%s - onFragmentDetached", f.toString());
}
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/app/GlobalConfiguration.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.app;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
import com.jess.arms.base.delegate.AppLifecycles;
import com.jess.arms.di.module.GlobalConfigModule;
import com.jess.arms.http.imageloader.glide.GlideImageLoaderStrategy;
import com.jess.arms.http.log.RequestInterceptor;
import com.jess.arms.integration.ConfigModule;
import java.util.List;
import java.util.concurrent.TimeUnit;
import me.jessyan.mvparms.demo.BuildConfig;
import me.jessyan.mvparms.demo.mvp.model.api.Api;
import me.jessyan.progressmanager.ProgressManager;
import me.jessyan.retrofiturlmanager.RetrofitUrlManager;
/**
* ================================================
* App 的全局配置信息在此配置, 需要将此实现类声明到 AndroidManifest 中
* ConfigModule 的实现类可以有无数多个, 在 Application 中只是注册回调, 并不会影响性能 (多个 ConfigModule 在多 Module 环境下尤为受用)
* ConfigModule 接口的实现类对象是通过反射生成的, 这里会有些性能损耗
*
* @see com.jess.arms.base.delegate.AppDelegate
* @see com.jess.arms.integration.ManifestParser
* @see 请配合官方 Wiki 文档学习本框架
* @see 更新日志, 升级必看!
* @see 常见 Issues, 踩坑必看!
* @see MVPArms 官方组件化方案 ArmsComponent, 进阶指南!
* Created by JessYan on 12/04/2017 17:25
* Contact me
* Follow me
* ================================================
*/
public final class GlobalConfiguration implements ConfigModule {
// public static String sDomain = Api.APP_DOMAIN;
@Override
public void applyOptions(@NonNull Context context, @NonNull GlobalConfigModule.Builder builder) {
if (!BuildConfig.LOG_DEBUG) { //Release 时, 让框架不再打印 Http 请求和响应的信息
builder.printHttpLogLevel(RequestInterceptor.Level.NONE);
}
builder.baseurl(Api.APP_DOMAIN)
//强烈建议自己自定义图片加载逻辑, 因为 arms-imageloader-glide 提供的 GlideImageLoaderStrategy 并不能满足复杂的需求
//请参考 https://github.com/JessYanCoding/MVPArms/wiki#3.4
.imageLoaderStrategy(new GlideImageLoaderStrategy())
//想支持多 BaseUrl, 以及运行时动态切换任意一个 BaseUrl, 请使用 https://github.com/JessYanCoding/RetrofitUrlManager
//如果 BaseUrl 在 App 启动时不能确定, 需要请求服务器接口动态获取, 请使用以下代码
//以下方式是 Arms 框架自带的切换 BaseUrl 的方式, 在整个 App 生命周期内只能切换一次, 若需要无限次的切换 BaseUrl, 以及各种复杂的应用场景还是需要使用 RetrofitUrlManager 框架
//以下代码只是配置, 还要使用 Okhttp (AppComponent 中提供) 请求服务器获取到正确的 BaseUrl 后赋值给 GlobalConfiguration.sDomain
//切记整个过程必须在第一次调用 Retrofit 接口之前完成, 如果已经调用过 Retrofit 接口, 此种方式将不能切换 BaseUrl
// .baseurl(new BaseUrl() {
// @Override
// public HttpUrl url() {
// return HttpUrl.parse(sDomain);
// }
// })
//可根据当前项目的情况以及环境为框架某些部件提供自定义的缓存策略, 具有强大的扩展性
// .cacheFactory(new Cache.Factory() {
// @NonNull
// @Override
// public Cache build(CacheType type) {
// switch (type.getCacheTypeId()){
// case CacheType.EXTRAS_TYPE_ID:
// return new IntelligentCache(500);
// case CacheType.CACHE_SERVICE_CACHE_TYPE_ID:
// return new Cache(type.calculateCacheSize(context));//自定义 Cache
// default:
// return new LruCache(200);
// }
// }
// })
//若觉得框架默认的打印格式并不能满足自己的需求, 可自行扩展自己理想的打印格式 (以下只是简单实现)
// .formatPrinter(new FormatPrinter() {
// @Override
// public void printJsonRequest(Request request, String bodyString) {
// Timber.i("printJsonRequest:" + bodyString);
// }
//
// @Override
// public void printFileRequest(Request request) {
// Timber.i("printFileRequest:" + request.url().toString());
// }
//
// @Override
// public void printJsonResponse(long chainMs, boolean isSuccessful, int code,
// String headers, MediaType contentType, String bodyString,
// List segments, String message, String responseUrl) {
// Timber.i("printJsonResponse:" + bodyString);
// }
//
// @Override
// public void printFileResponse(long chainMs, boolean isSuccessful, int code, String headers,
// List segments, String message, String responseUrl) {
// Timber.i("printFileResponse:" + responseUrl);
// }
// })
//可以自定义一个单例的线程池供全局使用
// .executorService(Executors.newCachedThreadPool())
//这里提供一个全局处理 Http 请求和响应结果的处理类, 可以比客户端提前一步拿到服务器返回的结果, 可以做一些操作, 比如 Token 超时后, 重新获取 Token
.globalHttpHandler(new GlobalHttpHandlerImpl(context))
//用来处理 RxJava 中发生的所有错误, RxJava 中发生的每个错误都会回调此接口
//RxJava 必须要使用 ErrorHandleSubscriber (默认实现 Subscriber 的 onError 方法), 此监听才生效
.responseErrorListener(new ResponseErrorListenerImpl())
.gsonConfiguration((context1, gsonBuilder) -> {//这里可以自己自定义配置 Gson 的参数
gsonBuilder
.serializeNulls()//支持序列化值为 null 的参数
.enableComplexMapKeySerialization();//支持将序列化 key 为 Object 的 Map, 默认只能序列化 key 为 String 的 Map
})
.retrofitConfiguration((context1, retrofitBuilder) -> {//这里可以自己自定义配置 Retrofit 的参数, 甚至您可以替换框架配置好的 OkHttpClient 对象 (但是不建议这样做, 这样做您将损失框架提供的很多功能)
// retrofitBuilder.addConverterFactory(FastJsonConverterFactory.create());//比如使用 FastJson 替代 Gson
})
.okhttpConfiguration((context1, okhttpBuilder) -> {//这里可以自己自定义配置 Okhttp 的参数
// okhttpBuilder.sslSocketFactory(); //支持 Https, 详情请百度
okhttpBuilder.writeTimeout(10, TimeUnit.SECONDS);
//使用一行代码监听 Retrofit/Okhttp 上传下载进度监听,以及 Glide 加载进度监听, 详细使用方法请查看 https://github.com/JessYanCoding/ProgressManager
ProgressManager.getInstance().with(okhttpBuilder);
//让 Retrofit 同时支持多个 BaseUrl 以及动态改变 BaseUrl, 详细使用方法请查看 https://github.com/JessYanCoding/RetrofitUrlManager
RetrofitUrlManager.getInstance().with(okhttpBuilder);
})
.rxCacheConfiguration((context1, rxCacheBuilder) -> {//这里可以自己自定义配置 RxCache 的参数
rxCacheBuilder.useExpiredDataIfLoaderNotAvailable(true);
//想自定义 RxCache 的缓存文件夹或者解析方式, 如改成 FastJson, 请 return rxCacheBuilder.persistence(cacheDirectory, new FastJsonSpeaker());
//否则请 return null;
return null;
});
}
@Override
public void injectAppLifecycle(@NonNull Context context, @NonNull List lifecycles) {
//AppLifecycles 中的所有方法都会在基类 Application 的对应生命周期中被调用, 所以在对应的方法中可以扩展一些自己需要的逻辑
//可以根据不同的逻辑添加多个实现类
lifecycles.add(new AppLifecyclesImpl());
}
@Override
public void injectActivityLifecycle(@NonNull Context context, @NonNull List lifecycles) {
//ActivityLifecycleCallbacks 中的所有方法都会在 Activity (包括三方库) 的对应生命周期中被调用, 所以在对应的方法中可以扩展一些自己需要的逻辑
//可以根据不同的逻辑添加多个实现类
lifecycles.add(new ActivityLifecycleCallbacksImpl());
}
@Override
public void injectFragmentLifecycle(@NonNull Context context, @NonNull List lifecycles) {
//FragmentLifecycleCallbacks 中的所有方法都会在 Fragment (包括三方库) 的对应生命周期中被调用, 所以在对应的方法中可以扩展一些自己需要的逻辑
//可以根据不同的逻辑添加多个实现类
lifecycles.add(new FragmentLifecycleCallbacksImpl());
}
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/app/GlobalHttpHandlerImpl.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.app;
import android.content.Context;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.reflect.TypeToken;
import com.jess.arms.http.GlobalHttpHandler;
import com.jess.arms.http.log.RequestInterceptor;
import com.jess.arms.utils.ArmsUtils;
import java.util.List;
import me.jessyan.mvparms.demo.mvp.model.entity.User;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
/**
* ================================================
* 展示 {@link GlobalHttpHandler} 的用法
*
* Created by JessYan on 04/09/2017 17:06
* Contact me
* Follow me
* ================================================
*/
public class GlobalHttpHandlerImpl implements GlobalHttpHandler {
private Context context;
public GlobalHttpHandlerImpl(Context context) {
this.context = context;
}
/**
* 这里可以先客户端一步拿到每一次 Http 请求的结果, 可以先解析成 Json, 再做一些操作, 如检测到 token 过期后
* 重新请求 token, 并重新执行请求
*
* @param httpResult 服务器返回的结果 (已被框架自动转换为字符串)
* @param chain {@link okhttp3.Interceptor.Chain}
* @param response {@link Response}
* @return {@link Response}
*/
@NonNull
@Override
public Response onHttpResultResponse(@Nullable String httpResult, @NonNull Interceptor.Chain chain, @NonNull Response response) {
if (!TextUtils.isEmpty(httpResult) && RequestInterceptor.isJson(response.body().contentType())) {
try {
List list = ArmsUtils.obtainAppComponentFromContext(context).gson().fromJson(httpResult, new TypeToken>() {
}.getType());
User user = list.get(0);
Timber.w("Result ------> " + user.getLogin() + " || Avatar_url------> " + user.getAvatarUrl());
} catch (Exception e) {
e.printStackTrace();
return response;
}
}
/* 这里如果发现 token 过期, 可以先请求最新的 token, 然后在拿新的 token 放入 Request 里去重新请求
注意在这个回调之前已经调用过 proceed(), 所以这里必须自己去建立网络请求, 如使用 Okhttp 使用新的 Request 去请求
create a new request and modify it accordingly using the new token
Request newRequest = chain.request().newBuilder().header("token", newToken)
.build();
retry the request
response.body().close();
如果使用 Okhttp 将新的请求, 请求成功后, 再将 Okhttp 返回的 Response return 出去即可
如果不需要返回新的结果, 则直接把参数 response 返回出去即可*/
return response;
}
/**
* 这里可以在请求服务器之前拿到 {@link Request}, 做一些操作比如给 {@link Request} 统一添加 token 或者 header 以及参数加密等操作
*
* @param chain {@link okhttp3.Interceptor.Chain}
* @param request {@link Request}
* @return {@link Request}
*/
@NonNull
@Override
public Request onHttpRequestBefore(@NonNull Interceptor.Chain chain, @NonNull Request request) {
/* 如果需要在请求服务器之前做一些操作, 则重新构建一个做过操作的 Request 并 return, 如增加 Header、Params 等请求信息, 不做操作则直接返回参数 request
return chain.request().newBuilder().header("token", tokenId)
.build(); */
return request;
}
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/app/ResponseErrorListenerImpl.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.app;
import android.content.Context;
import android.net.ParseException;
import com.google.gson.JsonIOException;
import com.google.gson.JsonParseException;
import com.jess.arms.utils.ArmsUtils;
import org.json.JSONException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import me.jessyan.rxerrorhandler.handler.listener.ResponseErrorListener;
import retrofit2.HttpException;
import timber.log.Timber;
/**
* ================================================
* 展示 {@link ResponseErrorListener} 的用法
*
* Created by JessYan on 04/09/2017 17:18
* Contact me
* Follow me
* ================================================
*/
public class ResponseErrorListenerImpl implements ResponseErrorListener {
@Override
public void handleResponseError(Context context, Throwable t) {
Timber.tag("Catch-Error").w(t);
//这里不光只能打印错误, 还可以根据不同的错误做出不同的逻辑处理
//这里只是对几个常用错误进行简单的处理, 展示这个类的用法, 在实际开发中请您自行对更多错误进行更严谨的处理
String msg = "未知错误";
if (t instanceof UnknownHostException) {
msg = "网络不可用";
} else if (t instanceof SocketTimeoutException) {
msg = "请求网络超时";
} else if (t instanceof HttpException) {
HttpException httpException = (HttpException) t;
msg = convertStatusCode(httpException);
} else if (t instanceof JsonParseException || t instanceof ParseException || t instanceof JSONException || t instanceof JsonIOException) {
msg = "数据解析错误";
}
ArmsUtils.snackbarText(msg);
}
private String convertStatusCode(HttpException httpException) {
String msg;
if (httpException.code() == 500) {
msg = "服务器发生错误";
} else if (httpException.code() == 404) {
msg = "请求地址不存在";
} else if (httpException.code() == 403) {
msg = "请求被服务器拒绝";
} else if (httpException.code() == 307) {
msg = "请求被重定向到其他页面";
} else {
msg = httpException.message();
}
return msg;
}
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/app/service/DemoService.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.app.service;
import com.jess.arms.base.BaseService;
/**
* ================================================
* 展示 {@link BaseService} 的用法
*
* Created by JessYan on 09/07/2016 16:59
* Contact me
* Follow me
* ================================================
*/
public class DemoService extends BaseService {
@Override
public void init() {
}
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/app/utils/RxUtils.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.app.utils;
import com.jess.arms.mvp.IView;
import com.jess.arms.utils.RxLifecycleUtils;
import com.trello.rxlifecycle2.LifecycleTransformer;
import com.trello.rxlifecycle2.RxLifecycle;
import io.reactivex.ObservableTransformer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
/**
* ================================================
* 放置便于使用 RxJava 的一些工具方法
*
* Created by JessYan on 11/10/2016 16:39
* Contact me
* Follow me
* ================================================
*/
public class RxUtils {
private RxUtils() {
}
public static ObservableTransformer applySchedulers(final IView view) {
return observable -> observable.subscribeOn(Schedulers.io())
.doOnSubscribe(disposable -> {
view.showLoading();//显示进度条
})
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.doFinally(() -> {
view.hideLoading();//隐藏进度条
}).compose(RxLifecycleUtils.bindToLifecycle(view));
}
/**
* 此方法已废弃
*
* @param view
* @param
* @return
* @see RxLifecycleUtils 此类可以实现 {@link RxLifecycle} 的所有功能, 使用方法和之前一致
* @deprecated Use {@link RxLifecycleUtils#bindToLifecycle(IView)} instead
*/
@Deprecated
public static LifecycleTransformer bindToLifecycle(IView view) {
return RxLifecycleUtils.bindToLifecycle(view);
}
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/di/component/UserComponent.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.di.component;
import com.jess.arms.di.component.AppComponent;
import com.jess.arms.di.scope.ActivityScope;
import dagger.BindsInstance;
import dagger.Component;
import me.jessyan.mvparms.demo.di.module.UserModule;
import me.jessyan.mvparms.demo.mvp.contract.UserContract;
import me.jessyan.mvparms.demo.mvp.ui.activity.UserActivity;
/**
* ================================================
* 展示 Component 的用法
*
* @see Component wiki 官方文档
* Created by JessYan on 09/04/2016 11:17
* Contact me
* Follow me
* ================================================
*/
@ActivityScope
@Component(modules = UserModule.class, dependencies = AppComponent.class)
public interface UserComponent {
void inject(UserActivity activity);
@Component.Builder
interface Builder {
@BindsInstance
UserComponent.Builder view(UserContract.View view);
UserComponent.Builder appComponent(AppComponent appComponent);
UserComponent build();
}
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/di/module/UserModule.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.di.module;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.jess.arms.di.scope.ActivityScope;
import com.tbruyelle.rxpermissions2.RxPermissions;
import java.util.ArrayList;
import java.util.List;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import me.jessyan.mvparms.demo.mvp.contract.UserContract;
import me.jessyan.mvparms.demo.mvp.model.UserModel;
import me.jessyan.mvparms.demo.mvp.model.entity.User;
import me.jessyan.mvparms.demo.mvp.ui.adapter.UserAdapter;
/**
* ================================================
* 展示 Module 的用法
*
* @see Module wiki 官方文档
* Created by JessYan on 09/04/2016 11:10
* Contact me
* Follow me
* ================================================
*/
@Module
public abstract class UserModule {
@ActivityScope
@Provides
static RxPermissions provideRxPermissions(UserContract.View view) {
return new RxPermissions((FragmentActivity) view.getActivity());
}
@ActivityScope
@Provides
static RecyclerView.LayoutManager provideLayoutManager(UserContract.View view) {
return new GridLayoutManager(view.getActivity(), 2);
}
@ActivityScope
@Provides
static List provideUserList() {
return new ArrayList<>();
}
@ActivityScope
@Provides
static RecyclerView.Adapter provideUserAdapter(List list) {
return new UserAdapter(list);
}
@Binds
abstract UserContract.Model bindUserModel(UserModel model);
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/mvp/contract/UserContract.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.mvp.contract;
import android.app.Activity;
import com.jess.arms.mvp.IModel;
import com.jess.arms.mvp.IView;
import com.tbruyelle.rxpermissions2.RxPermissions;
import java.util.List;
import io.reactivex.Observable;
import me.jessyan.mvparms.demo.mvp.model.entity.User;
/**
* ================================================
* 展示 Contract 的用法
*
* @see Contract wiki 官方文档
* Created by JessYan on 09/04/2016 10:47
* Contact me
* Follow me
* ================================================
*/
public interface UserContract {
//对于经常使用的关于UI的方法可以定义到IView中,如显示隐藏进度条,和显示文字消息
interface View extends IView {
void startLoadMore();
void endLoadMore();
Activity getActivity();
//申请权限
RxPermissions getRxPermissions();
}
//Model层定义接口,外部只需关心Model返回的数据,无需关心内部细节,如是否使用缓存
interface Model extends IModel {
Observable> getUsers(int lastIdQueried, boolean update);
}
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/mvp/model/UserModel.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.mvp.model;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.OnLifecycleEvent;
import com.jess.arms.di.scope.ActivityScope;
import com.jess.arms.integration.IRepositoryManager;
import com.jess.arms.mvp.BaseModel;
import java.util.List;
import javax.inject.Inject;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.functions.Function;
import io.rx_cache2.DynamicKey;
import io.rx_cache2.EvictDynamicKey;
import me.jessyan.mvparms.demo.mvp.contract.UserContract;
import me.jessyan.mvparms.demo.mvp.model.api.cache.CommonCache;
import me.jessyan.mvparms.demo.mvp.model.api.service.UserService;
import me.jessyan.mvparms.demo.mvp.model.entity.User;
import timber.log.Timber;
/**
* ================================================
* 展示 Model 的用法
*
* @see Model wiki 官方文档
* Created by JessYan on 09/04/2016 10:56
* Contact me
* Follow me
* ================================================
*/
@ActivityScope
public class UserModel extends BaseModel implements UserContract.Model {
public static final int USERS_PER_PAGE = 10;
@Inject
public UserModel(IRepositoryManager repositoryManager) {
super(repositoryManager);
}
@Override
public Observable> getUsers(int lastIdQueried, boolean update) {
//使用rxcache缓存,上拉刷新则不读取缓存,加载更多读取缓存
return Observable.just(mRepositoryManager
.obtainRetrofitService(UserService.class)
.getUsers(lastIdQueried, USERS_PER_PAGE))
.flatMap((Function>, ObservableSource>>) listObservable -> mRepositoryManager.obtainCacheService(CommonCache.class)
.getUsers(listObservable
, new DynamicKey(lastIdQueried)
, new EvictDynamicKey(update))
.map(listReply -> listReply.getData()));
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
void onPause() {
Timber.d("Release Resource");
}
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/mvp/model/api/Api.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.mvp.model.api;
/**
* ================================================
* 存放一些与 API 有关的东西,如请求地址,请求码等
*
* Created by JessYan on 08/05/2016 11:25
* Contact me
* Follow me
* ================================================
*/
public interface Api {
String APP_DOMAIN = "https://api.github.com";
String REQUEST_SUCCESS = "0";
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/mvp/model/api/cache/CommonCache.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.mvp.model.api.cache;
import java.util.List;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.rx_cache2.DynamicKey;
import io.rx_cache2.EvictProvider;
import io.rx_cache2.LifeCache;
import io.rx_cache2.Reply;
import io.rx_cache2.internal.RxCache;
import me.jessyan.mvparms.demo.mvp.model.entity.User;
/**
* ================================================
* 展示 {@link RxCache#using(Class)} 中需要传入的 Providers 的使用方式
*
* Created by JessYan on 08/30/2016 13:53
* Contact me
* Follow me
* ================================================
*/
public interface CommonCache {
@LifeCache(duration = 2, timeUnit = TimeUnit.MINUTES)
Observable>> getUsers(Observable> users, DynamicKey idLastUserQueried, EvictProvider evictProvider);
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/mvp/model/api/service/CommonService.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.mvp.model.api.service;
/**
* ================================================
* 存放通用的一些 API
*
* Created by JessYan on 08/05/2016 12:05
* Contact me
* Follow me
* ================================================
*/
public interface CommonService {
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/mvp/model/api/service/UserService.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.mvp.model.api.service;
import java.util.List;
import io.reactivex.Observable;
import me.jessyan.mvparms.demo.mvp.model.entity.User;
import retrofit2.Retrofit;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.Query;
/**
* ================================================
* 展示 {@link Retrofit#create(Class)} 中需要传入的 ApiService 的使用方式
* 存放关于用户的一些 API
*
* Created by JessYan on 08/05/2016 12:05
* Contact me
* Follow me
* ================================================
*/
public interface UserService {
String HEADER_API_VERSION = "Accept: application/vnd.github.v3+json";
@Headers({HEADER_API_VERSION})
@GET("/users")
Observable> getUsers(@Query("since") int lastIdQueried, @Query("per_page") int perPage);
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/mvp/model/entity/BaseResponse.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.mvp.model.entity;
import java.io.Serializable;
import me.jessyan.mvparms.demo.mvp.model.api.Api;
/**
* ================================================
* 如果你服务器返回的数据格式固定为这种方式(这里只提供思想,服务器返回的数据格式可能不一致,可根据自家服务器返回的格式作更改)
* 指定范型即可改变 {@code data} 字段的类型, 达到重用 {@link BaseResponse}, 如果你实在看不懂, 请忽略
*
* Created by JessYan on 26/09/2016 15:19
* Contact me
* Follow me
* ================================================
*/
public class BaseResponse implements Serializable {
private T data;
private String code;
private String msg;
public T getData() {
return data;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
/**
* 请求是否成功
*
* @return
*/
public boolean isSuccess() {
return code.equals(Api.REQUEST_SUCCESS);
}
}
================================================
FILE: demo/src/main/java/me/jessyan/mvparms/demo/mvp/model/entity/User.java
================================================
/*
* Copyright 2017 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.mvparms.demo.mvp.model.entity;
import org.jetbrains.annotations.NotNull;
/**
* ================================================
* User 实体类
*