Android组件化框架项目详解
简介
什么是组件化?
项目发展到一定阶段时,随着需求的增加以及频繁地变更,项目会越来越大,代码变得越来越臃肿,耦合会越来越多,开发效率也会降低,这个时候我们就需要对旧项目进行重构即模块的拆分,官方的说法就是组件化。
组件化带来的好处
那么,采用组件化能带来什么好处呢?主要有以下两点:
1、现在Android项目中代码量达到一定程度,编译将是一件非常痛苦的事情,一般都需要编译5到6分钟。Android Studio 推出 instant run 由于各种缺陷和限制条件(比如采用热修复tinker)一般情况下是被关闭的。而组件化框架可以使模块单独编译调试,可以有效地减少编译的时间。
2、通过组件化可以更好的进行并行开发,因为我们可以为每一个模块进行单独的版本控制,甚至每一个模块的负责人可以选择自己的设计架构而不影响其他模块的开发,与此同时组件化还可以避免模块之间的交叉依赖,每一个模块的开发人员可以对自己的模块进行独立测试,独立编译和运行,甚至可以实现单独的部署。从而极大的提高了并行开发效率。
组件化框架
来看组件化一个简单的例子,图例如下:
基类库的封装
对于Android中常用的基类库,主要包括开发常用的一些框架。
1、网络请求(多任务下载和上传,采用 Retrofit+RxJava 框架)
2、图片加载(策略模式,Glide 与 Picasso 之间可以切换)
3、通信机制(RxBus)
4、基类 adapter 的封装(支持 item动画、多布局item、下拉和加载更多、item点击事件)
5、基类 RecyclerView 的封装(支持原生风格的下拉加载,item侧滑等)
6、mvp 框架
7、各组件的数据库实体类
8、通用的工具类
9、自定义view(包括对话框,ToolBar布局,圆形图片等view的自定义)
10、dagger 的封装(用于初始化全局的变量和网络请求等配置)
11、其他等等
组件模式和集成模式切换的实现
music组件 下的 build.gradle 文件,其他组件类似。
/控制组件模式和集成模式
if (rootProject.ext.isAlone) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library' }
apply plugin: 'com.neenbedankt.android-apt' android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
if (rootProject.ext.isAlone) {
//组件模式下设置applicationId
applicationId "com.example.cootek.music"
}
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
if (!rootProject.ext.isAlone) {
//集成模式下Arouter的配置,用于组件间通信的实现
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
sourceSets {
main {
//控制两种模式下的资源和代码配置情况
if (rootProject.ext.isAlone) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/module/java']
res.srcDirs = ['src/main/res', 'src/main/module/res']
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
} dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
//依赖基类库
compile project(':commonlibrary')
//用作颜色选择器
compile 'com.afollestad.material-dialogs:commons:0.9.1.0'
apt rootProject.ext.dependencies.dagger2_compiler
if (!rootProject.ext.isAlone) {
//集成模式下需要编译器生成路由通信的代码
apt rootProject.ext.dependencies.arouter_compiler
}
testCompile 'junit:junit:4.12'
}
为了区分集成模式和组件模式,我们使用isAlone变量来控制。
集成模式
1、首先需要在 config.gradle 文件中设置 isAlone = false。形如:
ext {
isAlone = false; //false:作为Lib组件存在,true:作为application存在
2、然后 Sync 下。
3、最后选择 app 运行即可。
组件模式
1、首先需要在 config.gradle 文件中设置 isAlone = true
2、然后 Sync 下。
3、最后相应的模块(new、chat、live、music、app)进行运行即可。
config.gradle 文件的配置情况如下:
ext {
isAlone = false;//false:作为集成模式存在,true:作为组件模式存在 // 各个组件版本号的统一管理
android = [
compileSdkVersion: ,
buildToolsVersion: "25.0.2",
minSdkVersion : ,
targetSdkVersion : ,
versionCode : ,
versionName : '1.0.0',
] libsVersion = [
// 第三方库版本号的管理
supportLibraryVersion = "25.3.0",
retrofitVersion = "2.1.0",
glideVersion = "3.7.0",
loggerVersion = "1.15",
// eventbusVersion = "3.0.0",
gsonVersion = "2.8.0",
butterknife = "8.8.0",
retrofit = "2.3.0",
rxjava = "2.1.1",
rxjava_android = "2.0.1",
rxlifecycle = "2.1.0",
rxlifecycle_components = "2.1.0",
dagger_compiler = "2.11",
dagger = "2.11",
greenDao = "3.2.2",
arouter_api = "1.2.2",
arouter_compiler = "1.1.3",
transformations = "2.0.2",
rxjava_adapter = "2.3.0",
gson_converter = "2.3.0",
scalars_converter = "2.3.0",
rxpermission = "0.9.4",
eventbus="3.0.0",
support_v4="25.4.0",
okhttp3="3.8.1"
] // 依赖库管理
dependencies = [
appcompatV7 : "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion",
design : "com.android.support:design:$rootProject.supportLibraryVersion",
cardview : "com.android.support:cardview-v7:$rootProject.supportLibraryVersion",
palette : "com.android.support:palette-v7:$rootProject.supportLibraryVersion",
recycleview : "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion",
support_v4 : "com.android.support:support-v4:$rootProject.support_v4",
annotations : "com.android.support:support-annotations:$rootProject.supportLibraryVersion",
eventBus : "org.greenrobot:eventbus:$rootProject.eventbus",
glide : "com.github.bumptech.glide:glide:$rootProject.glideVersion",
gson : "com.google.code.gson:gson:$rootProject.gsonVersion",
logger : "com.orhanobut:logger:$rootProject.loggerVersion",
butterknife : "com.jakewharton:butterknife:$rootProject.butterknife",
butterknife_compiler : "com.jakewharton:butterknife-compiler:$rootProject.butterknife",
retrofit : "com.squareup.retrofit2:retrofit:$rootProject.retrofit",
okhttp3 : "com.squareup.okhttp3:okhttp:$rootProject.retrofit",
retrofit_adapter_rxjava2 : "com.squareup.retrofit2:adapter-rxjava2:$rootProject.rxjava_adapter",
retrofit_converter_gson : "com.squareup.retrofit2:converter-gson:$rootProject.gson_converter",
retrofit_converter_scalars: "com.squareup.retrofit2:converter-scalars:$rootProject.scalars_converter",
rxpermission : "com.tbruyelle.rxpermissions2:rxpermissions:$rootProject.rxpermission@aar",
rxjava2 : "io.reactivex.rxjava2:rxjava:$rootProject.rxjava",
rxjava2_android : "io.reactivex.rxjava2:rxandroid:$rootProject.rxjava_android",
rxlifecycle2 : "com.trello.rxlifecycle2:rxlifecycle:$rootProject.rxlifecycle",
rxlifecycle2_components : "com.trello.rxlifecycle2:rxlifecycle-components:$rootProject.rxlifecycle_components",
dagger2_compiler : "com.google.dagger:dagger-compiler:$rootProject.dagger_compiler",
dagger2 : "com.google.dagger:dagger:$rootProject.dagger",
greenDao : "org.greenrobot:greendao:$rootProject.greenDao",
transformations : "jp.wasabeef:glide-transformations:$rootProject.transformations",
//路由通讯
arouter_api : "com.alibaba:arouter-api:$rootProject.arouter_api",
arouter_compiler : "com.alibaba:arouter-compiler:$rootProject.arouter_compiler"
]
}
组件间通信实现
组件间通信的实现可以使用阿里开源的 Arouter 路由通信。相关内容可以查看:https://github.com/alibaba/ARouter。
首先,初始化所有的数据信息。
private List<MainItemBean> getDefaultData() {
List<MainItemBean> result = new ArrayList<>();
MainItemBean mainItemBean = new MainItemBean();
mainItemBean.setName("校园");
mainItemBean.setPath("/news/main");
mainItemBean.setResId(R.mipmap.ic_launcher);
MainItemBean music=new MainItemBean();
music.setName("音乐");
music.setResId(R.mipmap.ic_launcher);
music.setPath("/music/main");
MainItemBean live = new MainItemBean();
live.setName("直播");
live.setResId(R.mipmap.ic_launcher);
live.setPath("/live/main");
MainItemBean chat = new MainItemBean();
chat.setName("聊天");
chat.setPath("/chat/splash");
chat.setResId(R.mipmap.ic_launcher);
result.add(mainItemBean);
result.add(music);
result.add(live);
result.add(chat);
return result;
}
然后在设置每个 item 的点击事件时,启动组件界面跳转。
@Override
public void onItemClick(int position, View view) {
MainItemBean item=mainAdapter.getData(position);
ARouter.getInstance().build(item.getPath()).navigation();
}
每个组件入口界面的设置(比如直播 Live 组件,其它组件类似)。
@Route(path = "/live/main")
public class MainActivity extends BaseActivity<List<CategoryLiveBean>, MainPresenter> implements View.OnClickListener {
//
}
res资源和AndroidManifest配置
我们通过判断组件处于哪种模式来动态设置项目res资源和Manifest、以及代码的位置。以直播组件为例,其它组件类似。
作为一个组件模块后,再来看一下直播组件的 build.gradle 文件对代码资源等位置的配置。
sourceSets {
main {
if (rootProject.ext.isAlone) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/module/java']
res.srcDirs = ['src/main/res', 'src/main/module/res']
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
全局application的实现和数据的初始化
采用类似于 Glide 在 Manifest 初始化配置的方式来初始化各个组件的 Application,下面以直播组件为例来完成初始化,其它类似。
在 BaseApplication 中,初始化 ApplicationDelegate 代理类。
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
applicationDelegate = new ApplicationDelegate();
applicationDelegate.attachBaseContext(base);
MultiDex.install(this);
}
ApplicationDelegate 内部是怎样的呢,看一段源码。
public class ApplicationDelegate implements IAppLife {
private List<IModuleConfig> list;
private List<IAppLife> appLifes;
private List<Application.ActivityLifecycleCallbacks> liferecycleCallbacks; public ApplicationDelegate() {
appLifes = new ArrayList<>();
liferecycleCallbacks = new ArrayList<>();
} @Override
public void attachBaseContext(Context base) {
//初始化Manifest文件解析器,用于解析组件在自己的Manifest文件配置的Application
ManifestParser manifestParser = new ManifestParser(base);
list = manifestParser.parse();
//解析得到的组件Application列表之后,给每个组件Application注入
//context,和Application的生命周期的回调,用于实现application的同步
if (list != null && list.size() > ) {
for (IModuleConfig configModule :
list) {
configModule.injectAppLifecycle(base, appLifes);
configModule.injectActivityLifecycle(base, liferecycleCallbacks);
}
}
if (appLifes != null && appLifes.size() > ) {
for (IAppLife life :
appLifes) {
life.attachBaseContext(base);
}
}
} @Override
public void onCreate(Application application) {
//相应调用组件Application代理类的onCreate方法
if (appLifes != null && appLifes.size() > ) {
for (IAppLife life :
appLifes) {
life.onCreate(application);
}
}
if (liferecycleCallbacks != null && liferecycleCallbacks.size() > ) {
for (Application.ActivityLifecycleCallbacks life :
liferecycleCallbacks) {
application.registerActivityLifecycleCallbacks(life);
}
}
} @Override
public void onTerminate(Application application) {
//相应调用组件Application代理类的onTerminate方法
if (appLifes != null && appLifes.size() > ) {
for (IAppLife life :
appLifes) {
life.onTerminate(application);
}
}
if (liferecycleCallbacks != null && liferecycleCallbacks.size() > ) {
for (Application.ActivityLifecycleCallbacks life :
liferecycleCallbacks) {
application.unregisterActivityLifecycleCallbacks(life);
}
}
}
}
组件 Manifest 中 application 的全局配置如下:
<meta-data
android:name="com.example.live.LiveApplication"
android:value="IModuleConfig" />
ManifestParser 会对其中 value 为 IModuleConfig 的 meta-data 进行解析,并通过反射生成实例。
public final class ManifestParser {
private static final String MODULE_VALUE = "IModuleConfig";
private final Context context;
public ManifestParser(Context context) {
this.context = context;
}
public List<IModuleConfig> parse() {
List<IModuleConfig> 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()) {
//会对其中value为IModuleConfig的meta-data进行解析,并通过反射生成实例
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 IModuleConfig", e);
}
return modules;
} //通过类名生成实例
private static IModuleConfig parseModule(String className) {
Class<?> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unable to find IModuleConfig implementation", e);
} Object module;
try {
module = clazz.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Unable to instantiate IModuleConfig implementation for " + clazz, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to instantiate IModuleConfig implementation for " + clazz, e);
} if (!(module instanceof IModuleConfig)) {
throw new RuntimeException("Expected instanceof IModuleConfig, but found: " + module);
}
return (IModuleConfig) module;
}
}
这样通过以上步骤就可以在 Manifest 文件中配置自己组件的 Application,用于初始化组件内的数据,比如在直播组件中初始化 Dagger注解 的全局配置。
public final class ManifestParser {
private static final String MODULE_VALUE = "IModuleConfig";
private final Context context;
public ManifestParser(Context context) {
this.context = context;
}
public List<IModuleConfig> parse() {
List<IModuleConfig> 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()) {
//会对其中value为IModuleConfig的meta-data进行解析,并通过反射生成实例
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 IModuleConfig", e);
}
return modules;
} //通过类名生成实例
private static IModuleConfig parseModule(String className) {
Class<?> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unable to find IModuleConfig implementation", e);
} Object module;
try {
module = clazz.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Unable to instantiate IModuleConfig implementation for " + clazz, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to instantiate IModuleConfig implementation for " + clazz, e);
} if (!(module instanceof IModuleConfig)) {
throw new RuntimeException("Expected instanceof IModuleConfig, but found: " + module);
}
return (IModuleConfig) module;
}
}
这样通过以上步骤就可以在 Manifest 文件中配置自己组件的 Application,用于初始化组件内的数据,比如在直播组件中初始化 Dagger注解 的全局配置。
public class LiveApplication implements IModuleConfig,IAppLife {
private static MainComponent mainComponent; @Override
public void injectAppLifecycle(Context context, List<IAppLife> iAppLifes) {
//这里需要把本引用添加到Application的生命周期的回调中,以便实现回调
iAppLifes.add(this);
} @Override
public void injectActivityLifecycle(Context context, List<Application.ActivityLifecycleCallbacks> lifecycleCallbackses) {
} @Override
public void attachBaseContext(Context base) {
} @Override
public void onCreate(Application application) {
//在onCreate方法中对Dagger进行初始化
mainComponent = DaggerMainComponent.builder().mainModule(new MainModule())
.appComponent(BaseApplication.getAppComponent()).build();
} @Override
public void onTerminate(Application application) {
if (mainComponent != null) {
mainComponent = null;
}
} public static MainComponent getMainComponent() {
return mainComponent;
}
}
组件内网络请求和拦截器
由于每个组件的 BaseUrl 和网络配置等可能不一样,所以每个组件可以在自己配置的 dagger 中的 MainConponent 实现自己的网络请求和拦截器。以直播为例,部分代码内容如下:
MainComponent:
@PerApplication
@Component(dependencies = AppComponent.class, modules = MainModule.class)
public interface MainComponent {
public DaoSession getDaoSession(); public MainRepositoryManager getMainRepositoryManager();
}
MainModule部分代码:
public class MainModule {
@Provides
@PerApplication
public MainRepositoryManager provideRepositoryManager(@Named("live") Retrofit retrofit, DaoSession daoSession) {
return new MainRepositoryManager(retrofit, daoSession);
}
@Provides
@Named("live")
@PerApplication
public Retrofit provideRetrofit(@Named("live") OkHttpClient okHttpClient,@Nullable Gson gson){
Retrofit.Builder builder=new Retrofit.Builder().baseUrl(LiveUtil.BASE_URL).addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson)).client(okHttpClient);
return builder.build();
}
@Provides
@Named("live")
@PerApplication
public OkHttpClient provideOkHttpClient(@Named("live")LiveInterceptor interceptor){
OkHttpClient.Builder builder=new OkHttpClient.Builder();
builder.connectTimeout(, TimeUnit.SECONDS).readTimeout(,TimeUnit.SECONDS);
builder.addInterceptor(interceptor);
return builder.build();
}
@Provides
@Named("live")
@PerApplication
public LiveInterceptor provideNewsInterceptor(){
return new LiveInterceptor();
}
}
难点
在项目中使用组件化,可能会遇到很多问题,下面将问题罗列如下:
资源命名冲突
官方说法是在每个 module 的 build.gradle 文件中配置资源文件名前缀。
这种方法缺点就是,所有的资源名必须要以指定的字符串(moudle_prefix)做前缀,否则会异常报错,而且这方法只限定xml里面的资源,对图片资源并不起作用,所以图片资源仍然需要手动去修改资源名。所以不是很推荐使用这种方法来解决资源名冲突。所以只能自己注意点,在创建资源的时候,尽量不让其重复。例如:
resourcePrefix "moudle_prefix"
butterKnife使用问题
虽然 Butterknife 支持在 lib 中使用,但是条件是用 R2 代替 R ,在组件模式和集成模式的切换中,R2<->R 之间的切换是无法完成转换的,切换一次要改动全身,是非常麻烦的!所以不推荐在组件化中使用 Butterknife。
library重复依赖问题
相信这个问题,大家在平时的开发中都会遇到,所以我们需要将多余的包给排除出去。可以参考如下的配置:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile(rootProject.ext.dependencies.appcompatV7) {
exclude module: "support-v4"
exclude module: "support-annotations"
}
compile rootProject.ext.dependencies.recycleview
compile rootProject.ext.dependencies.design compile(rootProject.ext.dependencies.support_v4) {
exclude module: "support-annotations"
}
compile rootProject.ext.dependencies.annotations
compile(rootProject.ext.dependencies.butterknife) {
exclude module: 'support-annotations'
}
compile rootProject.ext.dependencies.rxjava2
compile(rootProject.ext.dependencies.rxjava2_android) {
exclude module: "rxjava"
}
compile(rootProject.ext.dependencies.rxlifecycle2) {
exclude module: 'rxjava'
exclude module: 'jsr305'
}
compile(rootProject.ext.dependencies.rxlifecycle2_components) {
exclude module: 'support-v4'
exclude module: 'appcompat-v7'
exclude module: 'support-annotations'
exclude module: 'rxjava'
exclude module: 'rxandroid'
exclude module: 'rxlifecycle'
}
compile(rootProject.ext.dependencies.retrofit) {
exclude module: 'okhttp'
exclude module: 'okio'
}
compile(rootProject.ext.dependencies.retrofit_converter_gson) {
exclude module: 'gson'
exclude module: 'okhttp'
exclude module: 'okio'
exclude module: 'retrofit'
}
compile(rootProject.ext.dependencies.retrofit_adapter_rxjava2) {
exclude module: 'rxjava'
exclude module: 'okhttp'
exclude module: 'retrofit'
exclude module: 'okio'
}
compile rootProject.ext.dependencies.greenDao
compile rootProject.ext.dependencies.okhttp3
compile rootProject.ext.dependencies.gson
compile rootProject.ext.dependencies.glide
compile rootProject.ext.dependencies.eventBus
compile rootProject.ext.dependencies.dagger2
compile(rootProject.ext.dependencies.rxpermission) {
exclude module: 'rxjava'
}
compile rootProject.ext.dependencies.retrofit_converter_scalars
annotationProcessor rootProject.ext.dependencies.dagger2_compiler
annotationProcessor rootProject.ext.dependencies.butterknife_compiler
compile rootProject.ext.dependencies.butterknife
compile rootProject.ext.dependencies.transformations
compile rootProject.ext.dependencies.arouter_api
}
附:项目实例
优秀项目参考:
MVPArms
https://github.com/JessYanCoding/MVPArms
全民直播
https://github.com/jenly1314/KingTV
音乐项目
https://github.com/hefuyicoder/ListenerMusicPlayer
https://github.com/aa112901/remusic
大象:PHPHub客户端
https://github.com/Freelander/Elephant
MvpApp
https://github.com/Rukey7/MvpApp
CloudReader
https://github.com/youlookwhat/CloudReader
Android组件化框架项目详解的更多相关文章
- [Android Pro] 终极组件化框架项目方案详解
cp from : https://blog.csdn.net/pochenpiji159/article/details/78660844 前言 本文所讲的组件化案例是基于自己开源的组件化框架项目g ...
- Android热门网络框架Volley详解[申明:来源于网络]
Android热门网络框架Volley详解[申明:来源于网络] 地址:http://www.cnblogs.com/caobotao/p/5071658.html
- 从零开始搭建Android组件化框架
问题 在已经开发过几个项目的童鞋,如果这时需要重新开发一个新项目,是否需要自己重新搭建框架呢,还是从老项目中拷贝粘贴? 我们是否可以封装一个底层的lib库,这个底层的公共基础库 包括了一些第三方库(如 ...
- Android组件化框架设计与实践
在目前移动互联网时代,每个 APP 就是流量入口,与过去 PC Web 浏览器时代不同的是,APP 的体验与迭代速度影响着用户的粘性,这同时也对从事移动开发人员提出更高要求,进而移动端框架也层出不穷. ...
- Android热门网络框架Volley详解
.Volley简介 volley的英文意思为‘群发’.‘迸发’.Volley是2013年谷歌官方发布的一款Android平台上的网络通信库.Volley非常适合一些数据量不大,但需要频繁通信的网络操作 ...
- Android 开发 MaterialDialog框架的详解
前言 开始之前还是需要废话一下,因为有一些坑需要告知.首先MaterialDialog在GitHub上作者已经转型使用100% Kotlin语言编写,虽然可以在Java里调用Kotlin使用.但是个人 ...
- 我所理解的Android组件化之通信机制
之前写过一篇关于Android组件化的文章,<Android组件化框架设计与实践>,之前没看过的小伙伴可以先点击阅读.那篇文章是从实战中进行总结得来,是公司的一个真实项目进行组件化架构改造 ...
- 详解Android中的四大组件之一:Activity详解
activity的生命周期 activity的四种状态 running:正在运行,处于活动状态,用户可以点击屏幕,是将activity处于栈顶的状态. paused:暂停,处于失去焦点的时候,处于pa ...
- JAVA Eclipse使用Maven构建web项目详解(SSM框架)
tips: 启动项目后,welcome-file的链接即为测试用例 部署maven web项目 Eclipse使用Maven构建web项目详解 pom.xml添加webapp依赖: <depen ...
随机推荐
- js 弹性导航
<style> *{margin:0;padding:0;} #box{height:50px;float:left;position:relative;background:#f90;} ...
- [Alpha]Scrum Meeting#7
github 本次会议项目由PM召开,时间为4月9日晚上10点30分 时长15分钟 任务表格 人员 昨日工作 下一步工作 木鬼 整理并发布之前因为清明耽误的博客 撰写每日例会报告 SiMrua 添加暂 ...
- npm install 报错:ERR! code EINTEGRITY 解决方案
npm升级后,npm install 报错了,报错信息:ERR! code EINTEGRITY到处百度搜索解决方案,终于找到了!“npm cache verify”这条命令帮助了不少人 npm ca ...
- 蓝桥杯-Anagrams问题
算法训练 Anagrams问题 时间限制:1.0s 内存限制:512.0MB 问题描述 Anagrams指的是具有如下特性的两个单词:在这两个单词当中,每一个英文字母(不区分大小写 ...
- RabbitMQ初学之一:exchange与queue的绑定
最近公司需要使用RabbitMQ,但我之前一直使用的是ActiveMQ,对RabbitMQ进行了初步的学习,但是还不系统,自己做了一些小测试,怕自己以后忘了 一. 背景 拿到代码以后,发现,生产者在向 ...
- Python2.7环境下安装pydbg
From:http://blog.csdn.net/cheng_tian/article/details/7652058 最近在看<Python灰帽子:黑客与逆向工程师的Python编程之道&g ...
- PHP之string之trim()函数使用
trim (PHP 4, PHP 5, PHP 7) trim - Strip whitespace (or other characters) from the beginning and end ...
- 如果天空不死博客java阅读列表整理
如果天空不死的主页https://home.cnblogs.com/u/skywang12345 下面是最近总结的Java集合(JDK1.6.0_45)相关文章的目录. 01. Java 集合系列01 ...
- linux 安装jdk 二进制版本,非安装版
0.下载jdk8 登录网址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html选择对 ...
- Yii2:记一次尴尬的bug
创建一个文章模块,写完添加动作之后,分配到视图,发现报错: Exception (Not Supported) 'yii\base\NotSupportedException' with messag ...