Git使用流程

Git使用流程

Gitlab注册

  1. 申请GitLab账号。请确保邮箱能够收到邮件
  2. 生成并提交你的[SSH Key]公钥(http://192.168.90.252/profile/keys)
  3. QQ群内发送你的用户名,并@相关人员分配权限
  4. checkout相关项目

Git使用流程

迁出项目

1
2
3
mkdir workspace  
cd workspace
git clone git@192.168.90.252:Android/sample.git

切换到dev分支

1
2
cd sample
git checkout dev

更新dev分支

1
git pull

从dev分支检出自己的项目分支

1
git checkout -b $your_short_name

提交自己的分支到远端

1
git push --set-upstream origin $your_short_name

日常工作中,提交代码的流程

每天工作开始时,获取dev分支上的最新代码到自己分支。

1
2
git fetch
git merge dev

需要确保当前分支是你自己的分支,查看当前分支命令

1
git branch

分支内容有更改或者新增时

1
2
git commit -a -m "sth modify"
git push

合并代码到dev

1
2
git checkout dev
git merge $your_name

###Code Review和代码审核
需要作代码review的同学,将分支切换到dev

1
git diff $review_name

review_name为Review对象的分支名称

Trident Framework的使用以及遇到的问题

Trident Framework的使用以及遇到的问题

先上一张整体的类图


Base**这一类的Fragment主要是针对当前项目的一些配置,比如全局的标题设置,数据内容为空的一些判断。Framework**这一类的Framework主要是出于跨项目的重用进行构建的,主要做一些下拉刷新,下拉加载更多,提供一些非固定实现的接口,都是一些便于新项目的扩展的一些实现。

给Activity添加titleBar

通过重写BaseActivity中的ITitleDataBinder接口,来实现标题栏的各个部分

添加标题

1
2
3
4
5
/**
* 更新中间标题
* @param view 标题控件
*/

void updateMiddleView(TextView view);

给TextView添加图标和标题

1
2
3
4
5
Drawable left = mAct.getResources().getDrawable(R.drawable.title_ico);
left.setBounds(0, 0, left.getMinimumWidth(),
left.getMinimumHeight());
mLeftTxtView.setText("Miliyo");
mLeftTxtView.setCompoundDrawables(left, null, null, null);

获取动态数据,更新标题

1
2
3
4
/**
* 更新标题数据
*/

void notifyTitleData();

动态数据是指服务端返回的数据,或者Activity
onCreate调用之前没有办法确定的数据,通过notifyTitleData来更新数据,可以避免回调时,标题栏已经被销毁的情况。切忌不要在Fragment和Activity对象中,持有titleBar控件的引用

####标题栏右侧添加小红点或数字角标

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void updateRightViewTip(TextView view) {
int num = util.getCommentNew();
if(num >0){
view.setVisibility(View.VISIBLE);
view.setBackgroundResource(R.drawable.shape_msg_num);
view.setCompoundDrawables(null,null,null,null);
view.setText(String.valueOf(num));
}else{
view.setVisibility(View.GONE);
}
}

多Action的标题栏

1
2
3
4
5
6
7
8
9
10
11
private int[] mActionIds = new int[]{R.id.title_action1, R.id.title_action2, R.id.title_action3, R.id.title_action4};
private int[] mActionStrings = new int[]{R.string.online, R.string.dating, R.string.trip, R.string.like};
@Override
public void update4ActionTitle(ViewGroup viewParent) {
viewParent.setVisibility(View.VISIBLE);
for (int i = 0; i < mActionIds.length; i++) {
mActionTextViews[i] = (TextView) viewParent.findViewById(mActionIds[i]);
mActionTextViews[i].setText(mActionStrings[i]);
mActionTextViews[i].setOnClickListener(mActionClickListener);
}
}

需要注意的是,leftView默认设置的是返回图标,middleView默认设置是可见的,其它各个部件都是设置为不可见,需要使用时,请先设置setVisibility(View.VISIBLE);

给Fragment设置标题

原则上建议,针对Activity来进行标题栏设置,但有时候需求中,我们也会遇到多个Fragment共有一个Activity的情况,例如主页这类的需求,我们需要在对Fragment进行切换操作的时候,更新Activity标题栏的数据

1
2
3
4
5
6
7
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (object instanceof ITitleDataBinder) {
mTitleTool.binding((ITitleDataBinder) object);
}
}

