Android 天气APP(三十六)运行到本地AS、更新项目版本依赖、去掉ButterKnife

news2024/11/15 10:58:50

运行到本地AS、更新项目版本依赖、去掉ButterKnife

  • 前言
  • 正文
    • 一、新版Android Studio编译运行
      • ① 升级项目gradle版本
      • ② 切换JDK版本
      • ③ BuildConfig报错
    • 二、百度的SDK使用
      • ① 开发版SHA1的作用是什么?
      • ② 什么时候需要更换开发版SHA1?
      • ③ 怎么获取开发版SHA1?
      • ④ 发布版SHA1的作用是什么?
    • 三、依赖库更新
      • ① AndroidManifest.xml更新
      • ② BaseRecyclerViewAdapterHelper更新
    • 四、替换ButterKnife
      • ① 开启ViewBinding
      • ② 创建UiVBCallback
      • ③ 创建BaseVBActivity
      • ④ 使用BaseVBActivity
      • ⑤ 创建MvpVBActivity
      • ⑥ 使用MvpVBActivity
      • ⑦ 去掉ButterKnife依赖
    • 五、保存图片的适配
    • 六、源码

前言

  最近发现这个项目好似迎来了第二春,GitHub上的Start和Fork增加的很快,我的猜测是学生在通过这个项目来学习和完成自己的作业。随着Android版本的更新,Android Studio的更新,项目中一些内容不能在新版本中很好的使用,甚至出现编译不了的情况,这对我来说没啥,但是对于拿到项目满心欢喜的学生来说,运行不了,无疑是致命的,而我写在项目Readme下面我将演示一下怎么在本地运行这个项目的代码。

正文

  在之前写代码的时候还是有一些细节没有做到位,例如代码版本的管理就没有做,导致你看过前几篇文章,跟着写遇到问题,想要看源码,发现源码没有区分章节,而是整体一起的,这无疑给学习的同学造成了麻烦,那么我这篇文章在写之前将之前的代码添加到一个分支中,你学习的话先下载分支的代码,然后跟着写,写完之后就是当前最新的版本代码,这里的版本是2.8。项目地址:GoogWeather

在这里插入图片描述

你切换到Release_2.8再去下载就是之前的所有代码,下载代码你肯定会吧。

一、新版Android Studio编译运行

  下载代码之后,我们就需要在Android Studio中运行,这里我们可以使用Google官网中最新的AS版本,这里我使用的版本如下图所示:

在这里插入图片描述

  这里的版本是海豚,也属于比较新的版本了。你现在下载的可能比这个还要新一点,不过也没关系,操作方式是一样的,因为我开始写这个项目的时候是20年4月份,那时候用的是Android Studio3.5.2,和现在的新版本差别还是蛮大的,下面我们通过AS打开这个项目。

  打开之后AS会检查你这个项目采用的是什么版本的gradle,如果检查到当前电脑环境没有此项目中的gradle,就会下载,这里要注意网络环境问题,如果一个网络环境不行就切换其他的网络环境,例如换个WIFI,使用手机热点等方式。

在这里插入图片描述

  下载好之后AS就会根据这个gradle来编译你的项目,注意一点,高版本AS可以编译低版本的项目,而如果低版本AS项目编译gradle版本高于AS本身gradle版本的项目,则需要降低项目版本,或者提高AS版本,这两种方式就不展开讲了。还有一点就是编译的时候和项目的JDK也有关系,例如我之前的项目采用的JDK是1.8,AS中自带了两个1.8和11两个版本,新项目都是采用11的版本。JDK没有问题之后就会下载你的项目所依赖的远程仓库代码,下载之后再进行编译。这个过程可能会比较长,和项目所使用的库多少及当前网速快慢有关。最后一点,项目使用的Android SDK是28,因此会在编译过程中,提示你下载28的SDK,下载之后再编译就好了,目前我编译了20分钟,终于编译完成了。

① 升级项目gradle版本

在编译完成之后,你可能会看到这样一个提示窗口。

在这里插入图片描述

在Android Studio的右下角出现,什么情况下会出现呢?当你的项目gradle版本与当前AS的gradle版本不匹配时就会出现,我们点击这个upgraded,出现弹窗。

在这里插入图片描述

点击Begin Upgrade ,开始升级项目的gradle版本。

在这里插入图片描述

这里点击Run Selected Steps,运行选中步骤。同样会下载gradle版本,这里下载的是6.7.1,下载完会进行编译。这个过程大概也要十几分钟,完成后,再看。

在这里插入图片描述
然后通过下拉框,将4.2.2改成7.3.0,再点击Run Selected Steps,这里也需要一点时间。

在这里插入图片描述

这里只剩下最后一步了,最后这个是建议步骤,可做可不做。

② 切换JDK版本

做完之后,我们运行一下,然后会遇到一个问题。
在这里插入图片描述

我们的项目之前采用1.8,而现在更新了项目的gradle版本,这个版本最低要求是11,所以我们需要更改项目的JDK版本。怎么修改呢?点击File → Settings… → Gradle。

