hammer崔的程序世界

我的生涯一片无悔,我想起那个午夜在灯泡下的抠代码,那是我逝去的青春!


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 搜索
close

android最佳实践8:dagger2框架

发表于 2016-06-17   |   分类于 android   |  

dagger2

dagger2是一种依赖注入框架,由square开发,现在google负责维护。dagger2一般配合mvp,在mvp已经解耦的基础上,让解耦变得更彻底,以便于测试及维护。

dagger2与dagger的区别

  • 再也没有使用反射:图的验证、配置和预先设置都在编译的时候执行。
  • 容易调试和可跟踪:完全具体地调用提供和创建的堆栈
  • 更好的性能:谷歌声称他们提高了13%的处理性能
  • 代码混淆:使用派遣方法,就如同自己写的代码一样

当然所有这些很棒的特点都需要付出一个代价,那就是缺乏灵活性,例如:Dagger2没用反射所以没有动态机制。

注解含义

  • @Inject: 通常在需要依赖的地方使用这个注解,标识我们需要的实例。另外在@Provide中,直接返回的实例,需要在类的构造函数添加@Inject

  • @Module: 我们定义一个类,用@Module注解,Modules类里面的方法专门提供依赖。这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的依赖。modules的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的Component中可以有多个组成在一起的modules)。

  • @Provide: 在Modules类中,我们定义的方法是用这个注解。这些方法都是用来提供依赖,生成实例的。

  • @Singlton:常用在@Provide之前,表示提供的依赖是个单例,也就是每次都提供同一个实体。

  • @Component: 我们定义一个接口,用Components注解,我们可以理解为Component就是一个注入器,是@Inject和@Module的桥梁。它的主要作用就是连接这两个部分。 Components可以提供所有定义了的类型的实例,比如:我们必须用@Component注解一个接口然后列出所有的@Modules组成该组件,如果缺失了任何一块都会在编译的时候报错。所有的组件都可以通过它的Modules知道依赖的范围。

  • @Scope: Dagger2可以通过自定义注解限定注解作用域。后面会演示一个例子,这是一个非常强大的特点,因为就如前面说的一样,没 必要让每个对象都去了解如何管理他们的实例。在scope的例子中,我们用自定义的@PerActivity注解一个类,所以这个对象存活时间就和 activity的一样。简单来说就是我们可以定义所有范围的粒度(@PerFragment, @PerUser, 等等)。

  • Qualifier: 当类的类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示。例如:在Android中,我们会需要不同类型的context,所以我们就可以定义 qualifier注解“@ForApplication”和“@ForActivity”,这样当注入一个context的时候,我们就可以告诉 Dagger我们想要哪种类型的context。

引入工程

在app/build.gradle添加依赖

1
2
3
4
5
6
7
8
9
10
apply plugin:'android-apt'

dependencies {
...
//dragger2
//provided 'org.glassfish:javax.annotation:10.0-b28'
apt 'com.google.dagger:dagger-compiler:2.0.2'
compile 'com.google.dagger:dagger:2.0.2'
...
}