setPrimaryItem是位于SimpleViewPagerStateAdapter中的方法,当Viewpager中的current Fragment发生变化时会主动调用

不使用标题栏

1
2
3
4
@Override
public boolean hasTitleBar() {
return false;
}

标题栏默认的更新时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void setContentView(int layoutResID) {
mTitleTool = DefaultTitleTool.createInstance();
if (hasTitleBar()) {
View rootView = mTitleTool.addTitleView(
getApplicationContext(),
getLayoutInflater().inflate(layoutResID,
getDecotatorView(), false), getDecotatorView());
mTitleTool.binding(this);
super.setContentView(rootView);
} else {
super.setContentView(layoutResID);
}
}

标题栏的更新实现是在Activity的setContentView方法中,如果标题栏使用有遇到和预期不符的情况,请检查setContentView的参数,int类型的参数调用和View类型的参数调用产生的结果不一样

1
2
@Override
public void setContentView(int layoutResID);

一个简单的列表需求

首先我们需要继承BaseRecycleFragment,该类暂时只提供了LinearLayoutManager的支持,后期可能会对九宫格和瀑布流进行支持。需要做一个简单列表,首先我们需要实现网络请求接口sendRequest(),该方法是在onActivityCreate()中进行调用的,所以重写onActivityCreate()方法时请记得加上super.onActivityCreate()。

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
@Override
public void sendRequest() {
GsonRequest<Person> request = new GsonRequest<>(SIMPLE_URL);
// 设置自定义的gson解析工具,项目中需要对GsonString,
// EmojiString这种特定类型的数据做处理的时候,需要设置
// 项目中默认使用具有GsonString类型解析的构建器
// 如果需要解析带emoji表情的数据时,需要使用到miliyo中的具体构建器
request.setGson(GsonUtil.getGsonSringBuilderGson());
request.setClazz(Person.class);//设置返回值类型,默认类型为JSONObject
request.addPostParam("uid", 18);//添加post参数
request.addUrlParam("token", "fjdlsjhdf8392");//添加Url参数
request.setAutoHanderException(true);//设置是否需要全局进行异常处理
request.setLogAble(true);//是否需要打印请求地址,参数和请求返回结果

request.setCallback(new ICallback<Person>() {
@Override
public void callback(Person entity) {
CLog.d(TAG, String.format("callback,name is %s,age is %s", entity.name, entity.age));
}

@Override
public void onHasAnyException(VolleyError e) {
CLog.d(TAG, "onHasAnyException", e);
}
});

//execute可接受Fragment,Context两种参数,区别在于,
// 传入参数的生命周期影响callback的调用,如果传入的是application的context
// 则不论activity或者fragment是否销毁,都会回调callback
request.execute(fragment);
}

我们假设服务端返回的实体对象Person中有一个List的数据对象,用来返回这个人的朋友列表,我们可以在callback方法中实现数据绑定到UI界面

1
2
3
4
5
6
if(entity.friends!=null){
for(Friend friend:entity.friends){
getContentData().add(new FriendItem(friend));
}
}
getAdapter().notifyDataSetChanged()

上面方法实现了把数据添加到Content列表中,并更新显示。RecycleFragment提供getHeaderData,getFooterData,getContentData
三个数据集合对象,他们的区别在于,数据显示在头部还是尾部,类似于ListView的HeaderView,FooterView。
在一些具体实现中,我们会有刷新时清空数据,数据为空时显示EmptyView,遇到网络异常显示ErrorView这类需求,这时候为了处理这种重复需求,我们可以实现RecycleCallback,这是一个ICallback的代理实现,它提供了我们上面所说的这些需求的基本实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new RecycleCallback(
new ICallback<Person>() {

@Override
public void callback(Person entity) {
if(entity.friends!=null){
for(Friend friend:entity.friends){
getContentData().add(new FriendItem(friend));
}
}
}

@Override
public void onHasAnyException(VolleyError e) {
e.printStackTrace();
}
});

