安卓 Rxjava+Retrofit+Mvp网络框架搭建

 

说实话:

实习的时候,看到公司项目网络框架搭建真的是很羡慕(什么时候我自己也能搭建一个(≖‿≖)✧),所以在我屡战屡败的刻苦坚持下,终于肝出了这一篇秘籍,实现了普通的请求和单文件上传请求,第一篇文章先具体介绍一下mvp以及一些基类封装

目录:

一.项目结构

二.准备的Gradle

三.BaseView

四.BasePresenter

五.BaseActivity

六.Okhttp、Interceptor、Retrofit

七.BaseResponse与BaseObserver封装

八.上传请求解析

九.具体请求实例

 

讲道理:

 

MVP与MVC区别:

MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller。

 

                                                                                  项目结构:

 

                                                                               Gradle

                                                             不懂看这个哦,gradle配置详解

/*
apply plugin表示应用了一个插件,该插件一般有两种值可选:
com.android.application',表示该模块为应用程序模块,可以直接运行,打包得到的是.apk文件
com.android.library',表示该模块为库模块,只能作为代码库依附于别的应用程序模块来运行,打包得到的是.aar文件
 */
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    signingConfigs {// 自动化打包配置  想要debug  与release 版本互相切换 必须要  写这个配置
        debug {
            keyAlias 'key0'
            keyPassword '123456'
            storeFile file('yy.jks')  //将它放在app目录下才可以这样引入
            storePassword "123456"
        }
        release {
            keyAlias 'key0'
            keyPassword '123456'
            storeFile file('yy.jks')
            storePassword "123456"
        }
    }
    compileSdkVersion 28  //设置编译时用的Android版本
    defaultConfig {
        applicationId "com.ycb.mvpdemo"  //项目包名
        minSdkVersion 19   //项目最低支持的版本
        targetSdkVersion 28 //项目的目标使用版本
        versionCode 1   // 版本号
        versionName "1.0"  //版本名称
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        //表明要使用AndroidJUnitRunner进行单元测试
    }
    buildTypes {  // 正式/测试环境配置
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
        debug {
            signingConfig signingConfigs.debug
            buildConfigField("boolean", "LOG_DEBUG", "true")//配置Log日志
            buildConfigField("String", "DOMAIN_URL", "\"http://v.juhe.cn/toutiao/\"")// 配置URL前缀
        }

    }
}

//总体分成三部分  apply  android  dependencies

dependencies {
    //项目的依赖关系
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:support-v4:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.yanzhenjie:permission:1.0.5'
    implementation 'com.google.code.gson:gson:2.8.5'
    //Retrofit2
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
    //Rxjava
    implementation 'io.reactivex.rxjava2:rxjava:2.2.3'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
    //okhttp拦截器依赖
    implementation 'com.squareup.okhttp3:logging-interceptor:3.12.1'
    //butterknife
    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
    //chuck
    debugImplementation 'com.readystatesoftware.chuck:library:1.1.0'
    releaseImplementation 'com.readystatesoftware.chuck:library-no-op:1.1.0'
    //eventBus
    implementation 'org.greenrobot:eventbus:3.0.0'
    //Glide 4.6
    implementation 'com.github.bumptech.glide:glide:4.6.1'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    //动态权限申请库
    implementation 'pub.devrel:easypermissions:1.3.0'
}

                                                                                       BaseView

                                                                           封装一些常用的基础方法

public interface BaseView {
    /**
     * 显示dialog
     */
    void showLoading();

    /**
     * 显示下载文件dialog
     */

   // void showLoadingFileDialog();

    /**
     * 隐藏下载文件dialog
     */

   // void hideLoadingFileDialog();

    /**
     * 下载进度
     *
     * @param totalSize
     * @param downSize
     */

    //void onProgress(long totalSize, long downSize);

    /**
     * 隐藏 dialog
     */

    void hideLoading();

    void showError(String msg);
    
}

                                                                                    BasePresenter

public class BasePresenter<V extends BaseView> {

    public CompositeDisposable compositeDisposable;
    
    public V baseView;


    public BasePresenter(V baseView) {
        this.baseView = baseView;
    }

    /**
     * 解除绑定
     */
    public void detachView() {
        baseView = null;
        removeDisposable();
    }

