软工大作业·倾物语(二)

文章来源:中国软工亚洲指挥中心

共同作者:纪神,爵爷,老板,小男孩(按首字拼音排序)

责任编辑:爵爷

本周末我们组已经完成了《需求规格说明书》和《可行性分析报告》两份书面材料,具体内容见提交的文档。

这次想聊一下我们团队对于之后开发工作的构想。

就大的方面来说,我们在前端尽量使用现成的解决方案,并配合手写“胶水”,后端使用LeanCloud解决方案。

前端要展开有非常多的东西,因为Android的前端比HTML更加底层一点,要涉及的东西更多,考虑的面也更广,加之运行在移动平台,限制也颇多。

但是在参考现有解决方案(比如这个)以及基于我们之前的开发经验上,前端是可以跌跌撞撞地走的(当然是在不考虑外行的设计、咄咄逼人的PM、永不满足的客户的情况下)。
至于为什么不使用FB的react-native或是MUI等来做跨平台的界面,一是在性能上H5相对原生仍有不足,并且这一点在Android版本和手机性能差异极大的安卓市场上更为明显,二是一个移动端的应用无论干什么顶部都有一个小绿条咕咕咕地动在我们看来非常难受(纯个人观点)。因此我们考虑只在一些不重要的信息展示界面和时间上来不及的扩展功能上使用H5,其他情况仍然使用原生API开发。

还有就是 JetBrains(笔者免费IDE的提供商,因为有教育账号)的亲儿子 Kotlin,笔者没有深入了解,但是简单看了一下,第一感觉就是好玩,还没有感受到特别强大的地方(当然是了解非常不足),基于学习成本的考虑,我们还是使用了 Java 来作为主开发语言。

后端我们使用现成且强大的解决方案 LeanCloud。笔者多次被问到过“用LeanCloud这么简单方便甚至可以说是无脑的东西,还算是程序员吗?”。这种想法笔者之前也有过,但是如果不接受更快更好更方便的东西(并不是说LeanCloud就一定是这样,LeanCloud的适用场景其实限制还是蛮大的,这里不展开。但是相对于我们目前的开发需求,LeanCloud就是这样的),我们现在想用计算机还得在竹简上钻孔呢(笑)。另外笔者琢磨过算法,写过汇编优化的编译器(特别指明:最后什么都没做出来),也裸写过 socket 以及 RESTFUL,所以对于“不方便”的东西笔者多少还是有发表意见的权利的。出于学习目的或者对于要求特别严格的解决方案,唯有从底层慢慢写,但是就目前的场景来看使用 LeanCloud 是非常好的选择。应该针对不同的开发需求使用不同的方案,这就是我们使用 LeanCloud 的原因。

另外在开发上,我们目前的想法是先下手开始写代码,而不是先做细致的设计。因为目前团队整体的项目开发经验是不太够的,没有足够经验的支撑,一上来就做细致的设计很有可能会忽略了重要的东西而把精力放在了其实无足轻重的地方,而且做出的设计并不一定效果有多好(编者个人认为可以参考 Java 第一代 UI 库的设计)。当然先写代码并不是说“先写了代码再提取设计以完成任务”,而是先用粗糙的代码把流程简单走一遍,探探路上都有什么坑,然后再回头做设计,这样心里会踏实很多,设计结果也会更加可靠。这也是我们这个月的主要任务:做第一版最小化原型。

目前编者已经搭好了基于 Viewpager 的 Swipe View 以及 4 个 Fragment 作为主界面,下周先分工把这四个主界面按照原型设计做出来。然后依次跟进其他代码任务。下面是应用效果以及项目规模统计(是的我知道很丑,请不要再吐槽→_→)。

界面效果

程序规模

软工大作业·倾物语(一)

文章来源:中国软工亚洲指挥中心

共同作者:纪神,爵爷,老板,小男孩(按首字拼音排序)

责任编辑:爵爷

本周六我们进行了一整个下午的详尽讨论,围绕以下几点进行了细致的分析,并且有了比较明晰的结论。

APP的名字

这是最早被提出的问题,但也可能是(不只是一个笨手笨脚的大作业,对于所有的项目都是)最难的一个问题之一。这几乎不涉及技术问题,而是需要外对于整体产品和用户市场、内对于项目定位的一个精准的把控。