因为Dragger2是基于注解的,它会预先生成一些类文件,所以需要在整个项目的/build.gradle文件中加上apt工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
buildscript {
repositories {
jcenter()
}
dependencies {
...
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}

allprojects {
repositories {
jcenter()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

demo详解

我们通过使用greenDao的示例来说明,引入dagger2的好处。greenDao是非常流行的管理sqldata的orm,我们希望他以单例的形式存在,生命周期等同于Application。

首先,创建GreenDaoModule.java

GreenDaoModule.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Module
public class GreenDaoModule {

private final BaseApp application;

public GreenDaoModule(BaseApp application){
this.application = application;
}

@Provides
@Singleton
public BaseApp provideApplication(){
return application;
}


@Provides
@Singleton
SQLiteDatabase provideSQLiteDatabase(BaseApp baseApp){
DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(baseApp, "zlot-db", null);
SQLiteDatabase sqlDB = helper.getWritableDatabase();
return sqlDB;
}

@Provides
@Singleton
DaoMaster provideDaoMaster(SQLiteDatabase sqLiteDatabase){
DaoMaster daoMaster = new DaoMaster(sqLiteDatabase);
return daoMaster;
}
@Provides
@Singleton
DaoSession provideDaoSession(DaoMaster daoMaster){
DaoSession daoSession = daoMaster.newSession();
return daoSession;
}
}

在上述代码中,我们提供了BaseApp,SQLiteDatabase,DaoMaster,DaoSession的依赖,均是单例模式。
在目标类注解的时候,会在GreenDaoModule寻找依赖并提供实例。

创建AppComponent.java,提供注射器

我们新建一个interface,添加@Component注解,就提供了一个注射器,桥接@Inject跟@Module。

AppComponent.java

1
2
3
4
5
6
7
8
@Singleton
@Component(modules = {GreenDaoModule.class})
public interface AppComponent {
BaseApp getApplication();

BasePresenter inject(BasePresenter basePresenter);

}

其中BasePresenter是注射目标,也就是说想要在哪个类里面使用依赖注解,必须在component中注射。

比如我们想在BasePresenter这个类里,使用依赖注入DaoSession实例,必须在component里插入。

在Application中交给dagger2来完成初始化

Dagger会处理我们的注解,为components生成实现并重命名加上“Dagger”前缀。

MyApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 初始化依赖加载器
*/
private void initInjector(){
appComponent = DaggerAppComponent
.builder()
.apiModule(new ApiModule())
.greenDaoModule(new GreenDaoModule(this))
.build();
}

public AppComponent getAppComponent(){
return appComponent;
}

我们现在可以利用get方法获取创建的component,然后调用inject()方法将BasePresenter作为参数传进去,这样就完成了绑定BasePresenter依赖。

创建BasePresenter

BasePresenter的构造函数

1
2
3
public BasePresenter(BaseApp baseApp){
baseApp.getAppComponent().inject(this);
}

BasePresenter中使用依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BasePresenter {

@Inject
DaoSession daoSession;

/**
* 自定义http请求任务
*/
@Inject
public HttpTask httpTask;

public BasePresenter(BaseApp baseApp){
baseApp.getAppComponent().inject(this);
}

}

这样我们新建一个Presenter继承自BasePresenter,执行inject绑定,就直接可以使用Application提供的依赖来实例化对象了,而且这些实例都是singlton模式,生命周期等同于Application。
我们直接可以使用DaoSession来完成sql操作了。

继承Presenter

UserReguPresenter类继承自BasePresenter类,testLog()方法来测试数据库的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class UserReguPresenter  extends BasePresenter{
private UserReguActivity userReguActivity;


public UserReguPresenter(UserReguActivity userReguActivity) {
super((BaseApp) userReguActivity.getApplication());
this.userReguActivity = userReguActivity;
}


public void testLog(){
Logger.Debug("调用了presenter的测试");
//new一个实例
RegularHoldInfo regularHoldInfo = new RegularHoldInfo();
regularHoldInfo.setAmount(100.00);
regularHoldInfo.setCcsId(110);
//执行sql inset操作
daoSession.getRegularHoldInfoDao().insert(regularHoldInfo);
//执行sql query操作
Query query = daoSession.getRegularHoldInfoDao()
.queryBuilder()
.where(RegularHoldInfoDao.Properties.CcsId.eq("110"))
.build();
List<RegularHoldInfo> list = (List<RegularHoldInfo>)query.list();
if (list.size()>0){
Logger.Debug("查询结果:"+list.get(0).getAmount());
}

}

参考文献

泡在网上的日子 详解Dagger2

基于Retrofit2.0+RxJava+Dragger2实现不一样的Android网络构架搭建

Dagger2使用

android基础:多渠道打包和代码混淆

发表于 2016-06-15   |   分类于 android   |  

多渠道打包

AndroidManifest.xml文件解析

补充知识
AndroidManifest.xml 是每个android程序中必须的文件。它位于整个项目的根目录,描述了package中暴露的组件(activities, services, 等等),他们各自的实现类,各种能被处理的数据和启动位置。 除了能声明程序中的Activities, ContentProviders, Services, 和Intent Receivers,还能指定permissions和instrumentation(安全控制和测试)

1 <meta-adata>

语法

1
2
3
<meta-data android:name="string"
android:resource="resource specification"
android:value="string"/>

被包含于

<activity>
<activity-alias>
<service>
<receiver>
四个元素中。

说明
这个元素用name-value对的格式给其父组件提供任意可选的数据。
一个组件元素能够包含任意多个<meta-data>子元素,所有这些元素中定义的值会被收集到一个Bundle对象中,并且提供给组件的PackageItemInfo.metaData属性字段。
通常值是通过其value属性来指定的。但是,也可以使用resource属性来代替,把一个资源ID跟值进行关联。

例如,下面的代码就是把存储在@string/kangaroo资源中的值跟”zoo”名称进行关联:

1
<meta-data android:name="zoo" android:value="@string/kangaroo" />

另一个方面,使用resource属性会给zoo分配一个数字资源ID,而不是保存在资源中的值。例如:

1
<meta-data android:name="zoo" android:resource="@string/kangaroo" />

要避免使用多个独立的实体来提供相关的数据。相反如果有复杂的数据要跟组件关联,那么把数据作为资源来保存,并使用resource属性,把相关的资源ID通知给组件。

属性

  • android:name
    针对项目的一个唯一名称。使用Java样式的命名规则,可以确保名称的唯一性,例如:com.example.project.activity.fred。
  • android:resource
    这个属性定义了一个要引用的资源。资源的ID会跟这个项目进行关联。通过Bundle.getInt()方法能够从meta-data的Bundle对象中获取这个ID。

  • android:value
    这个属性会给这个项目分配一个值。下表列出了可能分配的数据的数据类型,以及获取这些数据的方法:

多渠道打包

以友盟统计为例,在AndroidManifest.xml里面会有

1
2
3
<meta-data
android:name="UMENG_CHANNEL"
android:value="Channel_ID" />

Channel_ID就是渠道标示,我们的目标就是在编译的时候这个值能够自动变化。

1 在AndroidManifest.xml里配置PlaceHolder

1
2
3
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />

${UMENG_CHANNEL_VALUE}这是个引用,具体值在gradle中配置

2 在build.gradle设置productFlavors

首先配置UMENG_CHANNEL_VALUE的默认值

1
2
3
4
5
defaultConfig {
···
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "channel_name"]
···
}

然后配置不同的渠道id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
android {  
...
productFlavors {
xiaomi {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
}
_360 {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "_360"]
}
baidu {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
}
wandoujia {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
}
}
...
}

更简洁的写法,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 友盟多渠道打包
productFlavors {
wandoujia {}
c360 {}
baidu {}
xiaomi {}
tencent {}
taobao {}
...
}

productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}

然后在签名配置完成后,就可以使用命令行打包了

gradle命令行打包

1 使用gradle命令配置签名

首先在app的build.gradle文件配置要签名的keystore文件

为了不暴露密码,不直接在signingConfigs中配置密码,需要在控制台输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}