    /**
     * 返回 view
     *
     * @return
     */
    public V getBaseView() {
        return baseView;
    }

    //observer 观察者  observable是被观察者
    //subscribeOn() 指定的就是发射事件的线程,observerOn 指定的就是订阅者接收事件的线程。
    //四种线程
    //Schedulers.io() 代表io操作的线程, 通常用于网络,读写文件等io密集型的操作;
    //Schedulers.computation() 代表CPU计算密集型的操作, 例如需要大量计算的操作;
    //Schedulers.newThread() 代表一个常规的新线程;
    //AndroidSchedulers.mainThread() 代表Android的主线程
    public void addDisposable(Observable<?> observable, BaseObserver observer) {
        if (compositeDisposable == null) {
            compositeDisposable = new CompositeDisposable();
        }
        compositeDisposable.add(observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(observer));


    }

    public void removeDisposable() {
        if (compositeDisposable != null) {
            compositeDisposable.dispose();  //在RxJava 2.x 中,新增的Disposable可以做到切断的操作,让Observer观察者不再接收上游事件
        }
    }

}

                                                                                BaseActivity

                                      BaseActivity里面有使用自定义注解显示布局和是否启用EventBus,

                                      还有引入了EasyPermissions进行权限管理,Butterknife管理控件。

因为不是每个Activity都需要使用EventBus,所以通过反射加注解的方式决定是否需要使用,抛出handleEvent方法,同时关于MvP的Presenter的调用方法,以及toolbar等常用方法。

public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity implements BaseView, EasyPermissions.PermissionCallbacks {

    public Context context;
    public P presenter;
    protected Unbinder unbinder;
    private boolean mIsOpenEventBus = false;
    private int mContentViewId ;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //如果是指定类型注解类型则返回true
        if (getClass().isAnnotationPresent(ActivityFragmentInject.class)) {
            //通过反射获取 反射对象的方法等  返回该类中与参数类型匹配的公有注解对象
            ActivityFragmentInject annotation = getClass().getAnnotation(ActivityFragmentInject.class);
            mContentViewId = annotation.contentViewId();
            mIsOpenEventBus = annotation.isOpenEventBus();
        } else {
            throw new RuntimeException("Class must add annotations of ActivityFragmentInitParams.class");
        }
        setContentView(mContentViewId);
        presenter=createPresenter();
        if (mIsOpenEventBus) {
            EventBus.getDefault().register(this);
        }
        unbinder = ButterKnife.bind(this);
        context = this;
        initView();
    }

    protected  P createPresenter(){
        return presenter;
    }


    public void initView() {
    }

    @Subscribe    //主线程执行
    private void onEventMainThread(EventBean eventBean) {
        if (eventBean != null) {
            handleEvent(eventBean);
        }
    }


    public void handleEvent(EventBean eventBean) {

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mIsOpenEventBus) {
            EventBus.getDefault().unregister(this);
        }
        if (presenter != null) {
            presenter.detachView();
        }
        if (unbinder != null) {
            unbinder.unbind();
        }
    }

    /**
     * @param s
     */
    public void showtoast(String s) {
        Toast.makeText(context, s, Toast.LENGTH_LONG).show();
    }

    public void initToolbar(Toolbar toolbar, TextView textView, String title, boolean isBack) {
        if (textView != null && title != null) {
            textView.setText(title);
        }
        if (toolbar != null) {
            toolbar.setTitle("");
            if (isBack) {
                toolbar.setNavigationIcon(R.drawable.ic_left);
                toolbar.setNavigationOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        finish();
                    }
                });
            }

        }
    }


    private void closeLoadingDialog() {
    /*   if (dialog!=null&&dialog.getDialog().isShowing()){
                   dialog.dismiss();
       }*/
    }

    //建议用自定义控件形式或者在比较耗时的操作时使用
    private void showLoadingDialog() {
        /*dialog = AnimationDialog.newInstance();
        //dialog.setOutCancel(false);
        dialog.show(getSupportFragmentManager(),"show");
*/
    }

    //显示loading  这里的方法都是实现BaseView里面的
    //目前可以通过
    @Override
    public void showLoading() {
        showLoadingDialog();
    }

    //隐藏loading
    @Override
    public void hideLoading() {
        closeLoadingDialog();
    }

    @Override
    public void showError(String msg) {
        showtoast(msg);
    }


    public void startActivity( Class<? extends Activity> cls) {
        if (context!=null) {
            Intent intent = new Intent();
            intent.setClass(context, cls);
            startActivity(intent);
        }
    }

    public  void startActivity( Class<? extends Activity> cls, Bundle bundle) {
        if (context != null && cls != null && bundle != null) {
            Intent intent = new Intent();
            intent.putExtras(bundle);
            intent.setClass(context, cls);
            startActivity(intent);
        }

    }

    // 将权限处理结果托管给EasyPermission
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        /**
         * @param requestCode 请求的code
         * @param permissions 一些列的请求权限
         * @param grantResults 用户授权的结果
         * @param receivers 监听的上下文,处理回调
         */
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    //权限申请成功
    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {

    }

    //权限申请失败时的回调
    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {

    }

    @Override
    protected void onResume() {
        super.onResume();
    }

}

 