由于条件的限制,我们并不能在这一点上花费太多的时间,我们认为基于我们实际情况的最佳的名字应该能够同时反映信息收集 + (基于共同关注点的)社交这两点,在头脑风暴中,我们想了很多正儿八经以及奇奇怪怪的名字。

  • 信息人大
  • 校园风
  • 三人行
  • 校园通
  • 圈里信息
  • 校园知事

最终我们比较中意的是“校园知事”,它满足了我们提出的两点基本要求,并且发音同“芝士”也让人多少有些亲切感(→_→ Fate第十职阶Eater)。暂定这个名字,只有有更好的想法再进行修改。

关于《需求规格说明书》以及《可行性分析报告》

在定下了基本的思路与设计之后我们开始着手书面材料。第一步要做的就是《需求规格说明书》以及《可行性分析报告》。在《需求规格说明书》中我们将按照课件上的标准进行编写,并且对成员进行了分工,具体分工如下:

  • 第一部分:系统规格说明:小男孩。内容包括系统概貌、功能要求、性能要求、运行要求、可能增加的要求、DFD、IPO
  • 第二部分:数据要求:纪神。内容包括DD、Hierarchy或Warnier Diaggram
  • 第三部分:用户系统描述:爵爷。内容包括初步用户手册:从用户的观点考虑系统,系统功能、性能 、使用与步骤
  • 第四部分:修正的开发计划:老板。 内容包括成本估计 ,资源使用计划 ,进度计划等。

在《可行性分析报告》上,如果按照业界正式工程的格式来做的话会非常繁冗,并且其中涉及的市场背景、政策背景、法律背景以及资金链等对于我们目前来说有些不切实际,所以我们对于《可行性分析报告的规格》的格式进行了适应性调整。主要分为六个部分:

  1. 背景了解以及基于问卷调查的需求分析
  2. 对市场现有的产品进行分析对比
  3. 我们满足这个需求的思路想法
  4. 技术层面上实现我们的思路想法
  5. 产品的测试和维护
  6. 产品的推广和运营

《可行性分析报告》将于《需求规格说明书》之后完成,届时将根据情况进行调整。预计最晚于下周末完成《需求规格说明书》和《可行性分析报告》这两份书面材料。

关于微人大权限以及各院API接口

因为涉及人大身份(目前APP只覆盖人大范围)验证问题,我们需要微人大的开发者权限,并且在信息收集这方面如果能直接拿到各个学院的新闻API岂不美哉[王司徒脸]。

在微人大权限上,我们在微人大上申请了开发者账号,系统说7日之内会给回复,然而两个七日过去了······再等等,再催催好了,另外关于微人大的讨论中又催生出了一个问题:登录模式。是直接采用微人大账号登录还是新注册账号登录,再于APP中进行人大身份验证(就像进入APP后再进行邮箱验证一样)。经过讨论我们认为,如果使用微人大账号登录虽然直接方便,但是给人一种官方死板的气息(不吹不黑),且不利于之后用户群体的扩展(怎么能占领清华北大呢);使用独立账号注册 + 后期验证的方式虽然多了一步流程,但是让人感觉不受束缚(这种感觉很难描述),并且可以让用户先体验一部分功能,并有利于之后对清华北大的占领工作。

在各院新闻API获取这方面,我们以尽可能诚恳的语气向30多个学院发送了邮件,分析了共赢新性并询问是否能拿到新闻的接口,但截至目前没有任何学院给予回复(其实也难怪,人大黄页上找到的邮箱地址有好几个格式都是错的,还有几个要么没这个主机要么没这个用户)。我们并不准备就以上的经历来分析办事效率问题,因为很可能是我们的方法出了问题。目前看来用爬虫抓新闻是比较可行的选择,现阶段无法预料在爬虫部分将遇到的问题,我们将于下周写一份爬虫的demo,并连接LeanCloud进行测试。

做最小化原型的详细功能设计以及简略的UI

这两部分是结合在一起做的,在简要功能的讨论中画出了主要的UI。我们不在这里贴出讨论过程中的草图,之后我们会用原型设计软件重新把图画一遍再发布。主要的界面就是新闻消息、 好友、发现(与自己关注点相同的人,了解自己最近的关注点或者查看自己的时间轴等等)以及更多(个人信息以及应用设置等)。虽然这是至关重要的一点,但是我们认为于”空想阶段“在这上面花费太多的时间不太合适,应该结合最小化原型设计出一个简略的功能框架,然后在做的过程中踩坑。