在这里插入图片描述

这里会看到Gradle JDK是1.8,点击右边的下拉按钮,我们切换到11

在这里插入图片描述

然后点击 Apply 按钮,应用一下刚才的修改,再点击 OK 关闭这个窗口,然后我们再运行一下,这里要用真机运行。因为虚拟机缺少定位传感器,同时SDK不一定兼容虚拟机。

③ BuildConfig报错

运行后发现报错了。
在这里插入图片描述

修改的方式很简单,如下图所示,我们补全一下报名就可以了。

在这里插入图片描述

下面没有报错了,我们再运行一下:

在这里插入图片描述

OK,没有问题,现在能够正常运行了,检查一下布局预览是否能正常显示。布局预览没有问题,那么我们的项目也在新版本的AS上面跑起来了。在你们哪里不一定能定位成功,因为还需要修改一个地方。

二、百度的SDK使用

  鉴权失败,这个会导致无法进行定位,下面说一下这个问题是怎么产生和解决的。

在这里插入图片描述

  这里要注意一个问题,首先你在百度地图开放平台上是否创建了应用,没有创建的话,你就需要去创建。创建应用的方式,在天气App的第一篇文章中已经说明了,先不要着急去看,听我说完,创建应用需要几个条件,发布版SHA1、开发版SHA1、应用包名。

在这里插入图片描述

① 开发版SHA1的作用是什么?

  开发版SHA1是让你能够在debug运行模式下使用SDK中的功能,注意这个debug运行要在真机上,不要妄想使用虚拟机或者模拟器,问题一大堆,你要是愿意死磕,也可以,鱼死不死不好说,但网一定破。

② 什么时候需要更换开发版SHA1?

  开发版SHA1随着你本地的环境改变而改变,就拿我们当前这种情况来说,本地没有项目,下载下来到AS中进行编译,这时就需要更换开发版SHA1的值,如果你之前没有创建百度地图平台应用的话,则就是在创建应用的时候将开发版SHA1填进去。另一种情况,比如之前的项目在文件夹A下,现在你挪到文件夹B下,那么你同样需要再次获取项目的开发版SHA1的值,更新平台上的开发版SHA1值。

③ 怎么获取开发版SHA1?

  由于Android Studio版本发生了改变,获取开发版SHA1的获取方式也一样改变了,在你准备获取SHA1版本之后请先配置好Java JDK的环境变量,再来操作。假设你已经配置好了环境变量,下面进行开发版SHA1的获取,Win + R ,输入cmd,进入命令窗口。

cd .android

先切换到.android目录下,然后输入keytool -list -v -keystore debug.keystore。

keytool -list -v -keystore debug.keystore

  回车之后会让你输入密钥,默认的密钥就是android,你输入的时候是不可见的,光标也不会有反应,你只管输入就行,输入完回车就能看到SHA1了,如下图所示。

在这里插入图片描述
这里的SHA1就是开发版SHA1,然后将SHA1值更新一下再保存。

在这里插入图片描述
现在你再运行一般来说就不会出现定位不成功的情况,也不会出现SDK鉴权失败的情况了。

④ 发布版SHA1的作用是什么?

  开发版SHA1的作用是让你在电脑本地通过Android Studio调试运行的时候,能够使用正常使用SDK中功能。而一个应用你不可能只有自己在使用吧,你可能会发给别人,这个时候你就需要将项目打包成APK,将apk发布给别人安装,至于怎么打包,我也有文章说明,搜一下就可以了。而获取发布版SHA1,你需要先成功打一个包,然后再进行发布版SHA1的获取,具体的操作获取方式参考第一篇文章,那个大体没有改动,只有最后不用选择v1、v2。

三、依赖库更新

  首先我们需要修改app的build.gradle和mvplibrary的build.gradle。

app的build.gradle代码如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 31
    defaultConfig {
        applicationId "com.llw.goodweather"
        minSdkVersion 21
        targetSdkVersion 31
        versionCode 1
        versionName "2.8"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false

            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }

    }

    compileOptions {//指定使用的JDK11
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility JavaVersion.VERSION_11
    }

    // 自定义打包
    android.applicationVariants.all { variant ->

        variant.outputs.all {
            outputFileName = "GoodWeather${variant.versionName}.apk"
        }
    }

    sourceSets {
        main {
            jniLibs.srcDir 'libs'
            //disable automatic ndk-build
        }
    }
    namespace 'com.llw.goodweather'
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test:runner:1.4.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    //butterknife  这个部分比较特殊,所以不管是模块还是项目里都要引入依赖,否则你的控件会报空对象
    implementation 'com.jakewharton:butterknife:10.2.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0'
    implementation project(':mvplibrary')//引入模块  然后将项目里的依赖移动到模块的build.gradle里
    //Bugly SDK
    implementation 'com.tencent.bugly:crashreport:3.4.4'
    // 友盟基础组件库(所有友盟业务SDK都依赖基础组件库)
    implementation "com.umeng.umsdk:common:9.5.2" //(必选)
    implementation "com.umeng.umsdk:asms:1.4.1" // asms包依赖(必选)
    implementation "com.umeng.umsdk:apm:1.7.0" // U-APM包依赖(必选)
}

  api版本由28更改为31,一些依赖库的版本更新了,有第三方的,也有android自己的库,jdk版本也更新了。

