通用的Android练习项目模板配置「常用工具类,项目结构,模板使用」
1 概述
作者:舍长III
原文链接: https://www.jianshu.com/p/cf6ce2139d94
为什么会有这篇文章呢,是因为我发现我以前在学习开发知识时,代码整理得太不好了。经常是会发生以前学习过的知识,现在却找不到代码在哪里的情况,于是又要重新开始。
因为Android开发涉及的内容很多,如果想要系统的去整理笔记的话,就必须要分文件夹,分项目进行管理。每个项目对应着不同方向的知识,而我们很多时候,每个练习项目都有着很多需要重复配置的东西。
比如常用的依赖啊,工具类,分包啊,还有基本的测试按钮,比如你想学习数据库操作,可能就会在xml布局中书写4个Button,再写上id,点击事件…基本上这些弄完,10分钟过去了。
而我接下来分享的小技巧,可能让你把原本半小时才能做完的事情,在5分钟内完成,因为我们会编写一个练习项目的模板,把重复的东西抽取出来。
文章不仅仅是为了介绍如何节约时间,更是因为在项目开发中经常要对各种Api进行封装处理,这也算是一个封装示例了。
项目采用Java语言,同时提供kotlin语言版本作为小彩蛋,需要说明的是,项目的开发环境是3.1.1,在3.0以下的软件运行起来需要调整,具体怎么调整我不告诉你,因为推荐升级软件。
项目源代码已经上传的Github仓库的template文件夹内了
https://github.com/13531982270/Blog4
现在,我们创建我们的template项目
2 创建项目结构
我们创建两个module,一个是默认的app,一个是appk,一个库common
- app用来编写Java测试代码
- appk用来编写kotlin测试代码
- common库用于存放一些公共的依赖,工具类
当然,这里的app和appk并不要求百分百严格区分Java和kotlin代码,看实际情况,比如你比较多的时间是在使用Java,那么就在app里面写,偶尔也可以在里面写些kotlin,这都无所谓。
现在打开右上角的File-Prokect Structure,我们把common库的依赖添加到app和appk中,这样两个module就能使用Common里面的东西啦。
3 App运行库
完成分包,配置主题,创建Menu菜单,drawable-hdpi,color,assets文件夹操作
1)创建项目的包结构
我们在module下创建以下包
然后,我来具体解释一下每个包的含义(其实看名字也挺清晰的了)
- adapter 适配器包,用来存放RecyclerView等的适配器
- app
创建自定义Application,因为后面可能配置很多第三方,而第三方的初始化建议是每个第三方单独的创建一个单例模式的类,提供一个init方法去初始化,不然全都写在Application里面,Application代码会太多,太凌乱,后面会提供一个AppConfig类作为示范,可以使用插件去生成单例模式的类,参考我的另一篇文章
https://www.jianshu.com/p/6a3b0ae4aeb4 - module
业务包,用来存放我们的各种实际练习代码,这里视你的项目是用来做什么来定,假如你是实际开发,就是一个登录包,一个主页包,如图,假如该项目你是用来学习Android原生控件,那么下面就是imageview,textview这样子; - 还有一个refrence包,这是用来存放你的练习草稿的,怎么说呢,你在练习的时候如果有什么需要单独提取出来测试的,你都可以在里面创建一个Activity,那个draft.txt也很好理解,就是草稿,你可以把在网上复制的代码先放到里面,后面写时可以去参考;
- net包用来存放网络请求相关的类;
- util包用来存放工具类;
- view包用来存放用到的自定义控件,可能你会有疑问了,实际开发中,view包不是都是放置在Common通用库里面的吗?是的,因为我们实际开发可能会使用组件化,多Module的开发,把view提取到Common通用库确确实实是为了让多个module来使用,但是这里我们只是练习模板项目,如果还要使用组件化那这个模板项目也就太复杂了,不符合我们当初节约时间的初衷,所以这里直接是,哪个Module的view就放在哪个module。
你可能还会有疑问,你App的包分得都那么详细了,那么还要Common通用库来做什么呢?
我是这么打算的,像下面举例子的L工具类,Glide工具类封装,都属于项目通用,必备的工具类,那就放在Common库,这样后面我们App模块的类不会太多。
而像一些只有在特定业务场景下才会使用达到的util工具类,你就直接放在App模块的util包下就可以了,毕竟工具类实在是太多了。我们不可能将每种都先考虑好放在Common库,而如果不在App下也设置一个util包,特定业务场景的工具类需要在Common库来回跳转就太麻烦了
比如下面这篇教程 https://www.jianshu.com/p/8b7186624ea5 中修改底部导航切换效果的工具类BottomNavigationViewHelper ,Common库还有的作用就是把经常用到的依赖,不至于使Module的代码太多。
2)配置主题和颜色
以前自己学习东西创建项目时,很喜欢把主题改成没有标题的,然后再到创建的Activity里面去创建几个Button,写点击事件去测试,这是多浪费时间的事情啊!
其实我们可以使用默认的主题来解决这个问题(为什么能解决后面会说),其实默认的主题并不丑,丑的是它的颜色,所以我们到res-values包下的colors文件,修改一下它的配色
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>
运行程序
或者更加青春一点的蓝色
<color name="colorAccent">#cc0094ff</color>
<color name="colorPrimary">#33aaff</color>
<color name="colorPrimaryDark">#2299ee</color>
你看,这样就好看多了吧
3)创建Menu文件,实现点击事件
上面已经解决了标题栏丑的问题,可能你还会觉得,默认的带标题的主题,未免有点占用我们的屏幕。是的,但是它的标题,却带给我们一个平时用的少,但是测试起来用的爽的东西,Menu
我们在res包下创建Menu文件夹,再创建menu.xml文件.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/one"
android:title="one" />
<item
android:id="@+id/two"
android:title="two" />
<item
android:id="@+id/three"
android:title="three" />
<item
android:id="@+id/four"
android:title="four" />
<item
android:id="@+id/five"
android:title="five" />
<item
android:id="@+id/six"
android:title="six" />
<item
android:id="@+id/seven"
android:title="seven" />
<item
android:id="@+id/eight"
android:title="eight" />
</menu>
8个按钮,足够你一个Activity测试了吧,毕竟比如练习数据库操作也就是增删改查,再多的代码也不适合放在同一个Activity里了,后面我们希望看到的,就是点击右边的…,就可以弹出我们的Menu测试按钮
之后我们在module-refrence包下创建JavaActiivty,将Activity作为第一启动项,编写以下代码来实现菜单的点击事件。
public class JavaActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_java);
setTitle("tonjies的页面");//设置标题名称
}
//创建菜单
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
//菜单选项
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int i = item.getItemId();
switch (i) {
//
case R.id.one:
Toast.makeText(this, "one", Toast.LENGTH_SHORT).show();
break;
//
case R.id.two:
Toast.makeText(this, "two", Toast.LENGTH_SHORT).show();
break;
//
case R.id.three:
Toast.makeText(this, "three", Toast.LENGTH_SHORT).show();
break;
//
case R.id.four:
Toast.makeText(this, "four", Toast.LENGTH_SHORT).show();
break;
//
case R.id.five:
Toast.makeText(this, "five", Toast.LENGTH_SHORT).show();
break;
//
case R.id.six:
Toast.makeText(this, "six", Toast.LENGTH_SHORT).show();
break;
//
case R.id.seven:
Toast.makeText(this, "seven", Toast.LENGTH_SHORT).show();
break;
//
case R.id.eight:
Toast.makeText(this, "eight", Toast.LENGTH_SHORT).show();
break;
}
return true;
}
}
运行效果如***意到我们onCreate方法里面的 setTitle(“tonjies的页面”);
如果我们不设置特定的标题,默认的标题名称就全都是项目名称,这样所有的页面标题都是一样,不利于区分。所以我们可以通过setTitle设置标题来区分是哪一个界面
ok,我们已经完美的完成了,可能你会觉得,这样代码量也不少啊,是的,但是它在所有Activity里面的代码都是一样的啊!
我们需要在哪个Activity用,就直接复制粘贴就可以了,其实也不推荐复制粘贴的方法,我们有更高效的,使用软件自己的代码模板功能,接下来演示一下该方法
代码模板具体的制作方法可以查看慕课网的教程的第三章,第二节
https://www.imooc.com/learn/924
不过实际上我们一般也用不了8个测试按钮这么多,我们快捷键一般设置4个就够了,当同时也把8个按钮的设置了,以防万一,比图4个按钮的快捷键是btnMenu,8个按钮的快捷键是btnMenu8这样。
另外如果你是使用Java代码开发,强烈推荐添加一个ButterKnife来节约我们findId控件的时间。
4)创建drawable-hdpi,color,asstes文件夹
可以将一些大图放置到比较高分辨率的文件夹下,因为放置在该文件夹里面的图片,多数情况下,如果屏幕分辨率较低,系统会帮助你进行一些压缩处理,避免当你不使用图片加载框架的时候,ImageView有可能因为图片过大而导致内存溢出,程序崩溃。
还有一个原因就是保持代码的整洁,我通常是.xml结尾的,例如选择器,使用shape制作的图片,vector图片就放置在drawable,而其它能直接看到的格式的,如,png,jpg,就会放置在drawable-hdpi。
我们可以事先防止几张图片在模板里,以备测试的不时之需。
有时候需要用网络图片来测试,所以也可以在Common的string.xml文件夹下添加几条网络图片链接
<resources>
<string name="app_name">template</string>
<string name="meizi_01">https://ww1.sinaimg.cn/large/0065oQSqly1fu7xueh1gbj30hs0uwtgb.jpg</string>
</resources>
而上面的颜色选择color包可能会在某些场景下用,比如使用BottomNavigationView的时候。
最后是我们的assets文件夹,用来存放如字体,本地调试html页面等,在源码里的Refrence包下会有调用字体文件的参考代码。
4 Common通用库
该库用来存放我们练习中几乎必备的工具类和相关的依赖,我们把这些东西提取在Common包里面,让app去添加它,这样app就不用显示过多的依赖,显得比较简洁,而且我们的appk同样可以使用它。
1)Log工具类
我们在common库中创建util文件夹,然后创建L日志打印工具类,简化我们后面的日志操作:
/**
* Created by 舍长 on 2018/12/14
* describe: 日志工具类
*/
public class L {
public void d(String msg) {
Log.d("helloWorld", msg);
}
}
后面就可以直接L.d使用啦。
不过实际项目开发中要再加一个过滤操作,因为我们的App上线之后,如果Log不关闭,有泄漏数据的风险,涉及到安全问题。所以我们可以这样做。
public class L {
private static final Boolean isOpen=true;
//普通版
public static void d(String msg) {
//打开日志
if(isOpen){
Log.d("helloWorld", msg);
}else {
//关闭日志
}
}
//进行String类型转换,所以我们可以随意传入任意类型的参数
public static void d(Object msg) {
String string = msg.toString();
Log.d("helloWorld", string);
}
}
设置一个变量,如何我们想要打印的话,就设置为true,不想打印的话,就将它设置为Fasle就可以啦,我们还重载了一个参数Object的d方法,该方法的作用是将传入的任意类型的字符串转换成String类型
这样做是为了方便我们可以传入任何类型的数据,这样我们就可以省去将数据转成String类型这一步了,
相信这难不倒你啦,接着我们来看第二个封装类,SharedPreferences封装类。
2)SharedPreferences封装类ACache
这里我们需要考虑一个问题,就是SharedPreferences和Log打印不一样,它是需要获取到当前的Context对象的,如下面所示在Activity中使用的getSharedPreferences,其实它隐藏了context的使用,实际代码是context.getSharedPreferences。
存数据
SharedPreferences.Editor editor=getSharedPreferences("data",MODE_PRIVATE).edit();
editor.putString("name","tonjies");
editor.putInt("age",20);
editor.apply();
取数据
SharedPreferences preferences = getSharedPreferences("data", MODE_PRIVATE);
String name = preferences.getString("name", "");
int age = preferences.getInt("age", 18);
L.d("name:" + name);
L.d("age" + age);
你第一个想到的,可能是可以通过参数,将当前Activity的Context对象传进来进行封装。
可是这样每次都要写个Activity.this,一点都不酷,所以我们使用另外一种方法,封装统一的全局Context对象,这在组件化的开发中也很常见,实际操作并不复杂,我们来看一下.
我们的Common库中创建app包,在该包下创建BaseApp类,让该类继承于Application,作为我们的基础Application,然后我们提供一个getContext()方法来返回Context对象.
3)AGide
封装思想在开发中应该是很重要的,其中不仅仅是因为我们为了节约时间,更是为了稳妥起见。
举个例子,我们如果使用图片加载框架glide,我们并不会直接的去使用它,而是会把它的代码写在一个包装类里面,使用包装类的方法去加载图片,这样实际上还是使用glide,那么这样有什么意义呢?
/**
* Created by 舍长 on 2019/1/2
* describe:Glide封装类
*/
public class AGlide {
//获取Url地址
public static void getUrl(Context context, String url, ImageView imageView) {
Glide.with(context).load(url).into(imageView);
}
}
试想这么一个场景,某一天你因为某些原因,不得不放弃glide,使用另外一个图片加载框架,那简直就是灾难,因为你代码中使用的地方实在是太多了,你要一个个的去注释,然后改成新的Api,而如果你是使用包装类,你只要把包装类里面的glide代码改了就行了,对整个项目并没有太大的影响:
public class AGlide {
//获取Url地址
public static void getUrl(Context context, String url, ImageView imageView) {
// Glide.with(context).load(url).into(imageView);
//伪代码,这里假装使用Picasso的Api
}
}
工具类部分举例子结束,除了这些常用的,我们还可以在工具类中封装Toast工具类啊,字体工具类啊,在源码中我会把它两补上。
3)添加常用依赖
这里需要注意的是,3.0以上后,AndroidStudio添加依赖推荐使用api和implementation和替代compile,不然build时就会报红(虽然还是可以运行)。
implementation和api的区别在于,implementation用于运行Module,api用于库添加依赖,什么意思呢,就是如果你在Common库里使用implementation来添加依赖,那么你的AppModule是检测不到你添加的依赖的。
换句话来说,implementation只能是当它自己的范围使用,不管你是库Module还是运行Module,所以如果我们想让在Common通用库里面加载的框架到AppModule运行Module里能使用的话,得使用api才行。
接下来我们在Common的budile.gradle下添加这几个依赖:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//Anko框架,用于kotlin开发扩展
api "org.jetbrains.anko:anko-commons:0.10.7"
//圆形处理框架
api 'de.hdodenhof:circleimageview:2.2.0'
// 材料设计
api 'com.android.support:design:28.0.0'
// Retrofit库
api 'com.squareup.retrofit2:retrofit:2.4.0'
api 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0' // 此处一定要注意使用RxJava2的版本
api 'com.squareup.retrofit2:converter-gson:2.4.0' // 支持Gson解析
// Okhttp库
api 'com.squareup.okhttp3:okhttp:3.11.0'
api 'com.squareup.okhttp3:logging-interceptor:3.11.0'
// 支持Gson解析
api 'com.squareup.retrofit2:converter-gson:2.4.0'
// RxJava
api 'io.reactivex.rxjava2:rxjava:2.1.7'
api 'io.reactivex.rxjava2:rxandroid:2.0.1'
// 图片加载框架
api 'com.github.bumptech.glide:glide:4.7.1'
// 卡片布局
api 'com.android.support:cardview-v7:28.0.0'
implementation 'com.android.support:appcompat-v7:28.0.0'
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'
api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
repositories {
mavenCentral()
}
添加常用第三方
我们可以选择使用腾讯bugly进行程序的异常检测,集成步骤比较简单,官网文档非常详细。
因为也不需要添加程序特定包名,所以我们注册一个应用,后面就可以一直用同一个ID了。
为什么要使用它呢,如果是上线应用,就必须使用它来进行异常检测,不然别的用户使用出现适配问题了,鬼知道发生了什么。
但是如果是对于我们学习来说的话,有时候通过AndroidStudio你检测不出error在哪里,虽然场景比较少,比如使用RxJava进行封装的时候,如果是独立于Actiivty之外的封装,log检测不出来。
5 实际练习代码的分类
1)笔记库的搭建
有了上面的项目模板,后面我们需要的话,就可以直接复制,粘贴,改个名字就可以直接上手学习了,但是我们的Android开发的内容很多,显然不是一个项目能放得下的。
所以根据学习内容的不同,我大致将它们分为以下内容,进而我们可以为不同的练习内容设置不同的项目文件名
大致就是这样,我们复制以上数量的项目文件夹,更改完名字后,我们的笔记库到这里就结束了,也许整个过程是稍显复杂,但是我们只要制作一次。
后面我们需要找代码就方便很多,还有的好处就是如果你上次在某个方面学习到一半,有其他的事情,你下次可以很快的找到,继续学习。
上面可能你会有个疑问,开源库和主流框架的区别,其实本质上是没有区别,但是我去区分它们的标准是,它们是否极其重要,不可替代的,极其重要的,如RxJava,就把它归纳到框架里面。
而其它的,像提供某个UI控件的库,使用的少,我们可以找到替代的,甚至我们可以自己写一个的,就会被归纳到库library里面。
2)配合笔记软件
上面制作的笔记目录,不一定适合你,实际上你可以使用笔记软件,石墨文档制作属于你自己的练习项目名称目录。
而有一些笔记,也是使用笔记软件记录比较合适。简单来说就是,你的文字笔记目录和你的练习代码仓库的目录是一致的。
不过需要注意的是,文字笔记和我们的练习代码仓库一样,不同类别的笔记是重新创建新的笔记来写的
这是因为石墨文档如果你这是同一份文档写的内容太多就会卡,这里我把我的总目录文档发现出来作为参考:
点进去后就是每一类知识的具体笔记。
不过不同类别的文档都会被放置在Android笔记的文件夹中:
好了,本篇文章就到这里了,老实说,这是我写的30篇文章中感觉写得最累的一次,因为涉及的东西虽然不难,但是很多,所以很累。但是本篇文章基本都是干货,实际运用中,能节约你大量配置项目的时间。
文章并没有对RxJava和Retrofit的封装进行详细的讲解,因为这一块讲起来是得花很多的篇幅,不过如果真要说封装项目模块,它又几乎是必需的。
再叨一下,例如组件化方案 https://blog.****.net/guiying712/article/details/55213884, 路由框架 https://blog.****.net/zhaoyanjun6/article/details/76165252 的学习,虽然作为练习项目模块我不想用,但是实际开发用的还是蛮多的,带来的安利希望对大家有所帮助。