进度安排

在这点上我们同样无法做出细致的估计,因为涉及公关处理、策略转型或团队配合等方方面面的问题,我们只是做了一个大阶段的预期。

  • 四月份: 第一版开发与测试
  • 五月份: 第一版集中测试,第二版开发与测试
  • 六月份: 第二版集中测试,产品推广

总体来说我们目前的进度是可以接受的,在第一版的开发过程中一定会遇到非常多的问题,而且这些问题中有些将超出我们的预期与能力(即所谓的”坑“),我们会翔实地把这个过程中的心得体会记录下来,作为之后的一个宝贵的参考。

软工大作业·源物语(三)

文章来源:中国软工亚洲指挥中心

共同作者:纪神,爵爷,老板,小男孩(按首字拼音排序)

责任编辑:爵爷

上周我们在拿到问卷统计数据之后,就APP的初步设想进行了讨论。得到了以下结果:

最小化原型的功能集

  1. 管理用户(用户要么直接接入微人大,要么另开注册通道。实在不行用mechanize等进行模拟)
  2. 收集信息(教务信息、实习信息等)
  3. 管理信息(教务信息、实习信息等)
  4. 记录用户喜好,寻找趣味相投的人
  5. 私信(站内通知)

开发的阶段以及模块

APP模块

  1. 信息管理模块
  2. 喜好推测模块
  3. 站内消息模块
  4. APP管理模块(应用设置,缓存管理,版本检测与更新等)

后台网站模块

手动添加信息,后台管理等

爬虫模块

信息爬取模块(通过网站送入LeanCloud)是否可以拿到学校后台的API

需要向各学院以及学校发邮件询问(包括各种新闻的API,以及微人大入口)

数据库粗略设计

  • 用户表
  • 信息表
  • 信息类型表
  • 用户喜好表
  • 关注者表
  • 被关注者表
  • 私信表
  • 映射表(放在爬虫端,不同的来源对信息的划类方式不同,统一映射成我们自己的格式)

除此之外还有其他的一些细节问题。讨论完之后我们又对数据库每张表的内容进行了设计,得到了一个初步的数据库方案,并准备再次进行修改验证。详细的方案会放在之后正式的文档中。

软工大作业·源物语(二)

文章来源:中国软工亚洲指挥中心

共同作者:纪神,爵爷,老板,小男孩(按首字拼音排序)

责任编辑:爵爷

本周我们设计并回收了一套调查问卷,用以了解在校大学生对于学校各类信息的需求程度、了便利程度以及对于基于关注信息的应用的期待程度。最终回收结果部分摘录如下:

  • 在刚刚结束不久的选课的过程中,您或身边的同学是否有询问过选课何时结束之类的问题?

    调查结果

  • 在刚刚结束不久的选课的过程中,您或身边的同学是否有询问过选课何时结束之类的问题?

    调查结果

  • 您觉得您获取消息及时吗?

    调查结果

  • 您获取消息的途径一般以什么为主?

    调查结果

  • 您是否使用校内新闻消息推送之类的APP?

    调查结果

  • 如果有人跟您关注相同的新闻,您是否愿意跟他(她)交流看法或互相分享信息?

    调查结果

  • 如果现在有一款推送校内信息的APP您有多大程度愿意使用?(1很愿意,5很不愿意)

    调查结果

从主要的调查结果部分来看,能简要总结出以下几点关键信息:

  • 在校学生对于教务信息等学校官方信息需求很大,需求种类也很多
  • 有相当一部分在校学生不能及时获得需要的信息
  • 在校学生的信息来源较为分散,并且信息的接收比较被动
  • 绝大多数在校学生并没有正在使用的校园信息推送应用
  • 将近半数的在校学生非常愿意与有着相同关注点的人交流,几乎所有的在校学生都可以接受这种交流
  • 41%的在校生对于校内信息推送的APP有着相当的期待,另外有41%的在校生持观望状态

以上是报告的部分内容,详细的分析报告我们会在《可行性分析》中给出。