下面是mvplibrary的build.gradle。

apply plugin: 'com.android.library'

android {
    compileSdkVersion 31

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 31

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles 'consumer-rules.pro'
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility JavaVersion.VERSION_11
    }
    namespace 'com.llw.mvplibrary'

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    api 'androidx.appcompat:appcompat:1.4.1'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test:runner:1.4.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    //在模块中添加的依赖若想在项目中使用,则implementation改成api
    //butterknife  绑定视图依赖BindView,告别findById,不过你还得安装一个butterknife插件才行
    api 'com.jakewharton:butterknife:10.1.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
    //Google Material控件,以及迁移到AndroidX下一些控件的依赖
    api 'com.google.android.material:material:1.6.0'//更强
    api 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    api 'androidx.annotation:annotation:1.3.0'
    api 'androidx.legacy:legacy-support-v4:1.0.0'

    //RecyclerView最好的适配器,让你的适配器一目了然,告别代码冗余
    api 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'

    //图片加载框架
    api 'com.github.bumptech.glide:glide:4.10.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
    //权限请求框架
    api 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'

    //状态栏
    api 'com.readystatesoftware.systembartint:systembartint:1.0.3'
    //支持okhttp
    api 'com.squareup.okhttp3:okhttp:3.14.9'

    //retrofit2
    api 'com.squareup.retrofit2:retrofit:2.9.0'
    //这里用api 是为了让其他模块也可以使用gson
    api 'com.squareup.retrofit2:converter-gson:2.9.0'
    //日志拦截器
    api 'com.squareup.okhttp3:logging-interceptor:3.10.0'
    api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    //rxjava
    api 'io.reactivex.rxjava2:rxandroid:2.1.1'
    api 'io.reactivex.rxjava2:rxjava:2.2.12'
    api 'androidx.preference:preference:1.2.0'

    //阿里巴巴 FastJson
    api 'com.alibaba:fastjson:1.2.57'
    //下拉刷新框架
    api 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-alpha-14'
    //没有使用特殊Header,可以不加这行
    api 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.0-alpha-14'
    //自由嵌套的RadioGroup
    api 'com.github.fodroid:XRadioGroup:v1.5'
    //EventBus
    api 'org.greenrobot:eventbus:3.1.1'

    //蠕虫蠕动动画TabLayout
    api 'com.ogaclejapan.smarttablayout:library:2.0.0@aar'
    //Optional: see how to use the utility.
    api 'com.ogaclejapan.smarttablayout:utils-v4:2.0.0@aar'
    //Android SQLite操作框架
    api 'org.litepal.guolindev:core:3.1.1'
    //列表item侧滑删除
    api 'com.github.mcxtzhang:SwipeDelMenuLayout:V1.3.0'
    //下拉框
    api 'com.github.arcadefire:nice-spinner:1.4.3'
}

① AndroidManifest.xml更新

  这里同样更改了一些库的版本,会造成什么问题我们目前尚未可知,有问题再解决就是了,下面Sync Now,然后运行。

在这里插入图片描述

  这里出现了问题,因为31表示Android 12,在Android 12中android:export需要显式指定元素<activitycom.llw.goodweather.ui.SplashActivity>。针对Android 12及更高版本的应用,当相应的组件定义了意图过滤器时,需要为“Android:exported”指定显式值。意思很明显我们的启动页面,需要增加exported属性,并且值设置为true。在AndroidManifest.xml中找到SplashActivity。

在这里插入图片描述

修改后运行一下。

② BaseRecyclerViewAdapterHelper更新

  这里使用的是3.0.4版本,相较于之前的改版,改动不到,但由于一个类的路径更改,所以代码中涉及到的地方同样需要更换。

在这里插入图片描述

  如图所示,我们发现这个com.chad.library.adapter.base包下,已经没有这个BaseViewHolder了,那么我们重新导包试试看,将之前的那个报错的导报语句删除掉,然后鼠标悬停在BaseViewHolder上面。

在这里插入图片描述

使用快捷键Alt + Enter进行导包,导包后会增加一行import代码:

import com.chad.library.adapter.base.viewholder.BaseViewHolder;

我们再对比之前报错的那个导包语句看看

import com.chad.library.adapter.base.BaseViewHolder;

  可以看到,区别只是新版本中多了一个viewholder包的层级,那么我们就只需要将新的这个导报语句替换掉报错的导包语句即可,使用全局替换,快捷键Ctrl + Shift + R 。