android {
...

//执行lint检查,有任何的错误或者警告提示,都会终止构建,我们可以将其关掉。
lintOptions {
abortOnError false
}


signingConfigs {
debug {
// No debug config
}
//release版本
release{
storeFile file('../keystore文件名')
keyAlias 'asar'
storePassword System.console().readLine("\nKeystore password: ")
keyPassword System.console().readLine("\nKey password: ")
}
}


buildTypes {
debug {
// 显示Log
buildConfigField "boolean", "LOG_DEBUG", "true"

versionNameSuffix "-debug"
minifyEnabled false
zipAlignEnabled false
shrinkResources false
signingConfig signingConfigs.debug
}
release {
...
// 不显示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
//混淆
minifyEnabled true
//zipAlgn优化
zipAlignEnabled true
//移除无用的resource文件
shrinkResources true

//加载默认的混淆配置文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//签名
signingConfig signingConfigs.release
//配置输出的apk文件名
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
// 输出apk名称为zlot_v1.0_2015-01-15_wandoujia.apk
def fileName = "zlot_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
}



}
}

...

2 使用命令行打包

1 view->ToolWindows->Terminal打开命令行
2 在命令行输入gradlew -v ,如果第一次会执行下载gradle,记得翻墙,不然会慢到崩溃
3 执行gradlew clean,等待时间会比较长
4 执行gradle build,开始编译,成功后zlot\build\outputs\apk目录会有编译后的apk,
这时编译的apk包括debug版本,release版本,特别慢
补充

  • gradlew assembleRelease :只编译并打Release的包
  • gradlew assembleDebug :只编译并打包debug版本

除此之外 assemble 还能和 Product Flavor 结合创建新的任务,其实 assemble 是和 Build Variants 一起结合使用的,而Build Variants = Build Type + Product Flavor , 举个例子大家就明白了:

  • gradlew assembleWandoujiaRelease只打包wandoujia渠道的release包

3 比命令行更简单的打包方式

打开androidStudiode gradle面板,会发现多了很多任务,

然后直接双击任务生成apk

错误处理

当build faile时,找不到错误的原因,可以执行gradlew check进行检测,一般会给出错误原因,
比如

1
2
3
4
5
6
7
8
ndroid {
lintOptions {
abortOnError false
}
}
...

* Try:

意义是让我们关闭lint检查,因为开启的话,有任何的错误或者警告提示,都会终止构建

代码混淆

参考文献

Android 使用Android Studio + Gradle 或 命令行 进行apk签名打包

Android Studio系列教程六–Gradle多渠道打包

Android studio 使用心得(四)—android studio 多渠道打包(二)

[Android Studio] Android studio 多渠道打包(超简洁版)

android最佳实践9:greenDao在android studio中的使用

发表于 2016-06-13   |   分类于 android   |  

greenDao是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案,性能很不错。

greenDao的特点

  • 精简
  • 性能最大化
  • 不使用注解,使用code generate

android studio中的使用

1 android工程配置[greendao generator]模块

1 .src/main目录新建一个与java目录同层的java-gen目录,用于存放由greenDao生成的Bean,Dao,DaoMaster,DaoSession类

2 app的build.gradle添加sourceSets与dependencies

1
2
3
4
5
6
7
8
9
buildTypes{
...

}
sourceSets {
main{
java.srcDirs = ['src/main/java',src/main/java-gen]
}
}
1
2
3
4
5
6
dependencies {
...
compile 'org.greenrobot:greendao:2.2.1'
compile 'org.greenrobot:greendao-generator:2.2.0'
...
}

2 新建greenDao generator模块,纯java工程

1 file->new->new module->java Library->填写包名类名->finish

然后在新工程的build.gradle中添加

1
2
3
4
5
dependencies{
...
compile 'org.greenrobot:greendao-generator:2.2.0'
...
}