从报告中可以看出我们目前的设想是基本可行的,下一步是尽快商讨最小化原型并收集用户评价,对现有轨道进行进一步的调整。

软工大作业·源物语(一)

文章来源:中国软工亚洲指挥中心

共同作者:纪神,爵爷,老板,小男孩(按首字拼音排序)

责任编辑:爵爷

在组队初期,我们就达成了一致的意见:做一款为学生群体服务的Android平台上的应用。在前两周中,我们查访并使用了市面上很多流行的APP,并且尽力避免在用户和投资人眼中都基本上不再有希望的应用,如O2O类、基于IM的社交类应用等。

最终吸引我们注意力的是两款应用:今日头条和芥末校园。我们注意到这两款应用虽然用户量算不上特别巨大,但是在学生群体中有比较好的使用氛围,并且两者的方向也都非常受我们组成员的喜欢。

  • 今日头条: 是一款新闻APP,但不同于传统APP的一点是它会不断学习掌握用户的新闻喜好,并不断修正,进而推送给用户其感兴趣的新闻,这样方便并且带有一定“黑科技”色彩的应用一经推出便好评不断,公司发展目前也是蒸蒸日上。
  • 芥末校园: 是一款校园交友APP,据我们了解,基本上90%的社交软件最后都变成了约炮软件(更何况这是一款校园!校园社交软件),虽然这其中有很多都是制作方有意无意的推动造成的,但交友最终变成交肾仍是我们对于社交类应用比较大的顾虑。遗憾的是,我们在这款软件上也看到了一些约炮软件套路的影子,但是这款软件的交友方式让我们很感兴趣。软件是需要注册用户先填写自己的信息以及兴趣点,之后可以在广场查看和自己兴趣相投(而不是**厘米图片)的用户,如果感兴趣就点个赞,当两人互相点赞(用户是看不到谁给自己点了赞的)之后就会成为好友。我们不能保证这一定是个有前途的点子,但这一定是有趣的。

除了查找了大量的APP,我们还注意在身边查找潜在的需求。我们发现学生群体需求比较大的一点是校园中各种信息,如出国信息、保研信息、招聘信息以及其他各种各样的事情。虽然这些信息在校网站、院网站或者班长发来的邮件中都能找到,但是信息来源分散,而且相当一部分同学都没有按时查看校网站、院网站或者邮箱的习惯,所以这是不容忽视的一点需求。

我们经过调查,发现之前有不少人尝试过做类似的软件,比如社团宣讲信息、讲座信息的集成APP等,但都不温不火最终慢慢消失了。我们认为这是由于单纯的信息集成虽然满足了用户需求,但是没有也无法培养用户粘性,致使用户只是单纯的用来查过几次信息,之后就遗忘在了手机的某个角落。

综合以上种种,我们决定制作一款能满足学生群体对于信息集成的需求的软件,同时做需求上的纵向延伸,培养用户粘性,并且增加推荐率(用户向其他用户安利或者其他用户看到了相关的信息进而加入)。当前的打算是借鉴今日头条,通过用户查看的种种信息掌握用户目前阶段的关注点,再借鉴芥末校园通过这个关注点推荐和用户有相同或相似关注点的用户,通过社交来增加用户粘性。

我们也研究了《精益创业》中的很多说法,认为单纯自己去做设想或是“信仰之跃”是没有意义的,所以还是需要了解用户需要什么。因此我们下一步的目标就是尽快制作一份问卷,调查使用过类似软件的用户的看法,以及没有使用过类似软件的用户对于这个想法的意见和建议,进而修正目前的轨道。

我的网站

终于找到相对靠谱的服务器了,域名:

rucer.cn

意思是:人大人.cn,果然人大是文科性质的学校,这么靠前的域名竟然没人注册。

旧版网站比较LOW,就不放上去了,之后有时间做新版。

处理运行时状态的改变

英文原文地址 http://developer.android.com/guide/topics/resources/runtime-changes.html

一些设备配置会在运行时改变(比如屏幕方向、键盘可用性和语言)。当发生这些改变时,Android会重新启动正在运行的Activity(onDestroy()会被调用,之后是onCreate())。设计重新启动行为是为了通过自动重新加载你的应用从而适应新的配置,重新加载的应用会采用适应新配置的替代资源。