可以发现,实际callback中的代码比之前的代码还少了notifyDatasetChanged过程,这是因为在代理内中,上层代码已经代为调用了,可以看到,我们实现了更多的额外功能,但是代码反而相对精简了。

下面我们看一下FriendItem的实现

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
public class FriendItem extends MultiBaseItem implements OnClickListener{

private String mInfo;

public FriendItem(Friend info) {
mInfo=info;
}

private static final ItemCreator ITEM_CREATOR = new ItemCreator() {

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item_friend, parent, false);
return new FriendHolder(view);
}
};

@Override
public ItemCreator getCreator() {
return ITEM_CREATOR;
}

static class FriendHoldler extends RecyclerView.ViewHolder {
TextView convertView;

public FriendHoldler(View view) {
super(view);
this.convertView=(TextView)view;
}

}

@Override
public void onBindViewHolder(ViewHolder holder, FrameworkRecycleFragment fragment, int position) {
super.onBindViewHolder(holder, fragment, position);
FriendHoldler vh = (FriendHolder)holder;
vh.convertView.setText(mInfo.name);
}

@Override
public void onClick(View v) {

}

}

MultiBaseItem是ItemCreatorable的抽象实现,我们需要去重写实现

1
public void onBindViewHolder(ViewHolder vh, FrameworkRecycleFragment fragment, int position);

需要调用Adapter等操作,可以通过参数fragment.getAdapter()来获取,上述方法主要用来处理数据和ViewHolder绑定的逻辑

1
2
@Override
public ItemCreator getCreator();

通过getCreator返回一个ItemCreator,需要注意的是,这个ItemCreator实例是用来区分Item的类型的,所以一定要确保是单例的对象,建议写法是,返回一个static的对象,如下所示代码

1
2
3
4
5
6
7
8
9
private static final ItemCreator ITEM_CREATOR = new ItemCreator() {

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item_friend, parent, false);
return new FriendHolder(view);
}
};

现在我们还需要实现的就只剩下item_friend的layout布局。

如果我们需要实现一个多类型的列表,我们只需要向Adapter的ContentData,HeaderData,FooterData中add不同的ItemCreatorable。

序列化列表

下面的代码是ParcelRecycleFragment中的默认实现,如果有特殊需求,可以按照自己的需要重写该方法

使用Bundle中的序列化数据恢复界面,返回true则不调用onCreateInit恢复数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public boolean onRestoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState != null) {
ArrayList<T> contentData = savedInstanceState.getParcelableArrayList(KEY_CONTENT_DATA);
isLoadingMore = false;
if(contentData != null && contentData.size() > 0){
getContentData().clear();
for (T info:contentData){
if(info==null){
return super.onRestoreInstanceState(savedInstanceState);
}
getContentData().add(info);
}
getAdapter().notifyDataSetChanged();
return true;
}
}
return super.onRestoreInstanceState(savedInstanceState);
}

保存数据到Bundle

1
2
3
4
5
6
7
8
9
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(KEY_CONTENT_DATA, (ArrayList<? extends Parcelable>) getContentData());
if(mEmptyItem!=null){
outState.putParcelable(KEY_EMPTY_ITEM, mEmptyItem);
outState.putInt(KEY_EMPTY_SHOW,isShowEmpty);
}
}

使用Databinding简化Item

自定义layout布局

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
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

<data>
<variable
name="friend"
type="com.trident.guide.framework.entity.Friend" />
<variable
name="handler"
type="com.trident.guide.framework.SampleRecycleFragment"/>
</data>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{handler.onClickNameChange}"
android:textColor="@android:color/white"
android:text="@{friend.name}" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:text="@{String.valueOf(friend.age)}" />
</LinearLayout>
</layout>

自定义一个类型构建器BindingItemCreator