2 编写【ExampleDaoGenerator】类,注意: 我们的 Java 工程只有一个类,它的内容决定了「GreenDao Generator」的输出,你可以在这个类中通过对象、关系等创建数据库结构,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class ExampleDaoGenerator {
public static void main(String args[]){
// 正如你所见的,你创建了一个用于添加实体(Entity)的模式(Schema)对象。
// 两个参数分别代表:数据库版本号与自动生成代码的包路径。
Schema schema = new Schema(1, "com.hammer.example");
//当然,如果你愿意,你也可以分别指定生成的 Bean 与 DAO 类所在的目录,只要如下所示:
// 一旦你拥有了一个 Schema 对象后,你便可以使用它添加实体(Entities)了。
addNote(schema);
// 最后我们将使用 DAOGenerator 类的 generateAll() 方法自动生成代码,此处你需要根据自己的情况更改输出目录(既之前创建的 java-gen)。
// 其实,输出目录的路径可以在 build.gradle 中设置,有兴趣的朋友可以自行搜索,这里就不再详解。
try {
new DaoGenerator().generateAll(schema, "E:/androidSpace/android-lesson/BaseLesson/src/main/java-gen");
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* @param schema
*/
private static void addNote(Schema schema) {
// 一个实体(类)就关联到数据库中的一张表,此处表名为「Note」(既类名)
Entity note = schema.addEntity("Note");
// 你也可以重新给表命名
// note.setTableName("NODE");
// greenDAO 会自动根据实体类的属性值来创建表字段,并赋予默认值
// 接下来你便可以设置表中的字段:
note.addIdProperty();
note.addStringProperty("text").notNull();
// 与在 Java 中使用驼峰命名法不同,默认数据库中的命名是使用大写和下划线来分割单词的。
// For example, a property called “creationDate” will become a database column “CREATION_DATE”.
note.addStringProperty("comment");
note.addDateProperty("date");
}
}

3 执行run main函数,生成如图4个文件

在android工程进行数据库操作

1 考虑到application的生命周期,把DaoMaster跟DaoSession的实例放到application中,避免重复创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

public DaoMaster getDaoMaster(Context context){
if (daoMaster == null){
daoMaster = new DaoMaster(getSQLDB(context));
}
return daoMaster;
}

public SQLiteDatabase getSQLDB(Context context){
if (sqlDB == null){
DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(context, "zlot-db", null);
sqlDB = helper.getWritableDatabase();
}
return sqlDB;
}

public DaoSession getDaoSession(Context context){
if (daoSession == null)
{
if (daoMaster == null)
{
daoMaster = getDaoMaster(context);
}
daoSession = daoMaster.newSession();
}
return daoSession;
}

2 考虑到松耦合性,新增一个Dao的中间层,用来切换不同的Dao实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class NoteDaoMiddle {
private NoteDao noteDao;
private ILessonSixActvity lessonSixActvity;
public ArrayList<Note> notes = new ArrayList<>();

public NoteDaoMiddle(ILessonSixActvity lessonSixActvity, DaoSession daoSession){
this.lessonSixActvity = lessonSixActvity;
this.noteDao = daoSession.getNoteDao();
}


public void add(Note note){
Observable.create(subscriber->{
noteDao.insert(note);
notes.add(note);
subscriber.onCompleted();
}).subscribeOn(Schedulers.io()) //指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) //指定 Subscriber 的回调发生在主线程
.subscribe(getUpdateSubScriber());
}

public void search(String title){
Observable.create(subscriber->{
//为空
if (TextUtils.isEmpty(title)){
// Query 类代表了一个可以被重复执行的查询
Query<Note> query = noteDao.queryBuilder()
.orderAsc(NoteDao.Properties.Date)
.build();
// 查询结果以 List 返回
notes = (ArrayList<Note>) query.list();
}
else{
Query query = noteDao.queryBuilder()
.where(NoteDao.Properties.Text.eq(title))
.orderAsc(NoteDao.Properties.Date)
.build();
notes = (ArrayList<Note>) query.list();
}

// 在 QueryBuilder 类中内置两个 Flag 用于方便输出执行的 SQL 语句与传递参数的值
QueryBuilder.LOG_SQL = true;
QueryBuilder.LOG_VALUES = true;
subscriber.onCompleted();
})
.subscribeOn(Schedulers.io()) //指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) //指定 Subscriber 的回调发生在主线程
.subscribe(getUpdateSubScriber());
}


private Subscriber<Object> getUpdateSubScriber(){

return new Subscriber<Object>() {
@Override
public void onCompleted() {
lessonSixActvity.onUpdateList(notes);
LogUtil.Debug("onCompleted");
}
@Override
public void onError(Throwable e) {
LogUtil.Debug("onError:"+e.toString());
}

@Override
public void onNext(Object o) {
}
};
}
}

3 响应view层的增删查改操作

新建NoteDaoMiddle实例

1
2
3
4
5
6
7
8
9
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
daoSession = MyApplication.getIns().getDaoSession(this);
noteDaoMiddle = new NoteDaoMiddle(this,daoSession);

'''
}

响应add search按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void addNote(){
//如果新增为空,弹出
if (TextUtils.isEmpty(editText.getText())){
Toast.makeText(this,"add不能为空",Toast.LENGTH_SHORT);
return;
}

'''
// 插入操作,简单到只要你创建一个 Java 对象
Note note = new Note(null, noteText, comment, new Date());
noteDaoMiddle.add(note);

}

private void search(String title) {
noteDaoMiddle.search(title);
}

通过回调或者接口响应查询结果,并通知listview刷新视图

1
2
3
4
5
@Override
public void onUpdateList(ArrayList<Note> notes) {
sixAdapter.setData(notes);
sixAdapter.notifyDataSetChanged();
}

参考

Android ORM 框架 greenDAO 使用经验总结

android基础:工具类

发表于 2016-06-12   |   分类于 android   |  

本文主要总结下android常用的工具类

TextUtils类,处理字符串

android.text.TextUtils这个类有以下功能

  • public static boolean isEmpty(@Nullable CharSequence str)这个方法判断字符串非null且非空,在对字符串进行判断时可以不必if(str !=null && str.length() > 0)这样判断。
  • public static CharSequence concat(CharSequence... text) 字符串连接。有很多字符串,你还在用+拼接或自己new 一个StringBuffer\StringBuilder么?其实这个方法已经帮我们做好了,用StringBuilder实现,进行字符串拼接时可以考虑使用这个方法。而且支持SpannableString。
  • public static boolean isDigitsOnly(CharSequence str) 判断所给的字符串是否只有数字
  • public static boolean equals(CharSequence a, CharSequence b) 判断两个字符串对象的内容是否相等,内部进行了非null判断
  • public static String htmlEncode(String s)将html代码中的特殊字符进行转码处理

android.text.format.DateUtils 日期格式化工具

android.webkit.URLUtil URI判断工具类

参考

[Android快速开发系列 10个常用工具类](http://blog.csdn.net/lmj623565791/article/details/38965311)

直播的相关技术

发表于 2016-06-10   |   分类于 技术杂谈   |  

重要从四点来说直播的相关技术

  • 服务器,云存储
  • 手机端推流,主播
  • 手机端拉流,观众
  • 互动sdk,比如打赏,弹幕

服务器

流媒体服务器就是p2p服务器

一般自建流媒体服务器平比较复杂,维护也麻烦,都选择现成的云存储解决方案
技术关键字rtmp推流,hls拉流

上行,就是数据上传,一般有
RTMP,HLS推流

下行,播放,一般有
RTMP、HLS和HTTP(FLV)协议

综述

RTMP,RTSP,HLS都是流媒体协议,如果要开发一套准实时的手机音视频直播系统,需要支持iphone,android,windows phone等多款手机,最好使用hls协议,这个时候客户端播放的是切割的ts文件,它的延迟取决于切片的大小。

  • rtmp和hls并存:rtmp一般用于pc-flash播放直播,而hls用于移动端播放
  • RTMP本质上是流协议,实时性高,RTMP的实时性在3秒之内
  • hls是apple搞出来的协议,实时性在3s左右, Apple 为了提高流播效率开发的技术,特点是将流媒体切分为若干 TS 片段(比如每10秒一段),然后通过一个扩展的 m3u 列表文件将这些 TS 片段集中起来供客户端播放器接收。比mp4适合做直播技术。

参考文章

腾讯云视频解决方案,全方向

最全的解决方案,包括服务器,客户端的sdk

代表商

  • 龙珠直播

提供游戏直播,美女主播解决方案,比如:

  • 直播提供HLS拉流/RTMP推流接入能力,快速接入游戏主播摄像头、游戏桌面、专业视频源等,并行进快速下发;通过500+CDN节点快速触及玩家;低至2秒延迟;
  • 互动直播通过双向SDK,提供400ms超低延迟的实时互动能力,提供游戏直播场景中的互动能力;下行也可通过标准HLS下发进行单向直播,无需安装SDK;
  • 提供推流端口,播放端的sdk
    架构如图

网易视频云,全方向

解决方案也很全,包括服务器,客户端的sdk,贵

代表商
BoBo美女主播

  • 提供推流Android SDK, iOS SDK,Windows SDK
  • 提供播放器Android SDK, iOS SDK

乐视云方案,手机方向

产品架构图

阿里云直播,pc方向

产品架构图

其他

手机端推流,主要就是链接服务器,上传ts文件碎片

手机端拉流,主要就是下载ts文件碎片,播放的问题

互动sdk,参考聊天,弹幕类,主要是udp技术。

付费扣费sdk,主要是http技术

android基础:Activity Window View ViewGroup

发表于 2016-05-30   |   分类于 android   |  

View与ViewGroup的关系

Android系统中的所有UI类都是建立在View和ViewGroup这两个类的基础上的。

  • 所有View的子类成为”Widget”
  • 所有ViewGroup的子类成为”Layout”
  • ViewGroup是一种特殊的View

View和ViewGroup之间采用了组合设计模式,可以使得“部分-整体”同等对待。ViewGroup作为布局容器类的最上层,布局容器里面又可以有View和ViewGroup

如下图

区别

  • 如果viewgroup有一个子view是invisible状态,viewgroup就是invisible状态


Activity与window

Activity

在Activity中使用LayoutInflater。
LayoutInflater是一个用来实例化XML布局文件为View对象的类
LayoutInflater.infalte(R.layout.test,null)用来从指定的XML资源中填充一个新的View

  • Activity的作用相当于人机交互界面,负责管理跟维护View与手机的操作。比如提供用户处理事件的API,如onKeyEvent, onTouchEvent等。

Window

  • window是一个抽象类
  • 当我们调用Acitivity的 setContentView方法的时候实际上是调用的Window对象的setContentView方法,Activity中关于界 面的绘制实际上全是交给Window对象来做的

Activity与Window关系

一个Activity包含了一个Window,Window才是真正代表一个窗口,也就是说Activity可以没有Window,那就相当于是Service了。在ActivityThread中也有控制Service的相关函数或许正好印证了这一点。
 Activity和Window的第一次邂逅是在ActivityThread调用Activity的attach()函数时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

//[window]:通过PolicyManager创建window,实现callback函数,所以,当window接收到
//外界状态改变时,会调用activity的方法,

final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
....
mWindow = PolicyManager.makeNewWindow(this);
//当window接收系统发送给它的IO输入事件时,例如键盘和触摸屏事件,就可以转发给相应的Activity
mWindow.setCallback(this);
.....
//设置本地窗口管理器
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
.....
}

 在attach()中,新建一个Window实例作为自己的成员变量,它的类型为PhoneWindow,这是抽象类Window的一个子类。然后设置mWindow的WindowManager。

Window,Activity和DecorView

DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。DecorView作为顶级View,一般情况下它内部包含一个竖直方向的LinearLayout,在这个LinearLayout里面有上下两个部分(具体情况和Android版本及主体有关),上面的是标题栏,下面的是内容栏。在Activity中通过setContentView所设置的布局文件其实就是被加到内容栏之中的,而内容栏的id是content,在代码中可以通过ViewGroup content = (ViewGroup)findViewById(R.android.id.content)来得到content对应的layout。
 Window中有几个视图相关的比较重要的成员变量如下所示:

  • mDecor:DecorView的实例,标示Window内部的顶级视图
  • mContentParent:setContetView所设置的布局文件就加到这个视图中
  • mContentRoot:是DecorView的唯一子视图,内部包含mContentParent,标题栏和状态栏。
     
    Activity中不仅持有一个Window实例,还有一个类型为View的mDecor实例。这个实例和Window中的mDecor实例有什么关系呢?它又是什么时候被创建的呢?
     二者其实指向同一个对象,这个对象是在Activity调用setContentView时创建的。我们都知道Activity的setContentView实际上是调用了Window的setContentView方法。
1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) { //[window]如何没有DecorView,那么就新建一个
installDecor(); //[window]
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
....
//[window]第二步,将layout添加到mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
.....
}