在这里插入图片描述

  上面一行是原来的报错导包,下面这一行是我们需要替换的导包语句,有21处需要替换,点击Replace All,替换所有,会弹窗提示你,问你是否确定要这么做,点击Replace。替换完成之后,我们再运行试试看。

在这里插入图片描述

  发现了一个新的错误,是添加点击事件的方法没有了,因此我们还需要修改这个部分的代码。首先将

helper.addOnClickListener(R.id.item_city);

这行代码注释掉或者删掉,然后我们进入使用适配器监听点击的地方,在MainActivity中,你找到如下图所示的代码:

在这里插入图片描述

  这个OnItemChildClickListener接口也没有了,那么我们需要怎么添加子控件的点击事件呢?修改方式如下图所示:

在这里插入图片描述

这里可以看到我们通过

areaAdapter.addChildClickViewIds(R.id.item_city);

添加需要进行点击的控件Id,注意这里可以是多个控件Id,例如:

areaAdapter.addChildClickViewIds(R.id.item_city, R.id.item_area);

  你会发现new OnItemChildClickListener()是灰色的,这表示可以省略掉,精简代码,使用Lambda表达式,点击灰色部分,使用快捷键,Alt + Enter,回车出现弹窗。

在这里插入图片描述

这里点击Relpace with lambda选项,替换为lambda,替换后如下图所示。

在这里插入图片描述

  这里你可以替换也可以不替换,随你自己。关于适配器添加点击事件的方法你已经学会了,那么你可以更改其他的适配器中的点击方式了,这个就没有快捷的方式了,你需要一个个去改,我就不重复说明了。所有适配器改完之后再运行看看,哦豁,又报错了。

在这里插入图片描述

  这个报错的原因是mContext值没有了,这是一个上下文,之前的依赖库版本中是有的,现在没有了,就需要替换了,将mContextgetContext()即可,自行替换就好了。然后再运行你会发现,不报错了,可以运行了,这个库的问题改完了。但是还有一个隐藏问题在WallPaperAdapter类中。

在这里插入图片描述

这个getAdapterPosition()方法没有了,这个我们也需要替换,将

helper.getAdapterPosition()

改成

getItemPosition(item)

即可。

好了,这个依赖库我们就改完了,开不开心?来人,奏乐,起舞,老子改了大半天BUG了,还不能享受享受吗?嗯?

四、替换ButterKnife

  为什么要替换掉呢?因为在新版Android Studio中你已经不能使用ButterKnife的插件了,ButterKnife的作者也告诉我们不再维护这个库了,推荐我们使用ViewBinding。

① 开启ViewBinding

  首先要使用ViewBinding,我们需要先开启它。在app的build.gradle的android{}闭包中增加如下所示代码:

	buildFeatures {
        viewBinding true    //开启ViewBinding
    }

在这里插入图片描述

然后Sync Now,下面我们以AboutUsActivity为例来修改一下,使用ViewBinding。

② 创建UiVBCallback

  还记得之前我们使用的框架吗?是MVP,现在我们去掉了ButterKnife,所以响应的底层也需要改动,之前我们有一个UiCallBack,所以为了区分,我在base包下新建了一个vb包,表示这是使用ViewBinding所需要的一些类和接口,那么我们在vb包下新建一个UiVBCallback接口,代码如下:

public interface UiVBCallback {

    default void onRegister(){}

    //初始化savedInstanceState
    default void initBeforeView(Bundle savedInstanceState) {}

    //初始化
    void initData();
}

  这里三个方法,很简单,前面两个不是必须要实现的,最后一个是必须要实现的,因为我们会涉及到ViewBinding的简单封装,除了第一个onRegister()方法你不清楚,其他的方法你之前应该都见过了,这个onRegister()的作用实际上是为了让你更好的使用Activity Result API,因为我们startActivityResult()过时了。具体的你可以上网看一下,这里就不展开说明了,用到了再说。

③ 创建BaseVBActivity

  为了能让所有的Activity都使用ViewBinding,我们创建一个类似于BaseActivity的抽象基类,在com.llw.mvplibrary.base.vb包下新建BaseVBActivity类,代码如下:

public abstract class BaseVBActivity<T extends ViewBinding> extends AppCompatActivity implements UiVBCallback {

    public T binding;
    protected Activity context;
    private static final int FAST_CLICK_DELAY_TIME = 500;
    private static long lastClickTime;
    private Dialog mDialog;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        onRegister();
        super.onCreate(savedInstanceState);
        initBeforeView(savedInstanceState);
        this.context = this;
        //添加继承这个BaseVBActivity的Activity
        BaseApplication.getActivityManager().addActivity(this);