1
2
3
4
5
6
7
8
9
10
11
12
public class FriendItemCreator extends BindingItemCreator<Friend>{
public FriendItemCreator(){
super(R.layout.item_friend, Friend.class);
}

@Override
public void onBinding(ViewDataBinding binding, Friend bindingData, int position) {
binding.setVariable(BR.friend, bindingData);
binding.setVariable(BR.handler,getFragment());
}

}

注册Item类型构建器

1
2
3
4
5
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getAdapterData().registerItemType(new FriendItemCreator());
}

向getContentData()中添加可解析的实体对象

1
2
3
if (person.friends != null) {
getContentData().addAll(person.friends);
}

FAQ

关于自动刷新和手动刷新

**DataFragment中的autoRefresh方法,sendRequest的区别在于,是否需要在刷新的时候,调用控件的下拉刷新动画。autoRefresh方法会触发下拉的动画同时,调用sendRequest(),autoRefresh()方法默认是在onCreateInit()调用的,onCreateInit()的调用是在onActivityCreate()当中。不建议重写refreshUI方法,如果有需要重写,可以组内做适当讨论。

列表对应的fragment基类具体是哪一个,非列表一般继承哪个fragment.

列表类型的Fragment,如果不需要进行序列化保存操作的话,继承BaseRecycleFragment,如果需要进行序列化操作的话,继承ParcelRecycleFragment。非列表需要有下拉刷新操作或者网络请求操作的类继承自BaseDataFragment,只是纯界面显示,不需要根据网络数据初始化界面的,使用BaseFragment。

fragment中主要方法的调用顺序,我们做初始化操作和注册广播最好在哪个方法里面调用比较好。

注册广播等初始化等需要Activity
context环境的操作建议在onActivityCreate中实现,onCreateInit可以确保在Activity初始化和序列化失败的时候调用。项目中建议使用initView来替代onCreateView方法。

Glide使用及需要注意的事项

Glide使用及需要注意的事项

Glide的更多调用,可以参考Glide的官方文档译文,Glide Wiki

###使用Glide加载图片

1
2
3
4
5
6
7
ImageView targetImageView = (ImageView) findViewById(R.id.imageView);
String internetUrl = "http://i.imgur.com/DvpvklR.png";

Glide
.with(context)
.load(internetUrl)
.into(targetImageView);

在这里需要注意的是,targetImageView如果大小是动态变化的,比如在项目需求中,我们需要GridView中的Item图片宽度等于高度,在listview或者gridView中频繁加载调用的话,有可能导致图片出现错乱或者加载不成功,解决方法是,使用override()方法固定图片源的宽高,避免短时间内多次重复加载。

这里的into方法还有很多的重载,具体的使用可以参考Glide的官方译文。

###使用Glide的扩展库,对图片进行处理
glide-transformations是一个基于Glide的transformation库,拥有圆角,裁剪,着色,模糊,滤镜等多种转换效果,以下主要介绍项目中常用的几种处理,需要有其它特效的可以自己去看github的使用说明。

####裁剪一个圆形图片

1
2
3
Glide.with(this).load(R.drawable.demo)
.bitmapTransform(new CropCircleTransformation(context))
.into((ImageView) findViewById(R.id.image));

如果需要裁剪一个圆角图片,可以使用RoundedCornersTransformation()

####高斯模糊

1
2
3
Glide.with(this).load(R.drawable.demo)
.bitmapTransform(new BlurTransformation(context, 25))
.into((ImageView) findViewById(R.id.image));

如果同时需要对一个图片进行圆角转换和高斯模糊的话,bitmapTransform的参数,是一个可变数组,我们的代码可以这样写

1
bitmapTransform(new BlurTransformation(context, 25), new CropCircleTransformation(context))

###后记
glide-transformations只是一个工具库,里面的工具类都是基于Glide的Transform接口,在Transform的基础上,我们也可以有一些自己的工具类,来实现一些特定的需求,Glide这个库,我并没有去深入的理解它的机制和流程(暂时没那么多时间去跟源码),有时间的小伙伴可以花一些时间,去把源码里,比较好的设计以及它的瓶颈在哪归纳整理出来,也是对自己的一种提高。