要正确地处理重新启动,非常重要的一点是你的activity在正常的Activity活动周期中存储下来它之前的数据,在活动周期中,Android在销毁你的activity之前会调用onSaveInstanceState()从而你可以保存有关应用状态的数据。你可以在onCreate()或者onRestoreInstanceState()中恢复数据。

为了测试你的应用是否状态完好地重新自启动,你应该在应用进行各种任务时改变配置状态(比如修改屏幕方向)。为了能够处理诸如配置改变或者用户接了一个电话,之后在你的应用进程被销毁之后回到应用之类的情况,你的应用需要能够在保证用户数据或状态无损的情况下随时重启。如果你想知道如何恢复activity的状态,请阅读Activity lifecycle.

但是你可能会遇到这样的情况:重新启动应用并恢复大量的数据开销非常大,而且会导致糟糕的用户体验。在这种情况下,你有另外两个选择:

  1. 在配置改变期间保留一个数据对象
    允许你的activity在配置改变时重启,但是要为你的activity保留一个有状态的对象(object)。
  2. 你自己来处理配置的改变
    在特定的配置改变时阻止系统重启应用,但是当配置改变时要接收回调函数,从而你可以按照需求手动地更新的你的activity。

在配置改变期间保留一个数据对象

如果重启应用需要你恢复大量的数据,重新建立网络连接或者进行其他密集型操作,那么由于配置改变而造成的一次完整的重启可能会是一次非常慢的用户体验。除此之外,你可能也无法使用系统通过onSaveInstanceState()回调函数为你保留的Bundle完整地恢复你的activity状态——它并非是被设计用来承载比较大的对象(比如bitmaps)的,而且其中的数据必须被序列化和反序列化,这些操作会占用非常多的内存并且使得配置切换缓慢。在这种情况下,你可以通过持有一个Fragment来减轻因为配置改变而重启activity时的负担。这个fragment将会保存你想要留存的有状态的对象的引用。

当安卓系统因为配置改变而关闭你的activity时,你标记为保留的fragment不会被销毁。你可以在你的activity中添加这样的fragment来保存有状态对象。

要想在运行配置改变期间在fragment中持有有状态对象:

  1. 继承Fragment类并声明对你的有状态对象的引用。
  2. 当fragment创建时调用setRetainInstance(boolean)。
  3. 把这个fragment添加到你的activity中。
  4. 当activity重启之后通过FragmentManager获取这个fragment。

举个例子,像下边这样定义你的 fragment:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class RetainedFragment extends Fragment {

// data object we want to retain
private MyDataObject data;

// this method is only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}

public void setData(MyDataObject data) {
this.data = data;
}

public MyDataObject getData() {
return data;
}
}

小心:虽然你可以存储任何对象,但是永远也不要传递一个绑定在Activity上的对象,比如Drawable,一个Adapter,一个View或者任何一个关联到Context上的对象。如果你这么做的话,会导致原有activity实例所有视图和资源的泄露。(资源泄漏是指你的应用一直挂起这些资源使他们无法被垃圾回收,因而很多内存会流失掉。)

然后使用FragmentManager把fragment添加到activity。当activity在配置改变期间重新启动后你可以从这个fragment获取数据对象。例如,像下边这样定义你的activity:

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
public class MyActivity extends Activity {

private RetainedFragment dataFragment;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag(“data”);

// create the fragment and data the first time
if (dataFragment == null) {
// add the fragment
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, “data”).commit();
// load the data from the web
dataFragment.setData(loadMyData());
}

// the data is available in dataFragment.getData()
...
}

@Override
public void onDestroy() {
super.onDestroy();
// store the data in the fragment
dataFragment.setData(collectMyLoadedData());
}
}

在这个例子中onCreate()添加了一个fragment或者恢复了对它的引用。onCreate()也在fragment实例中存储了有状态对象。onDestroy()更新了持有的fragment中的有状态对象。

你自己来处理配置的改变

如果你的应用在特定的配置改变时不需要更新资源并且由于性能限制不允许重启activity,那么你可以声明你的activity自己来处理配置改变,这样可以阻止系统重启你的activity。

