西瓜理财APP开源库的使用

接着上篇说,在Android开发过程中,我们经常会遇到是否需要自己造轮子的问题,我个人认为在开发周期有限人员配置有限的情况下,还是多参考些开源库比较好,在时间空闲的时候也鼓励大家为开源事业贡献一部分自己的力量当然看个人喜好啦~接下来讲讲在开发西瓜理财APP的过程中是如何使用和寻找开源库的。

在讲之前首先学会如何科学的上网,这里我想说一下网上有很多教程和工具教你如何姿势正确的上网例如赛风、ShadowsocksX等但是经常会出现连接不稳定而且最重要的是要想稳定连接必须付费!其实这里有个简单实用的技巧可以轻松访问国外网站:修改本机的hosts。这里推荐一个地址:http://htcui.com/4938.html 这里会定期更新,有点遗憾就是不能看YouTube视频但是网站可以访问,不过对于我们开发来说已经足够了。

常用的网站
1、https://github.com/
这个就不多说了,没注册的赶紧去注册!
2、http://www.jcodecraeer.com/plus/list.php?tid=31
泡在网上的日子这个网站也分享了很多优秀的开源项目,而且都带有效果图
3、http://www.mobile-open.com/
这个网站根据不同类型和用途分的很细,Android和IOS都有
4、http://android-arsenal.com/
Android军械库,顾名思义各种神器任你挑任你选
5、http://www.23code.com/
这个也是定期会更新一批好的开源项目
6、http://p.codekk.com/
codekk的开源项目集合

这样的网站太多了,最后分享两个各种API服务的聚合网站
1、http://www.devstore.cn/
2、http://apistore.baidu.com/

最后讲讲使用开源库应该注意的一些地方
1、选择开源库的时候一定不要选择那些已经停止更新的库,尽量选择star和fork人数多的库,并且还在更新中。

2、有些开源项目虽然很好但是功能太多如果直接引入,会带来太多不必要的代码从而增大apk大小,所以建议是先学习一下然后提取对自己有用的那一部分。

3、如果自己的工程中引入了太多的开源库,那么建议在公司自己的服务器上搭建一套私服环境,通过gradle和Nexus搭配这样会使编译速度加快许多因为不用每次到外网去下载这些开源库了。

4、一个项目中不要引入过多的开源库,引入的过多会使编译变慢,apk包变大,编译冲突等问题。

另外希望大家能够将自己使用开源库的心得和遇见的问题贡献出来互相学习!


FullStackEngineer的公众号,更多分享

Share Comments

西瓜理财APP用到的开源库和工具整理

今天来聊聊我之前负责过的一款APP——西瓜理财Android版本所用到的一些开源库和开发工具,不过由于微信公众号不支持外链所以就不贴地址了。

Android studio 插件

1、Android ButterKnife Zelezny
这是著名的Jake Wharton黄油刀插件,用过的都说好,连注解都不用亲自写了,效率直线提升。

2、GsonFormat
这个插件可以将mobileapi返回的json数据直接转换为实体类,省去了我们写一大堆的字段属性和Getter、Setter方法所花费的时间。

3、Android Parcelable code generator
大家如果用到Parcelable来序列化实体类的话,将会面临比Serializable复杂的多的步骤所以通过使用这个插件来帮我们一键生成对应的方法。另外:需要注意的是当有新的属性加入的时候记得重新生成一次不然会出现序列化错误。

4、.ignore
这个是配合Git控制来忽略一些本地配置文件和不需要同步的代码文件。
5、Genymotion
这个就不必多说了,用过的都说好。

第三方库