        Type type = this.getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            try {
                Class<T> clazz = (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0];
                //反射
                Method method = clazz.getMethod("inflate", LayoutInflater.class);
                binding = (T) method.invoke(null, getLayoutInflater());
            } catch (Exception e) {
                e.printStackTrace();
            }
            setContentView(binding.getRoot());
        }
        initData();
    }

    /**
     * 弹窗出现
     */
    protected void showLoadingDialog() {
        if (mDialog == null) {
            mDialog = new Dialog(context, R.style.loading_dialog);
        }
        mDialog.setContentView(R.layout.dialog_loading);
        mDialog.setCancelable(false);
        mDialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        mDialog.show();
    }

    /**
     * 弹窗消失
     */
    protected void dismissLoadingDialog() {
        if (mDialog != null) {
            mDialog.dismiss();
        }
        mDialog = null;
    }

    /**
     * 返回
     */
    protected void Back(Toolbar toolbar) {
        toolbar.setNavigationOnClickListener(v -> {
            context.finish();
            if (!isFastClick()) {
                context.finish();
            }
        });
    }

    /**
     * 两次点击间隔不能少于500ms
     *
     * @return flag
     */
    protected static boolean isFastClick() {
        boolean flag = true;
        long currentClickTime = System.currentTimeMillis();
        if ((currentClickTime - lastClickTime) >= FAST_CLICK_DELAY_TIME) {
            flag = false;
        }
        lastClickTime = currentClickTime;

        return flag;
    }
}

这里的核心思想就是反射。

④ 使用BaseVBActivity

下面我们修改一下AboutUsActivity中的代码,如下所示:

public class AboutUsActivity extends BaseVBActivity<ActivityAboutUsBinding> implements View.OnClickListener {

    private String updateUrl = null;
    private String updateLog = null;
    private boolean is_update = false;
    /**
     * 博客地址
     */
    private final String CSDN_BLOG_URL = "https://blog.csdn.net/qq_38436214/category_9880722.html";
    /**
     * 源码地址
     */
    private final String GITHUB_URL = "https://github.com/lilongweidev/GoodWeather";

    @Override
    public void initData() {
        Back(binding.toolbar);
        //蓝色状态栏
        StatusBarUtil.setStatusBarColor(context, R.color.about_bg_color);
        //设置文字下划线
        binding.tvCopyEmail.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG);
        //抗锯齿
        binding.tvCopyEmail.getPaint().setAntiAlias(true);
        binding.tvVersion.setText(APKVersionInfoUtils.getVerName(context));
        AppVersion appVersion = LitePal.find(AppVersion.class, 1);
        updateLog = appVersion.getChangelog();
        //提示更新
        if (!appVersion.getVersionShort().equals(APKVersionInfoUtils.getVerName(context))) {
            is_update = true;
            //显示红点
            binding.vRed.setVisibility(View.VISIBLE);
            updateUrl = appVersion.getInstall_url();
            updateLog = appVersion.getChangelog();
        } else {
            //隐藏红点
            binding.vRed.setVisibility(View.GONE);
            is_update = false;
        }
        //添加点击事件
        binding.layAppVersion.setOnClickListener(this);
        binding.tvBlog.setOnClickListener(this);
        binding.tvCode.setOnClickListener(this);
        binding.tvCopyEmail.setOnClickListener(this);
        binding.tvAuthor.setOnClickListener(this);
    }

    @SuppressLint("NonConstantResourceId")
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.lay_app_version://版本更新
                if (is_update) {
                    showUpdateAppDialog(updateUrl, updateLog);
                } else {
                    ToastUtils.showShortToast(context, "当前已是最新版本");
                }
                break;
            case R.id.tv_blog://博客地址
                jumpUrl(CSDN_BLOG_URL);
                break;
            case R.id.tv_code://源码地址
                jumpUrl(GITHUB_URL);
                break;
            case R.id.tv_copy_email://复制邮箱
                ClipboardManager myClipboard = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);
                ClipData myClip = ClipData.newPlainText("text", "lonelyholiday@qq.com");
                myClipboard.setPrimaryClip(myClip);
                ToastUtils.showShortToast(context, "邮箱已复制");
                break;
            case R.id.tv_author://作者
                ToastUtils.showShortToast(context, "你为啥要点我呢?");
                break;
            default:
                ToastUtils.showShortToast(context, "点你咋的!");
                break;
        }
    }

    /**
     * 跳转URL
     *
     * @param url 地址
     */
    private void jumpUrl(String url) {
        if (url != null) {
            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
        } else {
            ToastUtils.showShortToast(context, "未找到相关地址");
        }
    }

    AlertDialog updateAppDialog = null;

    /**
     * 更新弹窗
     *
     * @param downloadUrl 下载地址
     * @param updateLog   更新内容
     */
    private void showUpdateAppDialog(String downloadUrl, String updateLog) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context)
                .addDefaultAnimation()//默认弹窗动画
                .setCancelable(true)
                .setText(R.id.tv_update_info, updateLog)
                .setContentView(R.layout.dialog_update_app_tip)//载入布局文件
                .setWidthAndHeight(SizeUtils.dp2px(context, 270), ViewGroup.LayoutParams.WRAP_CONTENT)//设置弹窗宽高
                .setOnClickListener(R.id.tv_cancel, v -> {//取消
                    updateAppDialog.dismiss();
                }).setOnClickListener(R.id.tv_fast_update, v -> {//立即更新
                    ToastUtils.showShortToast(context, "正在后台下载,下载后会自动安装");
                    downloadApk(downloadUrl);
                    updateAppDialog.dismiss();
                });
        updateAppDialog = builder.create();
        updateAppDialog.show();
    }

    /**
     * 清除APK
     */
    public static File clearApk(String apkName) {
        File apkFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), apkName);
        if (apkFile.exists()) {
            apkFile.delete();
        }
        return apkFile;
    }

    /**
     * 下载APK
     */
    private void downloadApk(String downloadUrl) {
        clearApk("GoodWeather.apk");
        //下载管理器 获取系统下载服务
        DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl));
        //设置运行使用的网络类型,移动网络或者Wifi都可以
        request.setAllowedNetworkTypes(request.NETWORK_MOBILE | request.NETWORK_WIFI);
        //设置是否允许漫游
        request.setAllowedOverRoaming(true);
        //设置文件类型
        MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
        String mimeString = mimeTypeMap.getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(downloadUrl));
        request.setMimeType(mimeString);
        //设置下载时或者下载完成时,通知栏是否显示
        request.setNotificationVisibility(request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        request.setTitle("下载新版本");
        request.setVisibleInDownloadsUi(true);//下载UI
        //sdcard目录下的download文件夹
        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "GoodWeather.apk");
        //将下载请求放入队列
        downloadManager.enqueue(request);
    }
}

  在这里面我将关于ButterKnife的注解都删除掉了,findViewBinding通过ViewBinding的编译时技术在编译过程中就给我们生成了findViewById的代码了,这个代码就在ActivityAboutUsBinding类中,这个类是编译时生成了,只要你是layout下的xml,在开启了ViewBinding之后都会生成对应的类文件,文件命名规则就是布局名称 驼峰 + Binding,例如布局名称为activity_main.xml,生成的文件就是ActivityMainBinding,掌握这个规律就好了,文件核心就这么多,控件实例化了,但是我们还是需要自己写控件点击的代码,还是比较简单的吧。这个封装的核心思路就是通过编译时生成类,类继承自ViewBinding,通过泛型可以传入任何继承自ViewBinding的类,传入的类通过反射拿到具体的类,再通过类拿到里面的方法,通过方法拿到实例化之后的binding,最后setContentView,将binding.getRoot()设置进去即可,注意一点反射会比不反射在编译的时候耗时一些,子类可以直接拿到父类的binding,通过binding得到xml中的控件id,你会看到很多的binding.xxx,相信你应该能够理解了,不理解也没有关系,你先用,用的多了就理解了,同时我去掉了getLayoutId()。