上面介绍了mvp和一些基础类的封装,这里讲一下Rxjava+Retrofit的封装以及上传文件,还有在具体情况下的使用

讲两件注意事项:1.安卓9.0不支持http请求,当然可以解决    2.安卓7.0开始要用FileProvider

 

 

 

其实前面也写过关于这方面的文章,一直都有用聚合的新闻数据 ,也有很多人看了我写的文章,在这里也非常感谢大家(偷偷的说,大家用我的新闻key,在聚合工作台上面看到我的接口被调用了几千次,非常感谢大家),闲话不多说了,下面开始讲解

Retrofit使用:

 Retrofit的使用,自然少不了okhttp

    client = new OkHttpClient.Builder()
                //添加log拦截器   Http拦截器   Header拦截器(用于拦截token)
                .addInterceptor(new LogInterceptor())
                .addInterceptor(new ChuckInterceptor(BaseApplication.getAppContext()))
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .build();

这里隆重为大家推荐两个拦截器:ChuckInterceptor 、HeaderInterceptor

**
 * Created by asus on 2019/4/23.
 * 用户登陆状态保持思路
 * 首先在欢迎界面在用户信息缓存模块查找是否有token
 * 1.没有token  2.有token
 * 解决思路
 * 1.没有token 进入LoginActivity  登陆然后保存返回的用户信息
 * 2.有token 进入 主界面(默认主界面是有很多fragment)
 * 因为拦截器的原因 所以调用每个接口时 会添加token,所以每个接口的使用时成功的
 * 然后你在使用展示用户信息的那个fragment时   从用户缓存模块里面将需要展示的信息取出
 * 注意如果有什么地方对用户信息进行了修改 提交    则要对本地缓存的用户信息进行update
 *
 *
 * token失效了怎么办???  按照上面的逻辑还是会进入主界面的  但是任何接口操作 似乎都会报错
 * 解决办法 一:在BaseException 里面定义token 失效  对用户进行提示 重新登陆
 * 解决办法 二: 可不可以自动刷新  重新获取token   
 */
public class HeaderInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request.Builder  request;  //Builder  建造者模式  request请求 辅助类
        String  token="";  //从本地用户缓存信息里面取得
        if (token!=null&&!token.isEmpty()){
            request= chain.request().newBuilder().addHeader("usertoken","");  //根据具体情况填写
        }else {
            request = chain.request().newBuilder();
        }
        //  将修改后的请求  获得的Response响应数据  返回
        return chain.proceed(request.build());
        /*
        * 拦截器执行顺序 先后有序   从第一个添加的拦截器执行到最后一个拦截器  然后在把结果从最后一个一直返回到第一个  得到最终返回的Response
         */
    }
}

下面就是整体的Retrofit代码

public class ApiRetrofit {

    private static ApiRetrofit apiRetrofit;
    private Retrofit retrofit;
    private OkHttpClient client;
    private String TAG = "ApiRetrofit";