1、Nuwa
最近议论最多的热修复框架,这只是其中一种实现方案。
2、Umeng
用来做APP统计分析的平台,不过建议大家以后可以考虑阿里最近推出的移动应用数据分析平台。
3、诸葛IO
一款精细化数据分析的工具,重点在移动用户行为分析不过由于后期数据激增开始收费了所以放弃了。
4、Cobub Razor
号称私有版的友盟,因为考虑到友盟统计的数据不太能真实反映用户情况,所以我们决定搭建一套自己的数据采集分析系统但考虑到时间成本所以采用了这个开源项目,省去了设计各种上传策略的时间。
5、极光推送
这个要说一点的是注意官网的各种cpu架构下的so文件更新,一定要全都加入工程不然会在个别机型上报本地库加载异常的错误。
6、Fresco
这个是Facebook最近推出的一款图片加载框架,对OOM的问题做了特殊优化。
7、sharesdk
第三方分享首选
8、ButterKnife
都说程序员都是比较懒的,什么事情都想着让程序自动化帮忙减轻工作量,这个开源库可以让我们从大量的findViewById()和setonclicktListener()解放出来,最令人兴奋的是其对性能的影响微乎其微!
9、Gson
谷歌GSON这个Java类库可以把Java对象转换成JSON,也可以把JSON字符串转换成一个相等的Java对象。Gson支持任意复杂Java对象包括没有源代码的对象。
10、EventBus
在编程过程中,当我们想通知其他组件某些事情发生时,我们通常使用观察者模式,正是因为观察者模式非常常见,所以在jdk1.5中已经帮助我们实现了观察者模式,我们只需要简单的继承一些类就可以快速使用观察者模式,在Android中也有一个类似功能的开源库EventBus,可以很方便的帮助我们实现观察者模式,另外注意:EventBus有好几款开源库,github上有人专门做过对比各个库的优缺点大家可以参考。
11、Netroid
Netroid是一个基于Volley实现的Android Http库,提供执行网络请求、缓存返回结果、批量图片加载、大文件断点下载的常见Http交互功能,致力于避免每个项目重复开发基础Http功能,实现显著地缩短开发周期的愿景。
12、腾讯X5浏览内核
腾讯X5浏览服务由QQ浏览器团队出品,致力于优化移动端webview体验的整套解决方案,使用QQ浏览器X5内核SDK和X5云端服务,解决移动端webview使用过程中出现的一切问题,优化用户的浏览体验,同时腾讯还将持续提供后续的更新和优化,为开发者提供最新最优秀的功能和服务。

其他开发工具

1、蒲公英
其实这种应用内侧分发的平台很多,之所以选蒲公英是因为他有mac版的客户端上传比较方便,而且还有对于的gradle代码用来实现自动化打包发布。
2、Charles
这个是一款功能比较强大的抓包工具,在跟mobileapi对接和测试中非常高效。
3、LeakCanary
强烈推荐,帮助你在开发阶段方便的检测出内存泄露的问题,使用起来更简单方便。
4、Logger
让开发调试效率提高至少300%而且心情愉悦的Log神器。

结语

今天就写到这里吧,以后会定期推荐一些好的开源库和工具,详情戳公众号的菜单栏。


FullStackEngineer的公众号,更多分享

Share Comments

西瓜小贴士图片浏览功能实现思路

最近翻到前段时间的一版设计稿,如下图这样的一个效果,当时都已经实现了但后来由于需求变更所以……没能让它和广大用户见面,但感觉这种效果还是不错的所以拿出来分享一下。

功能需求

1、右上角是页数指示器
2、左右两个操作按钮要求触摸时时消失
3、可以左右滑动

实现思路

1、图片预存在本地通过Fresco来加载
2、滑动效果通过ViewPager+fragment实现

MainActivity.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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class MainActivity extends AppCompatActivity {

private static final Integer[] IMAGES = new Integer[] {
R.mipmap.tip1,R.mipmap.tip2,R.mipmap.tip3,R.mipmap.tip4,R.mipmap.tip5,R.mipmap.tip6,R.mipmap.tip7,R.mipmap.tip8
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fresco.initialize(this);

final ViewPager pager = (ViewPager) findViewById(R.id.pager);
PageIndicator mPageIndicator= (PageIndicator) findViewById(R.id.indicator);
ImageView ivLeft= (ImageView) findViewById(R.id.ivLeft);
ImageView ivRight= (ImageView) findViewById(R.id.ivRight);

ivLeft.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pager.setCurrentItem(pager.getCurrentItem()-1);
}
});
ivRight.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pager.setCurrentItem(pager.getCurrentItem()+1);
}
});


SlidePagerAdapter pagerAdapter =
new SlidePagerAdapter(getSupportFragmentManager());

// set pictures
pagerAdapter.addAll(Arrays.asList(IMAGES));

pager.setAdapter(pagerAdapter);
pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {

}

@Override
public void onPageScrollStateChanged(int state) {

}
});

mPageIndicator.setViewPager(pager);
}

SlidePageFragment.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
public class SlidePageFragment extends Fragment {
private static final String PIC_URL = "slidepagefragment.picurl";

public static SlidePageFragment newInstance(@NonNull final int picUrl) {
Bundle arguments = new Bundle();
arguments.putInt(PIC_URL, picUrl);

SlidePageFragment fragment = new SlidePageFragment();
fragment.setArguments(arguments);

return fragment;
}

@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_slide_page, container, false);

SimpleDraweeView view = (SimpleDraweeView) rootView.findViewById(R.id.pic);

Bundle arguments = getArguments();
if (arguments != null) {
int url = arguments.getInt(PIC_URL);
view.setBackgroundResource(url);
}

return rootView;
}
}

SlidePagerAdapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SlidePagerAdapter extends FragmentStatePagerAdapter {
private List<Integer> picList = new ArrayList<>();

public SlidePagerAdapter(FragmentManager fm) {
super(fm);
}

@Override
public Fragment getItem(int i) {
return SlidePageFragment.newInstance(picList.get(i));
}

@Override
public int getCount() {
return picList.size();
}

public void addAll(List<Integer> picList) {
this.picList = picList;
}
}

详细文件请访问:https://github.com/logan62334/Gallery


FullStackEngineer的公众号,更多分享

Share Comments

Android冷启动时间优化

冷启动时间是指当用户点击你的app那一刻到系统调用Activity.onCreate()之间的时间段。在这个时间段内,WindowManager会先加载app主题样式中的windowBackground做为app的预览元素,然后再真正去加载activity的layout布局,而通常情况下这个默认背景是黑色或者白色所以如果不加以优化会让用户感觉到app很卡很慢。

知道了Android冷启动时间的原理之后,就可以通过一些小技巧来对冷启动时间进行优化,从而让你app加载变得”快“一些(视觉体验上的快)。我是通过使用app闪屏页的图片来做为windowBackground这样可以传达企业的形象。

1、为启动的Activity自定义一个Theme

1
2
3
<style name="AppTheme.Launcher">
<item name="android:windowBackground">@drawable/window_background</item>
</style>

2、将新的Theme应用到设置到 AndroidManifest.xml 中

1
2
3
4
5
6
7
8
<activity
android:name=".MainActivity"
android:theme="@style/AppTheme.Launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

3、由于给MainActivity设置了一个新的Theme,这样做会覆盖原来的Theme,所以在MainActivity中需要设置回原来的Theme

1
2
3
4
5
6
7
8
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// Make sure this line comes before calling super.onCreate().
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
}
}

最后推荐大家一个开源项目也是用来实现冷启动优化的不过是MaterialDesign风格的:https://github.com/DreaminginCodeZH/MaterialColdStart


FullStackEngineer的公众号,更多分享

Share Comments

Android应用崩溃(Crash)日志报告

我们在开发应用的过程中不可避免的会遇到各种Crash,今天分享一下如何姿势正确的处理这些Crash来提高我们的开发效率。

对于应用的Crash处理分为测试环境和生产环境。

1、测试环境
在开发过程中为了方便快速定位崩溃所发生的代码,要求我们能在崩溃的同时将日志打印出来,最好以直观的界面显示。这里推荐大家一个开源项目:CustomActivityOnCrash
github地址:https://github.com/Ereza/CustomActivityOnCrash

2、生产环境
当应用发布上线就不能给用户显示这样的界面了所以要用一种用户比较能接受的方式处理。
在Application类中配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 初始化程序崩溃捕捉处理
*/
protected void initCrashHandler() {
if (BuildConfig.isDebug) {
CustomActivityOnCrash.install(this);
} else {
CrashHandler handler = CrashHandler.getInstance();
handler.init(getApplicationContext());
Thread.setDefaultUncaughtExceptionHandler(handler);
}
}