⑤ 创建MvpVBActivity

  现在基本的Activity可以使用ViewBinding了,下面是用到MVP的Activity,这里我们依然保留之前的方式,所以我们需要创建一个可以使用ViewBinding的MVPActivity,在mvp包下新建MvpVBActivity,代码如下:

public abstract class MvpVBActivity<T extends ViewBinding, P extends BasePresenter> extends BaseVBActivity<T> {

    protected P mPresent;

    @Override
    public void initBeforeView(Bundle savedInstanceState) {
        mPresent = createPresent();
        mPresent.attach((BaseView) this);
    }

    protected abstract P createPresent();

    @Override
    public void onDestroy() {
        super.onDestroy();
        mPresent.detach((BaseView) this);
    }

}

整体上来说,改动不是很大,不过这种方式你可以多加理解一下。

好了,下面我们来使用一下MvpVBActivity。

⑥ 使用MvpVBActivity

  同样我们需要一个例子来使用这个MvpVBActivity,那么就来改造一下WallPaperActivity吧。

在这里插入图片描述
  这里我们在泛型里面增加了一个ActivityWallPaperBinding,然后修改一下initData方法。

	@Override
    public void initData() {
        //加载弹窗
        showLoadingDialog();
        //高亮状态栏
        StatusBarUtil.StatusBarLightMode(this);
        //左上角的返回
        Back(binding.toolbar);
        initWallPaperList();

        binding.fabSetting.setOnClickListener(v -> {
            binding.fabSetting.hide();
            int type = SPUtils.getInt(Constant.WALLPAPER_TYPE, 4, context);
            showSettingDialog(type);
        });
    }

  因为只有一个点击事件,我就直接写在这里面了,原来的点击注解记得删除掉哦,使用就是这么的简单,你学会了吗?后续的其他Activity自己去修改,不会也没有关系,源码就在哪里,你拿去对比看一下就可以了,记住一个事情,如果你的电脑环境,版本,代码、运行环境和作者一样,SDK配置没有问题,那么你就肯定没有问题,那么如果有问题,怎么办,找到问题的发生原因,可能一开始你还不够熟练,但是解决问题比制造问题更能让一个人成长,实在解决不了再去求助,才会有醍醐灌顶的作用,遇到问题先自己想办法解决,这里说一个标准吧,遇到问题之后,百度上搜索,前面七页结果你都看一遍,百分之八十的问题就能解决,耐心很重要,很重要,千万不要浮躁,切记、切记。