标注:亲自处理配置改变将会比使用可选资源困难得多,因为系统不会自动为你应用这些资源。当你一定要禁用由于配置改变而造成的重新启动时,这项技术可以被看做最后的手段,对于大多数应用并不推荐。

为了声明你的activity要处理一项配置更改,在你的manifest文件中编辑合适的标签来修改android:configChanges属性,填入合适的值以代表你要处理的配置。可选的值列在了android:configChanges属性的文档中(最常用的值是”orientation”来避免屏幕方向改变时activity的重启,以及”keyboardHidden”来避免键盘可用性发生改变时activity的重启)。你可以通过使用 | 把值隔开的方式声明多个值。

举个例子,下边的manifest代码声明了一个同时处理屏幕方向改变和键盘可用性改变的activity。

1
2
3
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name">

现在,当这些配置的其中一个发生改变时,MyActivity不会重启,而是接收到了一个对onConfigurationChanged()的调用。这个方法传递了一个Configuration对象,指定了新的配置。通过读取Configuration中的字段,你可以定义新的配置并且通过更新你的界面所用资源来做出合适的修改。当这个方法被调用时,你的activity的Resources对象会被更新并返回基于新配置的资源,从而你可以在系统不重启你的activity的情况下重置你的UI的元素。

注意:从Android3.2(API 版本13)开始,当设备在横屏和竖屏之间进行切换时”screen size”也会发生变化。因此在做API版本13或者更高(在属性minSdkVersion和targetSdkVersion中声明)的开发时,如果你想避免因为屏幕方向改变而引起的重启,你必须在”orientation”的基础上包含”screenSize”属性。也就是说,你必须声明android:configChanges=“orientation|screenSize”。但是如果你的应用是面向API版本12或者更低的,那么你的activity总能自己处理这种配置改变。(即使运行在Android版本3.2或者更高的设备上,配置改变也不会引起activity重启)

例如,下边的onConfigurationChanged()实现检查了当前设备的屏幕方向:

1
2
3
4
5
6
7
8
9
10
11
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);

// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
}
}

Configuration对象表示出了目前所有的配置,不仅仅是已经改变了的。大部分情况下,你并不关心你的配置是如何改变的以及它是如何简单地重新分配你的资源以达到为你正在处理的配置提供备选的目的。例如,因为Resources对象现在已经更新,你可以通过setImageResource()重新设置ImageView,你也可以设置其他对已经使用的配置来说合适的资源(就像Providing Resources说的那样)。

注意Configuration中的字段都是整型,并且他们都与Configuration类中的常量相匹配。你可以参考Configuration中具体的部分来获取每个字段使用的常量。

记住:当你声明你的activity以处理配置改变时,你有责任重新设置你为之提供备选的每个元素。如果你声明你的activity来处理屏幕方向变化并且有需要在横屏和竖屏之间切换的图片,你必须在onConfigurationChanged()中为每个元素重新分配新的资源。

如果你不需要基于这些配置修改做activity的更新,你可以不去实现onConfigurationChanged()。在这种情况下,配置更改前后使用的资源是相同的,你仅仅是避免了activity的重新启动。但是,你的应用应该总是能够关闭后再打开时原样保留之前的状态。因此,在正常的activity生命周期内,你不应该把这项技术当成保留状态的选择。不仅是因为你不能阻止一些其他的配置改变引起的应用重启,而且你还应该处理诸如用户离开应用,在用户再次开启应用之前应用就被销毁的情况。

想了解更多关于你在activity中可以处理的配置改变,请查阅android:configChanges文档和Configuration类。

Python 进行 Web 开发

假期没事学python,因为之前一直在搭个人网站,后台用的是PHP,所以想用Python重写一下后台。关于python开发web应用,网上有一大堆教程,最多的是推荐用apache加载mod_python这个模块,看了下官网,13年停止更新了,而且对Python 3的支持很差,所以并不推荐这个。类似的有mod_msgi,这个可以看做是mod_python的继任者,但是配置起来尤其是在windows上配置起来非常麻烦,初学者做起来可能会比较痛苦。推荐使用Django框架开发,关于服务器官网里有这么一段话:

You’ve started the Django development server, a lightweight Web server writtenpurely in Python. We’ve included this with Django so you can develop thingsrapidly, without having to deal with configuring a production server – such asApache – until you’re ready for production.