参考文献

Android视图架构详解

Android应用程序窗口(Activity)的视图对象(View)的创建过程分析

android基础:adapter详解

发表于 2016-05-23   |   分类于 android   |  

什么是adapter

adapter就是适配器,适配器模式把一个类的接口转换成客户端所期待的另一种接口,从而使原本两个接口不匹配而无法在一起工作的类,能够在一起工作

使用场景

举个栗子
用电源接口做例子,笔记本电脑的电源一般都是接受5V的电压,但是我们生活中的电线电压一般都是220V的输出。这个时候就出现了不匹配的状况,在软件开发中我们称之为接口不兼容,此时就需要适配器来进行一个接口转换。在软件开发中有一句话正好体现了这点:任何问题都可以加一个中间层来解决。这个层我们可以理解为这里的Adapter层,通过这层来进行一个接口转换就达到了兼容的目的。

ListView中的Adapter模式

为什么listview要用adapter呢?我们知道,作为重要的视图元素,listview展示的view千变万化,ListView需要能够显示各式各样的视图,每个人需要的显示效果各不相同,显示的数据类型,数量也不尽相同,那么如何隔离这种变化尤为重要。

Android的做法是增加一个Adapter层来应对变化,将ListView需要的接口抽象到Adapter对象中,这样只要用户实现了Adapter的接口,ListView就可以按照用户设定的显示效果、数量、数据来显示特定的Item View。