⑦ 去掉ButterKnife依赖

  现在我们已经不需要ButterKnife了,可以去掉了,有五处地方,第一个地方,在工程的build.gradle中去掉butterknife的插件。

在这里插入图片描述

第二个地方,在app模块的build.gradle中去掉butterknife的依赖库和注解处理器库。

在这里插入图片描述

第三个地方,在mvplibrary模块的build.gradle中butterknife的依赖库和注解处理器库。

在这里插入图片描述
现在关于gradle中的改动就完成了,Sync Now,然后运行一下,看看App中是否哪里有报错,检查是否还有别的类使用了依赖库中的注解和方法。不出意外的话。

第五处,你的BaseActivity会报错,因为现在已经有了BaseVBActivity了,所以BaseActivity可以删掉,或者你把BaseActivity里面的关于butterknife的信息删掉也可以,注意BaseFragment中的相关信息也要删掉。

第四处,删掉KnifeKit,连同kit包一起删掉。

在这里插入图片描述

好了,你可以再次运行有残留包名的就删掉,直到你能够正常运行到手机上即可。

五、保存图片的适配

  之前的版本是基于Android10做的,而现在目标版本是Android12,因此保存图片的路径需要改动一下,也很简单。根据版本修改一下路径的值就可以了,打开ImageActivity,找到saveImageToGallery()方法,修改代码如下图所示:

在这里插入图片描述

然后你就可以在Android10以上的版本下载图片成功了。

六、源码

时隔一年多再次更新,老来俏啊~

源码地址:GoodWeather
欢迎 Star 和 Fork

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/12510.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM8S系列基于STVD开发,ADC不同精度采样示例

STM8S系列基于STVD开发&#xff0c;ADC不同精度采样示例&#x1f4cc;相关篇《STM8S系列基于STVD开发&#xff0c;自定义printf函数TIM5精确延时函数模块化工程示例》 ✨本工程以上面一篇的工程为模板&#xff0c;在此基础上实现ADC电压采样。 &#x1f3ac;&#x1f4fd;&…

java 读取resource下的文件

目录一、普通main代码里使用1.假设有如下结构的代码&#xff08;1&#xff09;、main方法里复制resource下的文件&#xff08;2&#xff09;、main方法里读取resource下的文件2.假设有如下结构的代码二、对于springboot项目读取resource下的资源文件一、普通main代码里使用 1.…

虚拟主播是什么,有什么技术原理?- 沉睡者IT

虚拟主播是什么&#xff1f;虚拟形象人物是通过人工智能技术的研究和积累&#xff0c;在克服了计算机图形学和AI核心技术的各个学科的智能化、平台化、虚拟人、虚拟内容在各个维度的技术难题后&#xff0c;提供给用户的核心资产。虚拟主播指的是在视频网站上使用虚拟图片进行投…

C. Bargain(数学贡献法)

Problem - 1422C - Codeforces 有时&#xff0c;要在讨价还价中达成协议并不容易。现在&#xff0c;萨沙和沃瓦就无法达成协议。萨沙说出了一个尽可能高的价格&#xff0c;然后沃瓦想从这个价格中删除尽可能多的数字。更详细地说&#xff0c;Sasha说出某个整数的价格n&#xff…

[R]第二节 练习一关于数值向量

1.产生一个等差数列(1,3,5,7,……,99)赋值给向量x x <- array(seq(from1, to99, by2)) seq函数解析 seq(from,to,length)该函数的意思是生成一组数字&#xff0c;从from开始&#xff0c;到to结束&#xff0c;每两个数间的间隔是length,如: seq(2,10,2),会生成一组数&…

ROS1学习笔记:服务中的Service和Client(ubuntu20.04)

参考B站古月居ROS入门21讲&#xff1a; 客户端Client的编程实现 服务端Server的编程实现 基于VMware Ubuntu 20.04 Noetic版本的环境 文章目录一、小乌龟例程中的服务二、创建功能包三、创建Client代码3.1 以C为例3.1.1 配置Client代码编译规则3.1.2 编译整个工作空间3.1.3 配置…

12 张图看懂 CPU 缓存一致性与 MESI 协议,真的一致吗?

本文已收录到 GitHub AndroidFamily&#xff0c;有 Android 进阶知识体系&#xff0c;欢迎 Star。技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 进 Android 面试交流群。 前言 大家好&#xff0c;我是小彭。 在上一篇文章里&#xff0c;我们聊到了 CPU 的三级缓存结构…

测试工作3年还在基础岗?可能只是因为你的工作能力差

对于职场人而言&#xff0c;工作中升职加薪是每个人都梦寐以求的事情&#xff0c;但有些小伙伴表示&#xff0c;自己来到一个公司三年&#xff0c;却依旧停留在基础岗位上&#xff0c;究竟是什么原因呢&#xff1f; 其实从根本来说&#xff0c;很有可能只是因为&#xff1a;你…

