Android app API环境切换需求与实现

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android app API环境切换需求与实现相关的知识,希望对你有一定的参考价值。

参考技术A

鉴于app在打包debug ,release, dev 等环境的时候,请求后天api接口的服务器地址不一样.导致测试可能频繁的问你要不同环境的测试apk包.
当工程大到一定程度的时候,gradle打包特别的慢.即使你自己有优化过,而且测试嫌麻烦,开发也觉得麻烦.

api地址变化的应该就基地址,全部变化也有办法处理.这里把基地址用一个 public static 的 String Url 变量去装载.
1:默认 Url 地址使用release的地址.
2:release版本的apk 按照需求,启动app调转到splash界面.debug版本的apk 跳转到api选择界面,当选择好api环境后,在调转到原splash界面.
3:选择api环境的时候,去修改 这个被public static 修饰的Url

1:修改被public static 修饰的String 不是问题.
2:根据不同apk版本启动不同的界面.这个是一个问题,之前没处理过类似的需求.好在google 有提供一种解决思路 饺子 manifest merge ( 合并多个清单文件 ),就是根据这个合并算法去解决这个问题.

创建debug文件夹,新建的项目一般会有3个文件夹,一个是默认类型,一个test 类型,一个android test类型,这里我们在创建一个debug类型的.

xml文件合并可以合并java 文件,和 资源文件.
我们这里定要创建manifest文件,这是算法配置的核心,配置就是在这里设置的,其次还有资源文件.
注意创建的时候文件格式应该和main类型的保持一致,可以将as切换到android模式下检查.
我们这里添加了一个EnvChangeActivity 和对应的布局文件.

这里我们想修改启动的界面为EnvChangeActivity.所以我们要替换点原main类型下的manifest文件中的配置.

如上设置就会把原Mainactivity中的配置全部替换成当前manifest文件中的配置.后面我们在解释这些配置.这里将EnvChangeActivity设置成了启动界面.所以完成了修改启动界面的功能.

创建一个java配置文件

可以不创在main类型下面.
在EnvChangeActivity中修改地址就好了.修改完之后在跳转到MainActivity.就原逻辑保持一致了

到这里需求的就已经说明完了.

接下来就说明合并的算法,在我们使用gradle编译打包的时候,合并工具会自动帮我们合并.
合并工具根据每个清单文件的优先级将所有清单文件按顺序合并到一个文件中。 例如,如果您有 3 个清单文件,则会先将优先级最低的清单合并到优先级第 2 高的清单中,然后再将合并后的清单合并到优先级最高的清单中

2应用模块的主清单文件
3所包括库中的清单文件 如果您有多个库,则其清单优先级与依赖顺序(库出现在 Gradle dependencies
块中的顺序)匹配。

合并工具可以在逻辑上将一个清单中的每个 XML 元素与另一个清单中的对应元素相匹配。
如果优先级较低的清单中的元素与优先级较高的清单中的任何元素均不匹配,则该元素将被添加至合并清单。 但是,如果有匹配元素,则合并工具会尝试将其中的所有属性合并到相同元素中。如果工具发现两个清单包含相同属性,但值不相同,则会出现合并冲突。

绝不会在清单之间匹配 <intent-filter>
元素。 每个元素都被视为唯一元素,并添加至合并清单中的常用父元素。

合并规则标记是一个 XML 属性,可用于表达您对关于如何解决合并冲突或删除不需要的元素和属性的首选项。 您可以对整个元素或只对元素中的特定属性应用标记。
合并两个清单文件时,合并工具会在高优先级清单文件中寻找这些标记。
所有标记均属于 Android tools 命名空间,因此您必须先在 <manifest> 元素中声明此命名空间,如下文所示:

要向整个 XML 元素(给定清单元素中的所有元素及其所有子标记)应用合并规则,请使用以下属性:

如果使用合并冲突启发式算法时没有冲突,则合并此标记中的所有属性以及所有嵌套元素。 这是元素的默认行为。

低优先级清单

高优先级清单

合并结果

低优先级清单

高优先级清单

合并结果