也就是Django自带了纯python编写的服务器,非常方便,在测试阶段和小规模的开发阶段,用这个足以满足需求。关于教程,强烈推荐去官网硬着头皮读英文原版(注意教程版本要和自己下载的Django一致)而不要去看翻译完的中文版,因为Django更新很快,而且时不时有大的变动,看比较老的版本的话有时候回给自己造成莫名其妙的困扰。

Django官网:https://www.djangoproject.com/

Python下载:https://www.python.org/

下载完之后,先把python.exe的目录放到环境变量的Path下(Windows系统),然后命令行进入Django目录,执行 [ python setup.py install ] 即可。安装Django成功之后,在Django官网中按照教程一步步来就可以了。

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class First extends Thread
{
public First()
{
start();
}

synchronized public void run()
{
try
{
wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
try
{
sleep(2000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println("hello world~");
}
}

class Second extends Thread
{
First first;
public Second(First first)
{
this.first = first;
start();
}

synchronized public void run()
{
try
{
wait();
}
catch (InterruptedException e1)
{
e1.printStackTrace();
}
synchronized( first )
{
try
{
sleep(2000);
System.out.println("I'm faster than first~");
}
catch(InterruptedException e)
{
e.printStackTrace();
}
first.notifyAll();
}
}
}

public class Main
{
public static void main(String[] args) throws InterruptedException
{
First first = new First();
Second second = new Second(first);
synchronized( second )
{
System.out.println("I'm faster than second~");
second.notifyAll();
}
}
}

本以为输出会很顺畅,但是出现的问题是,只输出了一行:I’m faster than second~

程序就一直处于无响应状态,纠结了好久终于想明白是这么一回事:在main函数中,对second.notifyAll()的调用早于second中的wait()调用(因为是多线程并行,故函数响应时间与代码先后顺序无关),这样先唤醒了second,紧接着second才开始wait,因此就处于无响应状态。

改进方法:只要在second.notifyAll()调用之前空出一点时间先让second的wait调用开始即可,事实上,这段时间如此之短以至于在我电脑上只需要在之前加一行输出语句即可。为了保险起见,还是多加了个sleep,改进后代码如下:

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
class First extends Thread
{
public First()
{
start();
}

synchronized public void run()
{
try
{
wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
try
{
sleep(2000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println("hello world~");
}
}

class Second extends Thread
{
First first;
public Second(First first)
{
this.first = first;
start();
}

synchronized public void run()
{
try
{
wait();
}
catch (InterruptedException e1)
{
e1.printStackTrace();
}
synchronized( first )
{
try
{
sleep(2000);
System.out.println("I'm faster than first~");
}
catch(InterruptedException e)
{
e.printStackTrace();
}
first.notifyAll();
}
}
}

public class Main
{
public static void main(String[] args) throws InterruptedException
{
First first = new First();
Second second = new Second(first);
System.out.println("wating for all threads prepared~");
Thread.sleep(2000);
synchronized( second )
{
System.out.println("I'm faster than second~");
second.notifyAll();
}
}
}

输出结果:

1
2
3
4
wating for all threads prepared~
I’m faster than second~
I’m faster than first~
hello world~

文件夹遍历 Java 版

Java下的File类(文件类,其实感觉文件类有点误导性,用“文件路径”会好一点)有list方法,会返回指定路径下所有文件和目录,用这个方法以及简单的递归可以写出一个简陋的文件遍历程序

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
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class test_for_team_study_2
{
public static void traverse(String name, FileWriter wt) throws IOException
{
wt.write(name + "\r\n");
File path = new File(name);
String[] list = path.list();
if( null == list )
return ;
for(int i = 0; i < list.length; i ++)
if( -1 == list[i].indexOf(".") )
traverse(name + "/" + list[i], wt);
}

public static void main(String[] args) throws IOException
{
FileWriter wt = new FileWriter("C:/Users/Administrator/Desktop/file in D.out");
String name = "D:/";
File path = new File(name);
String[] list = path.list();
for(int i = 0; i < list.length; i ++)
{
wt.write("now is the file#:" + i + "\r\n==========================\r\n");
traverse(name + "/" + list[i], wt);
wt.write("\r\n\r\n\r\n");
}
wt.flush();
wt.close();
System.out.println("finished!");
}
}