android push调研

项目现状:

当前推送退到后台的,很多机型存在不稳定的问题,android6.0以后,推出了Doze模式,后台推送的权限进一步收紧。国内存在众多的自定义rom,会对后台的推送进程进行强杀。(MIUI,魅族等友商)

在Doze Mode下,网络访问被屏蔽,维持唤醒(Wake Lock)被忽略,定时任务(Alarm)被推迟(以指数递增的后延),但不会杀进程。也就是说后台进程就啥都别想干了,乖乖躺着吧,push通道不可避免的会断掉。这次Google动真格的一点是,对所有应用,无视『目标版本(Target SDK Version)』全部生效,除了用户在设置中主动赦免的app以及Google自家的Play services和Play Store。(Google很贱的不让你剥夺这俩的赦免)

优化方向

暂时没有发现可靠的第三方库能对android的后台push有很好的保护,根据官方文档,有以下几种方案可以在一定程度上对push的送达率有优化作用

  • android5.0以后,建议使用JobSchedlue进行后台任务的优化,通过更好的任务触发机制,降低应用耗电,降低被回收的概率
  • 针对android6.0的Doze模式,应用可以声明 REQUEST_IGNORE_BATTERY_OPTIMIZATIONS 权限,并主动要求用户将其加入至白名单中,从而不受Doze
    Mode和App Standby的影响。
  • 对现有的android应用做优化,减少后台时候的内存占用,尽可能的降低被android进程回收的优先级
  • 通过native层搭建管道,监听进程状态,实现保活(非官方,短时间的调研过,效果不理想)MarsDaemon参考项目

预计工时

JobScheduleDoze模式的方案是针对android系统正规方案,预计会花费1周左右的工作时间,android应用优化的方案是针对整个团队的,包括应用后台以后,webview的资源释放,图片资源的释放等一系列的优化方案,这个时间上不好预估。但是应该需要持续去做。native层进程监听保活的话,更多是时间比较空闲的时候,去跟进调研,预计时间一周,优先级可以放低。

结论

在GCM没有办法进入国内的现阶段,应用尽量做到内存使用和电量损耗最小化,使进程被杀的概率尽量降低。针对小米的机器只能依赖于MiPush,能提供一个相对稳定的推送环境

android emoji调研

调研背景

项目中的emoji逻辑过于繁琐,且维护成本相对较高,针对emoji的几套标准还做
了不同的处理,最近出的一些emoji表情没有支持。

调研情况

当前主流的几套适配emoji的方案

  1. android4.4以上项目原生支持emoji
  2. 自己造轮子(在自有的库的基础上做优化)
  3. 使用第三方库

原生支持

优点:

不需要额外导入表情库,减少安装包的体积。

缺点:

  1. 风格不统一,缺乏广泛的辨别度
  2. 支持的系统版本偏高,不是全平台支持

自己造轮子

优点:

对以往的知识进行梳理,归纳和终结

缺点:

整套emoji的代码是基于五年前的经验搭建的,学习和借鉴的地方可能不多。而且代码相对
较为零散,整理的难度偏大

使用第三方库(建议方案

emojicon is A library to show emoji in TextView, EditText (like WhatsApp) for Android

优点:

  1. 有开源社区的支持,最近一次更新是在4个月前
  2. 源码相对条理性更强,维护难度小于项目中的代码
  3. 受众范围更大,遇到问题有更多的issue解决方案

缺点:

  1. 已知的源码在输入大量emoji表情的时候未做优化,会有卡顿
  2. 可能需要对原有的代码进行一定工作量的改动
  3. 改动可能会导致项目前期稳定性下降,但长远看,会提升项目的易维护性

工期预计:3天

总结:

最近出的emoji表情适配,方案二和方案三均没有办法很好的跟进支持,但是梳理清楚了代码逻辑以后,能提供一个方案很方便的接入新表情的支持。方案一的缺陷在于系统的限制性比较大。工作量的比较的话,方案二时间大于方案三大于方案一