通过代理数据集来告知ListView数据的个数( getCount()函数 )以及每个数据的类型( getItem()函数 ),最重要的是要解决Item View的输出。Item View千变万化,但终究它都是View类型,Adapter统一将Item View输出为View ( getView()函数 ),这样就很好的应对了Item View的可变性。

参考文献

Android源码之ListView的适配器模式

android基础:浅析mvc与mvp

发表于 2016-05-20   |   分类于 android   |  

为什么要使用框架

设计框架的目的:

  • 提高开发效率,尤其是协同工作
  • 业务逻辑的切分,渐进式开发,提高业务的重用性,灵活性
  • 测试以及问题的定位
  • 日常的更新与维护

MVC

经典的框架,view层,model层,Controller层.
用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

在android中的实践呢?

  • M层:适合的业务逻辑。数据库存取,网络操作,复杂算法,耗时操作
  • V层:显示数据
  • Contro层:在很多情况下activity会掺和很多Controller的事情,activity负责人机交互界面。

举例:
Activity读取V视图层的数据(eg.读取当前EditText控件的数据),控制用户输入(eg.EditText控件数据的输入),并向Model发送数据请求(eg.发起网络请求等)。

优点:

(1)耦合性低。所谓耦合性就是模块代码之间的关联程度。利用MVC框架使得View(视图)层和Model(模型)层可以很好的分离,这样就达到了解耦的目的,所以耦合性低,减少模块代码之间的相互影响。便于进行单元开发,单元测试
(2)可扩展性好。由于耦合性低,添加需求,扩展代码就可以减少修改之前的代码,降低bug的出现率。
(3)模块职责划分明确。主要划分层M,V,C三个模块,利于代码的维护。

缺点:

Activity中有很多关于视图UI的显示代码,因此View视图和Activity控制器并不是完全分离的,当activity类业务过多的时候,会变得难以管理和维护。
尤其是当ui的状态数据,跟持久化的数据混杂在一起,变得极为混乱。