    public ApiRetrofit() {
        client = new OkHttpClient.Builder()
                //添加log拦截器   Http拦截器  动态切换BaseUrl拦截器  Header拦截器(用于拦截token)
                .addInterceptor(new LogInterceptor())
                .addInterceptor(new ChuckInterceptor(BaseApplication.getAppContext()))
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .build();

        retrofit = new Retrofit.Builder()
                .baseUrl(Domain.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                //支持RxJava2
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(client)
                .build();
    }

    public static ApiRetrofit getInstance() {
        if (apiRetrofit == null) {
            synchronized (Object.class) {
                if (apiRetrofit == null) {
                    apiRetrofit = new ApiRetrofit();
                }
            }
        }
        return apiRetrofit;
    }

    //封装泛型方法    普通的Retrofit写法是  ApiServer  apiserver  = retrofit.create(ApiServer.class);
    //Class<T>代表获取这个类型的类  意思是获取class 这个类型的类
    //T 代表一个类型
    //这个方法的意思是  返回一个T 代表的类型,由Class<T>决定,
    // 因为T 代表一个类型,你给它什么类型,它就代表什么类型,就返回什么类型
    public <T> T setCreate(Class<T> reqServer) {
        return retrofit.create(reqServer);
    }

}

关于Rxjava的封装主要是体现在封装BaseObserver,继承Disposableobserver,可以做到切断Rxjava之间的连接,以防止内存泄漏

public abstract class BaseObserver<T> extends DisposableObserver<BaseResponse<T>> {

    protected BaseView view;

    private boolean isShowDialog;


    public BaseObserver(BaseView view) {
        this.view = view;
    }

    public BaseObserver(BaseView view, boolean isShowDialog) {
        this.view = view;
        this.isShowDialog = isShowDialog;
    }

    @Override
    protected void onStart() {
        if (view != null && isShowDialog) {
            view.showLoading();
        }
    }

    @Override
    public void onNext(BaseResponse<T> response) {
        if (response.getReason().equals("成功的返回")){
            onSuccess(response.getResult());
        }else {
            onFailure(null,response.getError_code()+"");
        }
    }

    @Override
    public void onError(Throwable e) {
        if (view != null && isShowDialog) {
            view.hideLoading();
        }
        onFailure(e, RxExceptionUtil.exceptionHandler(e));


    }

    @Override
    public void onComplete() {
        if (view != null && isShowDialog) {
            view.hideLoading();
        }

    }


    public abstract void onSuccess(T result);   //成功的返回

    public abstract void onFailure(Throwable e,String errorMsg);  //报错信息
    
}

 

                                                       项目实例—单文件上传

 

Retrofit上传文件(带圆形进度条):

上传使用接口:

    @Multipart
    @POST("member/avatar")
    Observable<DetailsBean> uploadImage(@Part MultipartBody.Part file);
解释:
@Part("description") RequestBody description
@Part MultipartBody.Part file 作用是将数据封装成表单的形式提交给服务器
上传图片
OKHttp源码分析(二)RequestBody https://blog.csdn.net/luoj_616/article/details/80750144
RequestBody 用来提交文件和说明文件类型
RequestBody reqFile = RequestBody.create(MediaType.parse("image/*"), file); //说明要上传的数据类型 请求文件主体
MultipartBody.Part 作用是将数据封装成表单的形式提交给服务器
同时调用的这个方法就是 MultipartBody.Part 这个类型,肯定要创建这个类型啊 加上它的作用理解
createFormData 就是它的初始化创建方法 将数据封装成表单的形式提交给服务器
MultipartBody.Part body =
//第一个参数String类型 第二个参数 文件名 第三个参数 RequestBody
MultipartBody.Part.createFormData("file", file.getName(), reqFile);

理解完Retrofit上传的参数涵义,来实现我们的上传方法,但是Retrofit没有提供带进度的提示框哦,我们通过继承RequestBody类,重写writeTo方法即可获取上传进度!

public class ProgressRequestBody extends RequestBody {

    private File mFile;
    private String mPath;
    private String mMediaType;
    private UploadCallbacks mListener;
    private int mEachBufferSize = 1024;

    /*  RequestBody  三个核心方法  可以看看源码哦,内容不多,挺好理解的
    1,public abstract MediaType contentType()//数据类型
    2,public long contentLength()//数据长度
    3,public abstract void writeTo(BufferedSink sink)//写操作*/

    public interface UploadCallbacks {
        //正在进行
        void onProgressUpdate(int percentage);
        //出错
        void onError();
        //结束
        void onFinish();
    }