完全替换低优先级元素。 也就是说,如果低优先级清单中有匹配元素,请将其忽略并完全按照其在此清单中显示样子来使用该元素。

低优先级清单

高优先级清单

合并结果

其他的自己看下官网,上面的解释也是我搬过来的.
官方解释
拙劣的dome,可以瞅瞅.
Demo地址

Android 天气APP城市切换 之 自定义弹窗与使用

上一篇:Android 天气APP(七)城市切换 之 城市数据源

添加数据库、城市切换

新版-------------------

  在上一篇文章中,完成了风力风向的显示,文章最后我添加了一个菜单,菜单里面只有一个选项,切换城市,本篇文章就是完成切换城市之后查询城市天气的功能,说起来是不是很简单,那么我们看一下实现这个功能要怎么做。

一、添加依赖和城市数据

  想一下,我们的城市数据怎么来,怎么保存和获取,在我之前的版本中,有一个txt文件,读取这个文件,然后显示在UI上,乍一看似乎可以使用,但是会有问题,性能损耗太大,每一次都需要重新进行文件读取,不优雅,这一块我们就用数据库来处理,只做一次文件读取,然后写入到数据库中,用的时候从数据库中读取即可,怎么保证只做一次文件读取呢,可以利用缓存值,在程序第一次运行时进行读取,后面就不再读取,现在想一下是不是比之前要复杂一些了,下面我们先添加依赖,在app的build.gralde的dependencies闭包下添加如下所示代码:

	//Room数据库
    implementation 'androidx.room:room-runtime:2.4.2'
    annotationProcessor 'androidx.room:room-compiler:2.4.2'
    //Room 支持RxJava2
    implementation 'androidx.room:room-rxjava2:2.4.2'
    //腾讯MMKV
    implementation 'com.tencent:mmkv:1.2.11'
    //Gson
    implementation 'com.google.code.gson:gson:2.9.0'

添加位置如下图所示:

  然后Sync Now,下面准备城市数据源,在app模块的main文件夹下创建一个assets文件夹,里面放入一个city.txt,这里我就不贴里面的内容了,你可以去我的源码里面直接获取,如下图所示:

二、添加启动页

  一般程序会在启动页做一些事情,比如初始数据获取等一些操作,我们现在只有一个MainActivity,这明显是不够的,下面我们在com.llw.goodweather包下新建ui,ui包下新建一个SplashActivity,创建Activity的方式你应该会吧,为了方便管理,我们将MainActivity也移动到ui包下,移动之后检查一下MainActivity里面有没有报错,有的话就是ViewBinding的问题,你把错误的那一条语句删掉,重新导包就行了,然后就需要将SplashActivity作为启动页面了,修改AndroidManifest.xml中的代码,如下图所示:

  要注意细节,在Android 12中有一个android:exported属性,启动Activity必须为true,其他的就默认为false就行了,下面简单修改一下activity_splash.xml中的内容,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/splash_bg"
    android:fitsSystemWindows="true"
    tools:context=".ui.SplashActivity">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:scaleType="centerCrop"
        android:src="@drawable/splash_bg" />
</androidx.constraintlayout.widget.ConstraintLayout>

这里用到的背景图你可以去源码里面去找,那么你现在运行一下,应该就是显示SplashActivity了。

三、城市数据操作

  下面我们进行城市数据的操作,也就是从txt中读取、通过数据库保存、查询,这里我们用到的是Room数据库,首先我们在bean包下新建一个Province类,里面的代码如下:

@Entity
public class Province 

    @PrimaryKey(autoGenerate = true)
    private int uid;
    private String provinceName;
    private List<City> cityList;

    public int getUid() 
        return uid;
    

    public void setUid(int uid) 
        this.uid = uid;
    

    public String getProvinceName() 
        return provinceName;
    

    public void setProvinceName(String provinceName) 
        this.provinceName = provinceName;
    

    public List<City> getCityList() 
        return cityList;
    

    public void setCityList(List<City> cityList) 
        this.cityList = cityList;
    

    public Province() 

    @Ignore
    public Province(String provinceName, List<City> cityList) 
        this.provinceName = provinceName;
        this.cityList = cityList;
    

    public static class City 
        private String cityName;
        private List<Area> areaList;

        public String getCityName() 
            return cityName;
        

        public void setCityName(String cityName) 
            this.cityName = cityName;
        

        public List<Area> getAreaList() 
            return areaList;
        

        public void setAreaList(List<Area> areaList) 
            this.areaList = areaList;
        

        public City() 
        

        public static class Area 
            private String areaName;

            public String getAreaName() 
                return areaName;
            

            public void setAreaName(String areaName) 
                this.areaName = areaName;
            

            public Area(String countyName) 
                this.areaName = countyName;
            
        
    

  这里面用到的注解你记得导包,都是room下的,然后我们创建一个转换器,在bean包下新建一个CityConverter类,代码如下:

public class CityConverter 

    @TypeConverter
    public List<Province.City> stringToObject(String value) 
        Type userListType = new TypeToken<ArrayList<Province.City>>() .getType();
        return new Gson().fromJson(value, userListType);
    

    @TypeConverter
    public String objectToString(List<Province.City> list) 
        return new Gson().toJson(list);
    

  这里的注解导包同样是room下的,为什么要有这个转换器呢?因为我们是在一张表里面又插入了一张表,不用这个转换器就会报错。

然后我们再回到Province,在@Entity注解上面再添加一行注解,代码如下:

@TypeConverters(CityConverter.class)

添加位置如下图所示:

  下面我们在com.llw.goodweather包下新建一个db包,然后在db包下创建一个AppDatabase类,稍微我们会用到它,下面将bean包移到db包下,然后在db包下新建一个dao包,dao包中就是对于数据库操作的接口方法包,在dao包下新建一个ProvinceDao接口,代码如下所示:

@Dao
public interface ProvinceDao 

    /**
     * 查询所有
     */
    @Query("SELECT * FROM Province")
    Flowable<List<Province>> getAll();

    /**
     * 插入所有
     * @param provinces 所有行政区数据
     */
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    Completable insertAll(Province... provinces);

  这里的Flowable和Completable 都是RxJava中的内容,背压,你可以去了解一下,下面回到AppDatabase类,修改代码如下所示:

@Database(entities = Province.class,version = 1,exportSchema = false)
public abstract class AppDatabase extends RoomDatabase 

    private static final String DATABASE_NAME = "GoodWeatherNew";
    private static volatile AppDatabase mInstance;

    public abstract ProvinceDao provinceDao();

    /**
     * 单例模式
     */
    public static AppDatabase getInstance(Context context) 
        if (mInstance == null) 
            synchronized (AppDatabase.class) 
                if (mInstance == null) 
                    mInstance = Room.databaseBuilder(context.getApplicationContext(),
                            AppDatabase.class, DATABASE_NAME).build();
                
            
        
        return mInstance;
    

  注意看这是一个抽象类,我们通过注解会生成一个编译时类,然后将之前创建的Province当成一个表放进数据库,数据库版本为1,里面有一个抽象接口方法,还有一个单例,单例中做数据库的构建,下面关于数据库的操作就基本上完成了,看一下db包下的内容,如下图所示:

  那么我们在哪里调用呢?在Repository中,下面在repository包下新建一个CustomDisposable类,这里面是对数据的背压操作所创建的工具类,里面的代码如下所示:

public class CustomDisposable 

    private static final CompositeDisposable compositeDisposable = new CompositeDisposable();

    /**
     * Flowable
     * @param flowable
     * @param consumer
     * @param <T>
     */
    public static <T> void addDisposable(Flowable<T> flowable, Consumer<T> consumer) 
        compositeDisposable.add(flowable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(consumer));
    

    /**
     * Completable
     * @param completable
     * @param action
     * @param <T>
     */
    public static <T> void addDisposable(Completable completable, Action action) 
        compositeDisposable.add(completable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(action));
    

然后在repository包下新建一个CityRepository类,代码如下所示:

public class CityRepository 

    private static final String TAG = CityRepository.class.getSimpleName();

    private static final class CityRepositoryHolder 
        private static final CityRepository mInstance = new CityRepository();
    

    public static CityRepository getInstance() 
        return CityRepository.CityRepositoryHolder.mInstance;
    

    /**
     * 添加城市数据
     */
    public void addCityData(List<Province> cityList) 
        Province[] provinceArray = cityList.toArray(new Province[0]);
        Completable insertAll = WeatherApp.getDb().provinceDao().insertAll(provinceArray);
        CustomDisposable.addDisposable(insertAll, () -> Log.d(TAG, "addCityData: 插入数据成功。"));
    

    /**
     * 获取城市数据
     */
    public void getCityData(MutableLiveData<List<Province>> listMutableLiveData) 
        Flowable<List<Province>> listFlowable = WeatherApp.getDb().provinceDao().getAll();
        CustomDisposable.addDisposable(listFlowable, listMutableLiveData::postValue);
    

  然后就是调用这些方法的地方,根据MVVM的框架模式,我们可以在viewmodel包下新建一个SplashViewModel类,代码如下:

public class SplashViewModel extends BaseViewModel 

    public MutableLiveData<List<Province>> listMutableLiveData = new MutableLiveData<>();

    /**
     * 添加城市数据
     */
    public void addCityData(List<Province> provinceList) 
        CityRepository.getInstance().addCityData(provinceList);
    

    /**
     * 获取所有城市数据
     */
    public void getAllCityData() 
        CityRepository.getInstance().getCityData(listMutableLiveData);
    

下面在Constant中新增两个常量,代码如下:

	/**
     * 程序第一次运行
     */
    public static final String FIRST_RUN = "firstRun";

    /**
     * 今天第一次启动时间
     */
    public static final String FIRST_STARTUP_TIME_TODAY = "firstStartupTimeToday";

下面我们添加MMKV的使用,在utils包下新建一个MVUtils 类,代码如下所示:

public class MVUtils 

    private static MVUtils mInstance;
    private static MMKV mmkv;

    public MVUtils() 
        mmkv = MMKV.defaultMMKV();
    

    public static MVUtils getInstance() 
        if (mInstance == null) 
            synchronized (MVUtils.class) 
                if (mInstance == null) 
                    mInstance = new MVUtils();
                
            
        
        return mInstance;
    

    /**
     * 写入基本数据类型缓存
     *
     * @param key    键
     * @param object 值
     */
    public static void put(String key, Object object) 
        if (object instanceof String) 
            mmkv.encode(key, (String) object);
         else if (object instanceof Integer) 
            mmkv.encode(key, (Integer) object);
         else if (object instanceof Boolean) 
            mmkv.encode(key, (Boolean) object);
         else if (object instanceof Float) 
            mmkv.encode(key, (Float) object);
         else if (object instanceof Long) 
            mmkv.encode(key, (Long) object);
         else if (object instanceof Double) 
            mmkv.encode(key, (Double) object);
         else if (object instanceof byte[]) 
            mmkv.encode(key, (byte[]) object);
         else 
            mmkv.encode(key, object.toString());
        
    

    public static void putSet(String key, Set<String> sets) 
        mmkv.encode(key, sets);
    

    public static void putParcelable(String key, Parcelable obj) 
        mmkv.encode(key, obj);
    

    public static Integer getInt(String key) 
        return mmkv.decodeInt(key, 0);
    

    public static Integer getInt(String key, int defaultValue) 
        return mmkv.decodeInt(key, defaultValue);
    

    public static Double getDouble(String key) 
        return mmkv.decodeDouble(key, 0.00);
    

    public static Double getDouble(String key, double defaultValue) 
        return mmkv.decodeDouble(key, defaultValue);
    

    public static Long getLong(String key) 
        return mmkv.decodeLong(key, 0L);
    

    public static Long getLong(String key, long defaultValue) 
        return mmkv.decodeLong(key, defaultValue);
    

    public static Boolean getBoolean(String key) 以上是关于Android app API环境切换需求与实现的主要内容,如果未能解决你的问题,请参考以下文章

App多语言实现

Android应用内切换语言

Android app应用多语言切换功能实现

tabhost实现android菜单切换

不只是切换多语言Android

Android主题切换(Theme)实现日夜间功能