MVP

为了解决view层代码过多,引入了这套设计框架。
MVP从更早的MVC框架演变过来,与MVC有一定的相似性:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。
如下图

MVP框架由3部分组成:View负责显示,Presenter负责逻辑处理,Model提供数据。在MVP模式里通常包含3个要素(加上View interface是4个):

  • View:负责呈现ui,与用户进行交互,如activity fragment
  • Model: 负责存储,检索,操作数据
  • Presenter:作为view跟model的桥梁,处理交互与数据逻辑。
  • View Interface:Presenter中持有的view层接口,负责给view返回消息。降低耦合,方便单元测试

优点

1 模型与视图完全分离,我们可以修改视图而不影响模型;
2 可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部;
3 我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁;
4 如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)。

两者的区别

(最主要区别)View与Model并不直接交互,而是通过与Presenter交互来与Model间接交互。而在MVC中View可以与Model直接交互
通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑。而Controller是基于行为的,并且可以被多个View共享,Controller可以负责决定显示哪个View
Presenter与View的交互是通过接口来进行的,更有利于添加单元测试。

附赠MVVM知识

MVVM可以算是MVP的升级版,其中的VM是ViewModel的缩写,ViewModel可以理解成是View的数据模型和Presenter的合体,ViewModel和View之间的交互通过Data Binding完成,而Data Binding可以实现双向的交互,这就使得视图和控制层之间的耦合程度进一步降低,关注点分离更为彻底,同时减轻了Activity的压力。

MVC -> MVP -> MVVM 这几个软件设计模式是一步步演化发展的,MVVM 是从 MVP 的进一步发展与规范,MVP 隔离了MVC中的 M 与 V 的直接联系后,靠 Presenter 来中转,所以使用 MVP 时 P 是直接调用 View 的接口来实现对视图的操作的,这个 View 接口的东西一般来说是 showData、showLoading等等。M 与 V已经隔离了,方便测试了,但代码还不够优雅简洁,所以 MVVM 就弥补了这些缺陷。在 MVVM 中就出现的 Data Binding 这个概念,意思就是 View 接口的 showData 这些实现方法可以不写了,通过 Binding 来实现。

google官方已经推出了Data Binding库来写声明的layouts文件,并且用最少的代码来绑定你的app逻辑和layouts文件
Data Binding库不仅灵活而且广泛兼容- 它是一个support库,因此你可以在所有的Android平台最低能到Android 2.1(API等级7+)上使用它。

三者的异同

异
三者的差异在于如何粘合View和Model,实现用户的交互操作以及变更通知

参考文献

Android App的设计架构:MVC,MVP,MVVM与架构经验谈

Data Binding 用户指南(Android)

Android DataBinding:再见Presenter,你好ViewModel!

android最佳实践10:开源库汇总

发表于 2016-05-16   |   分类于 android   |  

第一部分:工具库

依赖注入库

dagger:Square公司的明星项目,已被google官方采用

  • 依赖注入,适用于 Android 和 Java
  • 在android平台,特别适合mvp架构
  • 官网
  • Dagger 源码解析
  • Dagger2使用
  • MVP+Dagger2+Retrofit实现更清晰的架构

AndroidAnnotations(Code Diet)

  • 目前我正在使用的项目,不是很方便,每次要编译时生成Activity的子类_Activity
  • 特点:(1) 依赖注入:包括 view,extras,系统服务,资源等等
    (2) 简单的线程模型,通过 annotation 表示方法运行在 ui 线程还是后台线程
    (3) 事件绑定:通过 annotation 表示 view 的响应事件,不用在写内部类
    (4) REST 客户端:定义客户端接口,自动生成 REST 请求的实现
    (5) 没有你想象的复杂:AndroidAnnotations 只是在在编译时生成相应子类
    (6) 不影响应用性能:仅 50kb,在编译时完成,不会对运行时有性能影响。
    其他:与 roboguice 的比较:roboguice 通过运行时读取 annotations 进行反射,所以可能影响应用性能,而 AndroidAnnotations 在编译时生成子类,所以对性能没有影响

图片缓存库

  • picasso
  • fresco
  • Glide

对比

  • fresco facebook的明星项目,5.0以下特别流畅,但是包大。亮点是渐进式显示图片
  • picasso Square的明星项目,跟okhttp结合最好用,包最小
  • Glide是google出品的图片缓存框架

总结,我选择使用Picasso,因为简单易上手。

gradle-retrolambda:java7兼容lambda库

使用教程

rx-android:感受响应式编程魅力

使用教程

Retrofit2.0:最热门的restfull规范网络请求框架

使用教程

EventBus:发布 / 订阅的事件总线

使用教程

高版本向低版本兼容库

nineoldandroids:JakeWharton大神的项目

  • NineOldAnimations 源码解析
  • 文档介绍

第二部分 ui库

刷新ui

BGARefreshLayout-Android:多种下拉刷新效果、上拉加载更多、可配置自定义头部广告位

  • 目前我正在使用的下拉刷新,上拉加载

Android-PullToRefresh

  • 目前使用最广泛的强大的拉动刷新开源项目,强烈推荐使用
  • 支持各种控件下拉刷新,ListView、ViewPager、WebView、ExpandableListView、GridView、ScrollView、Horizontal ScrollView、Fragment 上下左右拉动刷新
  • APP 示例:新浪微博各个页面