    public ProgressRequestBody(final File file, String mediaType, final UploadCallbacks listener) {
        mFile = file;
        mMediaType = mediaType;
        mListener = listener;
    }

    public ProgressRequestBody(final File file, String mediaType, int eachBufferSize, final UploadCallbacks listener) {
        mFile = file;
        mMediaType = mediaType;
        mEachBufferSize = eachBufferSize;
        mListener = listener;
    }
    @Override
    public MediaType contentType() {
        return MediaType.parse(mMediaType);  //说明提交文件类型
    }

    //这个方法执行的是写操作
    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        long fileLength = mFile.length();
        byte[] buffer = new byte[mEachBufferSize];
        FileInputStream in = new FileInputStream(mFile);
        long uploaded = 0;
        int read;
        try {
            Handler handler = new Handler(Looper.getMainLooper());
            while ((read = in.read(buffer)) != -1) {  //为-1时说明文件已经读完
                //一次传进去一个1024长度的 buffer 而不是往buffer里边传
                handler.post(new ProgressUpdater(uploaded, fileLength));  //post 传一个Runnable  到主线程进行处理
                uploaded += read;
                sink.write(buffer, 0, read);  //将写入buffer 个数据,从offset开始  到 read
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            in.close();
        }

    }

    private class ProgressUpdater implements Runnable {
        private long mUploaded;
        private long mTotal;

        public ProgressUpdater(long uploaded, long total) {
            mUploaded = uploaded;
            mTotal = total;
        }

        @Override
        public void run() {
            mListener.onProgressUpdate((int) (100 * mUploaded / mTotal));
        }
    }
}

AffairUtil 查询本地图片类:用Rxjava做这些异步操作挺好用的,还可以用于数据库存大量数据时的子线程操作

public class AffairUtil {

    public void SelectImage(final Context mContext) {
        final List<String> mPhotoPathList = new ArrayList<>();
        //第一步初始化被观察者
        Observable.create(new ObservableOnSubscribe<List<String>>() {
            @Override   //被观察者通过这个发送消息给
            public void subscribe(ObservableEmitter<List<String>> emitter) throws Exception {
                //String path= PictureUtil.getImagePath(context,uri,null);
                Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                ContentResolver mContentResolver = mContext.getContentResolver();
                //只查询jpeg和png的图片  
                Cursor mCursor = mContentResolver.query(mImageUri, null,
                        MediaStore.Images.Media.MIME_TYPE + "=? or "
                                + MediaStore.Images.Media.MIME_TYPE + "=?",
                        new String[]{"image/jpeg", "image/png"}, MediaStore.Images.Media.DATE_MODIFIED);
                if (mCursor == null) {
                    return;
                }
                while (mCursor.moveToNext()) {
                    //获取图片的路径  
                    String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA));
                    if (!TextUtils.isEmpty(path)) {
                        mPhotoPathList.add(path);
                    }
                }
                mCursor.close();
                emitter.onNext(mPhotoPathList);
            }
        }).subscribeOn(Schedulers.newThread())  //被观察者所在线程
                .observeOn(AndroidSchedulers.mainThread())  //观察者所在线程
                .subscribe(new Observer<List<String>>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(List<String> list) {
                        if (lisenter != null) {
                            lisenter.onSelect(list);
                        }
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    private onSelectLisenter lisenter;

    public void setOnSelectLisenter(onSelectLisenter mLisenter) {
        this.lisenter = mLisenter;
    }

    public interface onSelectLisenter {
        void onSelect(List<String> list);
    }
}

PhotoListActivit.kt  :界面代码使用kotlin编写

@ActivityFragmentInject(contentViewId = R.layout.activity_photo_list)
class PhotoListActivity : BaseActivity<FilePersenter>(), View.OnClickListener,FileBaseView {


    val affair = AffairUtil()
    var permissions: Array<String> = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.CAMERA)
    var mPhotoListAdapter: PhotoListAdapter? = null
    var mPath: String = ""

    override fun createPresenter(): FilePersenter {
        return FilePersenter(this)
    }

    override fun initView() {
        mPhotoListAdapter = PhotoListAdapter(context)
        initPermission()


    }

    private fun initPermission() {
        // 检查是否有权限    实际建议这些获取权限操作还是放在欢迎页时比较好
        if (EasyPermissions.hasPermissions(this, permissions.toString())) {
            initData()
        } else {
            //不要用EasyPermissions  会飞快闪过  个人觉得和初始化加生命周期有关
            // 用了ActivityCompat后面会在onRequestPermissionsResult 托管给EasyPermissions
            ActivityCompat.requestPermissions(this, permissions, 1)
        }
    }


    //获取失败
    override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) {
        super.onPermissionsDenied(requestCode, perms)
        //onPermissionsDenied(int requestCode, List perms)方法:当权限申请失败的时候执行的回调,
        // 在这个方法里面,官方还建议用EasyPermissions.somePermissionPermanentlyDenied(this, perms)方法来判断是否有权限被勾选了不再询问并拒绝,
        // 还提供了一个AppSettingsDialog来给我们使用,在这个对话框里面解释了APP需要这个权限的原因,用户按下是的话会跳到APP的设置界面,
        // 可以去设置权限(是不是很不要脸^_^),这个Dialog可以使用默认的样式new AppSettingsDialog.Builder(this).build().show(),
        //原文:https://blog.csdn.net/qq_38414907/article/details/76535559

    }