Spring Boot日志配置及输出

1.日志框架有哪些&#xff1f; 常见的日志框架有log4j、logback、log4j2。 log4j这个日志框架显示是耳熟能详了&#xff0c;在Spring开发中是经常使用&#xff0c;但是据说log4j官方已经不再更新了&#xff0c;而且在性能上比logback、log4j2差了很多。 logback是由log4j创始…

WebRTC系列<四> 全面了解客户端-服务器网页游戏的WebRTC

转载&#xff1a;https://blog.brkho.com/2017/03/15/dive-into-client-server-web-games-webrtc/ 多人游戏很有趣。对于他们在单人沉浸感方面所缺乏的东西&#xff0c;在线游戏弥补了与朋友一起探索、在线结识陌生人以及与有能力的同龄人正面交锋的独特奖励体验。人们只需要看…

C# Control.DoubleBuffered 属性的使用

C# Control.DoubleBuffered 属性的使用 在我们开发的过程中,经常需要对界面进行美化,而美化的过程,一般来说就是添加图片, 让界面更加清新脱俗,更加耳目一新。 有一次有一个软件发送到客户那里试用,客户对功能是非常满意的,但是对界面的布局和颜色,就大为不满。 原来…

【Hack The Box】windows练习-- Resolute

HTB 学习笔记 【Hack The Box】windows练习-- Resolute &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年9月7日&#x1f334; &#x1…

uniapp picker 的使用,这玩意做的真不怎么样

uniapp picker 的使用&#xff0c;这玩意做的真不怎么样 最近要做小程序&#xff0c;考虑到需要多平台都用一套东西&#xff0c;就选用了 uniapp。 在写表单的时候用到它的 picker 组件&#xff0c;看官方文档楞是没看明白怎么用&#xff0c;试了半天没试出来&#xff0c;还是…

每日一题 —— LC. 790 多米诺和托米诺

有两种形状的瓷砖&#xff1a;一种是 2 x 1 的多米诺形&#xff0c;另一种是形如 “L” 的托米诺形。两种形状都可以旋转。 给定整数 n &#xff0c;返回可以平铺 2 x n 的面板的方法的数量。返回对 10^9 7 取模 的值。 平铺指的是每个正方形都必须有瓷砖覆盖。两个平铺不同&…

数据集成平台关于【源平台调度任务生命周期】

任务调度者 调度事件生产任务调度任务池-异步
AsynDispatcher --source 实例化适配器执行 消费任务实例化集成应用 DataHub Instance
handleSourceDispatch()依赖注入集成方案适配器调度条件检查执行适配器调度方法联动其它方案调度任务 SourceEvent 适配器调度周期 初始化…

化合物应用 | 动物实验溶剂选择

在给药时为了实现药物准确运送到动物体内、减少溶剂本身的副作用和毒性等的目标&#xff0c;需要选择合适的溶剂配方。溶剂的理化性质&#xff0c;如 pH、粘稠度、渗透压等都会对给药产生影响&#xff0c;需要慎重考虑。例如粘稠度过高可能会导致注射用针头的堵塞&#xff0c;粘…

作为新人,如何快速融入新团队?用好这8个点

大家好&#xff0c;之前在公司调岗&#xff0c;转到了新团队。 从一开始的不适应、不习惯&#xff0c;到现在的逐步习惯&#xff0c;真的就是和那句老话说的一样「有压力才会有成长」&#xff0c;下面晨光会结合在新团队学到的内容进行分享。 文章分为以下几个部分&#xff1…

通过云速搭CADT实现云原生分布式数据库PolarDB-X 2.0的部署

云速搭 CADT 是一款为上云应用提供自助式云架构管理的产品&#xff0c;显著地降低应用云上管理的难度和时间成本。本产品提供丰富的预制应用架构模板&#xff0c;同时也支持自助拖拽方式定义应用云上架构&#xff1b;支持较多阿里云服务的配置和管理。用户可以方便的对云上架构…

人力资源数字化转型,是企业高质量发展的关键

情景一 中层管理者&#xff0c;每天不是在开会&#xff0c;就是在帮下属解决问题&#xff0c;时间被搞的一团乱麻&#xff1b; 为了顺利推进项目&#xff0c;总是把自己逼成卷王&#xff0c;即使如此也没能挽救业绩下滑的命运。 情景二 由于业务能力出色被提拔带团队的新晋…

计算机网络-传输层(UDP协议报文格式,伪首部,UDP校验过程)

文章目录1. UDP协议UDP报文格式UDP校验过程1. UDP协议 UDP只在IP数据报服务之上增加了很少功能&#xff0c;即复用分用和差错检测功能。 UDP的主要特点: UDP是无连接的&#xff0c;减少开销和发送数据之前的时延。 UDP使用最大努力交付&#xff0c;即不保证可靠交付。 UDP是…