android-Ultra-Pull-To-Refresh

  • star数目4991的项目,跟上一个刷新控件媲美
  • 这个继承于 ViewGroup 可以包含任何 View。功能甚至比 SwipeRefreshLayout 强大。使用起来非常简单。良好的设计,如果你想定制自己的 UI 样式,非常简单,就像给 ListView 加一个 Header View 那么简单。支持 API LEVEL >= 8
  • android-Ultra-Pull-To-Refresh 源码解析

Material Design UI

material-dialogs

  • 各种material风格的dialog。 漂亮的的、易于使用的和可定制的Dialog API,你能够使用Material designed风格的Dialog到API 8
  • 目前正在使用,文档很全面。

仿ios的ui

Android-PickerView:仿ios的pickview库

仿iOS的PickerView控件,有时间选择和选项选择并支持一二三级联动效果

ViewPager 、Gallery轮播图 旋转木马效果

Android-ConvenientBanner:banner轮播图

  • 目前我正在使用的控件,没有解决每次imageview重新加载的问题,导致大的图片会先出现空白。

LoopRotarySwitch:旋转木马

  • 目前正在使用,适用于3-4个图片,支持无线滚动。
    优点
  • 适用于Activity和Fragment
  • 可定制大小,可监听事件,可调整旋转滚动半径

CarouselViewProject:画廊效果

  • 不是很灵活,不支持无限滚动

3d旋转木马效果

以下三个3d旋转木马效果,都来自于这篇文章Android 3D Carousel,分别是不同的实现

carousel-android:star31

缺点

  • 在Fragment中使用会出现问题。

Android-3D-Carousel:star4


优点

  • 增加了垂直滚动模式,尚未测试

CarrouselView:star22 中文版

优点

  • 中文版,效果最佳
  • 布局可以随意自定义,不受只是图片的限制。
  • 2、增加点击切换到中间位置。
  • 3、手势滑动切换。
  • 4、切换中间回调。
  • 5、自定义适配器更加方便用户设置自己需要的布局。

GlFancyCoverFlow:电影海报轮播图

  • 借鉴自FancyCoverFlow
  • 增加了无限滚动,更适合海报等效果
  • 但是继承自GalleryView,这个类已经被google放弃了,有内存溢出的风险

特殊ui

ExpandableLayout:控件收缩

  • 目前正在使用,可以自己修改源码。

android基础:构建神器gradle

发表于 2016-05-03   |   分类于 android   |  

什么是gradle

项目自动化构建工具,用以取代xml的繁琐配置,目前大量用于android开发。
作用提现在以下两点

  • 首先Gradle需要找到你工程需要的东西,这些东西就是所谓的“依赖”。
  • 另外Gradle需要构建和上传你工程的产出,这就是所谓的发行。

如何学习

  • 了解Groovy基本语法

Gradle基于Groovy语言,Groovy语法的学习,推荐官方文章Differences with Java和IBM developerWorks的精通Groovy

  • 粗读Gradle User Guide和Gradle Plugin User Guide

gradle概念

gradle构建android工程

gradle实用技能

1.解决重复依赖

如何发现

  • 方法1

windows环境下

1
2
命令行执行
gradlew -q dependencies module名称:dependencies --configuration compile

mac os环境下

1
2
命令行执行
gradle -q dependencies yourProject:dependencies --configuration compile

  • 方法2

下载gradle view插件

如何解决

在重复引用的依赖里,去除对引入的其他依赖

eg:

1
2
3
4
5
6
7
8
dependencies {
compile('org.hibernate:hibernate:3.1') {
//excluding a particular transitive dependency:
exclude module: 'cglib' //by artifact name
exclude group: 'org.jmock' //by group
exclude group: 'org.unwanted', module: 'iAmBuggy' //by both name and group
}
}

需要注意的是,当我们依赖本地module时,
eg:

1
2
3
4
5
6
7
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile 'com.android.support:support-v4:23.3.0'
···

···
}

2 使用gradle统一配置sdk和dependencies版本

首先在工程目录下新建config.gradle文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
ext {
android = [compileSdkVersion:23,
buildToolsVersion:"23.0.1",
minSdkVersion:15,
targetSdkVersion:23]
dependencies = [
appcompatv7:'com.android.support:appcompat-v7:23.3.0',
junit:'junit:junit:4.12',
xlog:'com.elvishew:xlog:1.3.0',
flexbox:'com.google.android:flexbox:0.2.5'
]
}

在工程的build.gradle里引入

1
apply from:'config.gradle'

然后在app或者module的build.gradle使用这些配置信息,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion

defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode 1
versionName "1.0"
}
}

dependencies {
...
//
compile rootProject.ext.dependencies.appcompatv7
...
}

参考文献

Android项目中如何用好构建神器Gradle?

Gradle之依赖管理

1234…7
hammercui

hammercui

69 日志
13 分类
19 标签
RSS
github
Links
  • logicool的技术专栏
  • xiekw2010的专栏
  • 江清清的技术专栏
© 2017 hammercui
由 Hexo 强力驱动
主题 - NexT.Pisces