    //获取成功
    override fun onPermissionsGranted(requestCode: Int, perms: MutableList<String>) {
        super.onPermissionsGranted(requestCode, perms)
        initData()
    }

    private fun initData() {
        affair.SelectImage(context)
        affair.setOnSelectLisenter(object : AffairUtil.onSelectLisenter {
            override fun onSelect(list: MutableList<String>) {
                mPhotoListAdapter?.addData(list)
                rv_photo_list.layoutManager = GridLayoutManager(context, 3)
                rv_photo_list.adapter = mPhotoListAdapter
                rv_photo_list.addItemDecoration(ThreeGridDecoration(SizeUtils.dp2px(context, 2F), SizeUtils.dp2px(context, 2F)))
                mPhotoListAdapter?.setPhotoAlbumListener(object : PhotoAlbumListener<String> {
                    //选择照片
                    override fun onSelected(t: String) {
                        mPath = t
                        Glide.with(context).load(t).into(iv_preview)
                        iv_preview.setOnClickListener(this@PhotoListActivity)
                        tv_preview.setOnClickListener(this@PhotoListActivity)
                        tv_next.setOnClickListener(this@PhotoListActivity)
                        tv_preview.setTextColor(resources.getColor(R.color.title))
                        tv_next.setTextColor(resources.getColor(R.color.title))
                        //关于Recyclerview 实现多选,单选,全选,反选,批量删除的功能的实现
                        //https://blog.csdn.net/guohaosir/article/details/72403724
                    }

                    //这个是调用相机的
                    override fun onClickCamera() {

                    }
                })
            }
        })
    }

    override fun onResume() {
        super.onResume()
        tv_preview.setTextColor(resources.getColor(R.color.sub_title))
        tv_preview.setClickable(false)
        tv_next.setTextColor(resources.getColor(R.color.sub_title))
        tv_next.setClickable(false)
        mPhotoListAdapter?.changeCheckState()
    }

    override fun onClick(v: View) {
        when (v.id) {
            R.id.iv_preview ->
                iv_preview.visibility = View.GONE
            R.id.tv_preview ->
                iv_preview.visibility = View.VISIBLE
            R.id.tv_next ->
                if (!TextUtils.isEmpty(mPath)) {
                    uploadPicture();
                } else {
                    showtoast("请选择一张图片,亲")
                }
        }
    }

    //上传方法
    private fun uploadPicture() {
        presenter.UploadImage("image/*", mPath)
    }

    //启动loading
    override fun StartLoading() {
        fl_circle_progress.visibility = View.VISIBLE
    }

    override fun ShowLoading(percentage: Int) {
        circle_progress.setProgress(percentage)
    }

    override fun onSucceed(bean: DetailsBean) {
        circle_progress.visibility = View.GONE
        //正常情况下  接口里面会有返回成功的消息
        showtoast("上传成功")
    }

    override fun showError(msg: String) {
       circle_progress.visibility=View.GONE
       showtoast(msg)
    }

}

项目链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值