CrashHandler类文件:

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
 public class CrashHandler implements UncaughtExceptionHandler {
/** Debug Log tag*/
public static final String TAG = "CrashHandler";
/** 是否开启日志输出,在Debug状态下开启,
* 在Release状态下关闭以提示程序性能
* */
public static final boolean DEBUG = false;
/** 系统默认的UncaughtException处理类 */
private UncaughtExceptionHandler mDefaultHandler;
/** CrashHandler实例 */
private static CrashHandler INSTANCE;
/** 程序的Context对象 */
private Context mContext;
/** 使用Properties来保存设备的信息和错误堆栈信息*/
private Properties mDeviceCrashInfo = new Properties();
private static final String VERSION_NAME = "versionName";
private static final String VERSION_CODE = "versionCode";
private static final String STACK_TRACE = "STACK_TRACE";
/** 错误报告文件的扩展名 */
private static final String CRASH_REPORTER_EXTENSION = ".cr";

/** 保证只有一个CrashHandler实例 */
private CrashHandler() {}

/** 获取CrashHandler实例 ,单例模式*/
public static CrashHandler getInstance() {
if (INSTANCE == null) {
INSTANCE = new CrashHandler();
}
return INSTANCE;
}

/**
* 初始化,注册Context对象,
* 获取系统默认的UncaughtException处理器,
* 设置该CrashHandler为程序的默认处理器
* @param ctx
*/
public void init(Context ctx) {
mContext = ctx;
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}

/**
* 当UncaughtException发生时会转入该函数来处理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
//如果用户没有处理则让系统默认的异常处理器来处理
mDefaultHandler.uncaughtException(thread, ex);
} else {
//Sleep一会后结束程序
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Log.e(TAG, "Error : ", e);
}
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
}

/**
* 自定义错误处理,收集错误信息
* 发送错误报告等操作均在此完成.
* 开发者可以根据自己的情况来自定义异常处理逻辑
* @param ex
* @return true:如果处理了该异常信息;否则返回false
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
Log.w(TAG, "handleException --- ex==null");
return true;
}
final String msg = ex.getLocalizedMessage();
if(msg == null) {
return false;
}
//使用Toast来显示异常信息
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast toast = Toast.makeText(mContext, "程序出错,即将退出",
Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
// MsgPrompt.showMsg(mContext, "程序出错啦", msg+"\n点确认退出");
Looper.loop();
}
}.start();
//收集设备信息
collectCrashDeviceInfo(mContext);
//保存错误报告文件
saveCrashInfoToFile(ex);
//发送错误报告到服务器
//sendCrashReportsToServer(mContext);
return true;
}

/**
* 在程序启动时候, 可以调用该函数来发送以前没有发送的报告
*/
public void sendPreviousReportsToServer() {
sendCrashReportsToServer(mContext);
}
/**
* 把错误报告发送给服务器,包含新产生的和以前没发送的.
* @param ctx
*/
private void sendCrashReportsToServer(Context ctx) {
String[] crFiles = getCrashReportFiles(ctx);
if (crFiles != null && crFiles.length > 0) {
TreeSet<String> sortedFiles = new TreeSet<String>();
sortedFiles.addAll(Arrays.asList(crFiles));
for (String fileName : sortedFiles) {
File cr = new File(ctx.getFilesDir(), fileName);
postReport(cr);
cr.delete();// 删除已发送的报告
}
}
}
private void postReport(File file) {
// TODO 发送错误报告到服务器
}

/**
* 获取错误报告文件名
* @param ctx
* @return
*/
private String[] getCrashReportFiles(Context ctx) {
File filesDir = ctx.getFilesDir();
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(CRASH_REPORTER_EXTENSION);
}
};
return filesDir.list(filter);
}

/**
* 保存错误信息到文件中
* @param ex
* @return
*/
private String saveCrashInfoToFile(Throwable ex) {
Writer info = new StringWriter();
PrintWriter printWriter = new PrintWriter(info);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
String result = info.toString();
printWriter.close();
mDeviceCrashInfo.put("EXEPTION", ex.getLocalizedMessage());
mDeviceCrashInfo.put(STACK_TRACE, result);
try {
//long timestamp = System.currentTimeMillis();
Time t = new Time("GMT+8");
t.setToNow(); // 取得系统时间
int date = t.year * 10000 + t.month * 100 + t.monthDay;
int time = t.hour * 10000 + t.minute * 100 + t.second;
String fileName = "crash-" + date + "-" + time + CRASH_REPORTER_EXTENSION;
FileOutputStream trace = mContext.openFileOutput(fileName,
Context.MODE_PRIVATE);
mDeviceCrashInfo.store(trace, "");
trace.flush();
trace.close();
return fileName;
} catch (Exception e) {
Log.e(TAG, "an error occured while writing report file...", e);
}
return null;
}

/**
* 收集程序崩溃的设备信息
*
* @param ctx
*/
public void collectCrashDeviceInfo(Context ctx) {
try {
PackageManager pm = ctx.getPackageManager();
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),
PackageManager.GET_ACTIVITIES);
if (pi != null) {
mDeviceCrashInfo.put(VERSION_NAME,
pi.versionName == null ? "not set" : pi.versionName);
mDeviceCrashInfo.put(VERSION_CODE, ""+pi.versionCode);
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Error while collect package info", e);
}
//使用反射来收集设备信息.在Build类中包含各种设备信息,
//例如: 系统版本号,设备生产商 等帮助调试程序的有用信息
//具体信息请参考后面的截图
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
mDeviceCrashInfo.put(field.getName(), ""+field.get(null));
if (DEBUG) {
Log.d(TAG, field.getName() + " : " + field.get(null));
}
} catch (Exception e) {
Log.e(TAG, "Error while collect crash info", e);
}
}
}
}

FullStackEngineer的公众号,更多分享

Share Comments

Git简易指南

今天来分享一下我在使用Git的过程中经常用到的一些命令:

1、创建新仓库

1
git init

2、检出仓库

1
git clone username@host:/path/to/repository

3、添加与提交

1
2
git add *
git commit -m "代码提交信息"

4、推送改动

1
git push origin master

5、创建分支

1
git checkout -b feature_x

6、切换分支

1
git checkout master

7、删除本地分支

1
git branch -d feature_x

8、删除远程分支

1
git push origin :feature_x

9、将分支推送到远端仓库

1
git push origin <branch>

10、更新你的本地仓库至最新改动

1
git pull

11、合并其他分支到你的当前分支

1
git merge <branch>

FullStackEngineer的公众号,更多分享

Share Comments