Android中的类MVC设计方案 – Trigger机制

当然,“MVC”是针对Web项目提出的;也就是说,Android中没有MVC一说;但此处“MVC”能十分精辟地进行概括,毕竟Java开发人员对该概念十分之熟悉,所以在此借用“类MVC”;下面进入正题:

一、效果图展示

android 4j trigger     android 4j triggerandroid 4j trigger

层面是无法体现该方案的便宜之处,但从设计层面可以得到很好的体现,层次划分清晰可见,每层各负其责,耦合度相当之低。为了更能说明问题,4J就分别以ListActivity(即上图中的“4J NewsHeadline”)和Activity(即上图中的“4J Weather”)进行展示;但两者的实质完全相同,避免累赘,下文在阐述时仅以“4J NewsHeadline”为例。

二、设计思想

为了能更直观的阐述设计思想,先将类图和时序图贴出来:
android 4j trigger
android 4j trigger

1. OnTriggerListener

该Interface有且仅有1个方法onTrigger (),具体如下:

public boolean onTrigger(TriggerInfo triggerInfo);

2. FJUIControlManager

首先,该类中有3个成员属性:
mCurrentActivity:Activity,用于记录当前处于foreground状态的Activity;
mOnTriggerListener: OnTriggerListener,用于监听mCurrentActivity;
mFJUIControlManagerHandler: FJUIControlManagerHandler

其次,该类中有3个重要的成员方法:setCurrentActivity()、setOnTriggerListener()和postTrigger()。

该类的详细代码如下:

public class FJUIControlManager {

	// 当前处于foreground状态的Activity
	private static Activity mCurrentActivity;
	// 用于监听mCurrentActivity
	private static OnTriggerListener mOnTriggerListener;

	// 接收Control层post过来的Trigger,并通过重写handleMessage()做出相应处理
	private static final FJUIControlManagerHandler 
		mFJUIControlManagerHandler = new FJUIControlManagerHandler();

	public static void setCurrentActivity(Activity currentActivity) {
		mCurrentActivity = currentActivity;
	}

	public static void setOnTriggerListener(OnTriggerListener onTriggerListener) {
		mOnTriggerListener = onTriggerListener;
	}

	public static void removeOnTriggerListener() {
		mOnTriggerListener = null;
	}

	/**
	 * 供Control层调用,将Trigger post到主消息队列中去。
	 */
	public static void postTrigger(TriggerInfo triggerInfo) {
		Message msg = mFJUIControlManagerHandler.obtainMessage
                                                  (TriggerId.IDENTIFY, triggerInfo);
		mFJUIControlManagerHandler.sendMessage(msg);
	}

	private static class FJUIControlManagerHandler extends Handler {
		/**
		 * 注意:一定得是Looper.getMainLooper()
		 */
		public FJUIControlManagerHandler() {
			super(Looper.getMainLooper());
		}

		@Override
		public void handleMessage(Message msg) {
			// msg来自于postTrigger()
			if ((msg == null) || (msg.what != TriggerId.IDENTIFY) 
                             || (msg.obj == null) || (msg.obj.getClass() != TriggerInfo.class))
				return;

			TriggerInfo triggerInfo = (TriggerInfo) msg.obj;
			mOnTriggerListener.onTrigger(triggerInfo);
		}
	}

}

3. FJListActivity

该类主要负责重写onResume()、onPause()和实现onTrigger();在onResume()中主要负责给FJUIControlManager. mCurrentActivity赋值和注册监听,这样在子类中就不需每次重复,只需super.onResume()代替之;在onPause()中主要负责注销监听,同理在子类中只需super.onPause()代替之;而onTrigger()留给子类重写,进而分别实现各自的功能。该类的具体代码如下:

public class FJListActivity extends ListActivity implements OnTriggerListener {

	@Override
	protected void onResume() {
		super.onResume();

		FJUIControlManager.setCurrentActivity(this);
		FJUIControlManager.setOnTriggerListener(this);
	}

	@Override
	protected void onPause() {
		super.onPause();

		FJUIControlManager.removeOnTriggerListener();
	}

	public boolean onTrigger(TriggerInfo triggerInfo) {
		return true;
	}
}

4. FJTriggerMainActivity

该类继承FJListActivity,有1个重要的成员属性:mNewsHeadlineRequestId;2个重要的成员方法:onListItemClick()和onTrigger();具体代码(主要部分)如下:

public class FJTriggerMainActivity extends FJListActivity {

	// 发送NewsHeadline请求时返回的Trigger唯一标识符
	private int mNewsHeadlineRequestId; 

	@Override
	protected void onListItemClick(ListView l, View v, int position, long id) {		
		switch (position) {
		case 0:			
			// 将数据请求发送到Control层,返回一个Trigger唯一标识符。
			mNewsHeadlineRequestId = NewsHeadlineControl.requestHeadlines();
			break;
		default:
			break;
		}
	}

	@Override
	public boolean onTrigger(TriggerInfo triggerInfo) {
		int triggerId = triggerInfo.getId();

		if (triggerId == mNewsHeadlineRequestId) {
			switch (triggerInfo.getArg1()) {
			case TriggerId.NEWS_HEADLINE_SUCCESS:
                                // 画面跳转
				startActivity(new Intent(this, FJNewsHeadlineActivity.class));
				break;
			case TriggerId.NEWS_HEADLINE_SERVER_EXCEPTION:
				Toast.makeText(this, 
                                             R.string.news_headline_request_server_exception, 1000).show();
				break;
			default:
				break;
			}
		} else {}

		return true;
	}

}

5. NewsHeadlineControl

该类代码比较单一,不需过多解释,就直接贴代码:

public class NewsHeadlineControl extends FJControl {

	private static List mHeadlinesList = new ArrayList();

	public static int requestHeadlines() {
		// 从服务器获取数据
		getHeadlinesFromServer();

		TriggerInfo triggerInfo = TriggerInfo.factory
                                                        (TriggerId.NEWS_HEADLINE_IDENTIFY,
						         TriggerId.NEWS_HEADLINE_SUCCESS);

		// 将Trigger发送到消息队列中去
		FJUIControlManager.postTrigger(triggerInfo);

		return triggerInfo.getId();
	}

	public static List getHeadlines() {
		return mHeadlinesList;
	}

6. FJNewsHeadlineActivity

该类主要用于展示内容,而这些数据从Control层取得:NewsHeadlineControl.getHeadlines()

三、源码下载

http://7xl53s.com1.z0.glb.clouddn.com/download/2015/08/src/4jtrigger.zip

关于搭建Android App – APK推荐站点的一些想法

4J前期做过部分准备工作,并将该项目命名为“APK Talk”,下面将详细阐述其中的各个细节。

一、想法萌芽

前不久入手一部HTC Desire,本本分分地熟悉了一星期;实在是耐不住寂寞,开始折腾起来。首先,刷MIUI的ROM——当然,这个不是我要说的重点;然后,开始折腾App。

在此插播一段说明,4J个人习惯将Android App称为APK,可能有些许牵强,APK是Android Package的缩写,可能有点偏向于开发方面的知识。这种叫法主要有以下两种缘由:第一,Android App安装包的扩展名就为”.apk”。第二,出于搜索排名方面的考虑,如果以”android app”作为关键字进行Google,因为是组合查询,搜索结果中无效数据的比例应该非常大;毕竟当下iPhone已经自然而然地抢占”app”关键字,民众一听到”app”这三个字符就立马想到的是Apple的AppStore;但如果以”apk”作为关键字的话,无效数据比例应该非常之小。

回到正题。所谓智能机,最大的乐趣应该来源于折腾无穷无尽的App;但作为一名Android菜鸟,如何选取APK就是一件令人无比头疼的事情。当时4J就在想,如果有这么一个APK推荐站点该多让人省心。

二、市场需求

作为一个Android手机终端用户,对手机的功能需求就不仅仅只是局限于语音通话和文字短信;他们往往对功能的需求会高出许多,或者是对新功能充满好奇,等等;然而,功能的载体是APK;所以说随着Android手机用户数量的节节攀升,对APK的需求会日益增多;软件厂商为了满足用户的需求,进而达到抢占市场的目的,就会大量发布APK。

如此一来,菜场里就如雨后春笋,数以亿计的APK上架,最终导致终端用户不知如何取舍。作为一名折腾者,4J一般是通过如下几种途径来获取心仪的APK,具体如下:

1. 菜场排行榜

android market 排行榜

2. 搜索引擎

以下两幅截图分别对应“android app 推荐”和“android app 测评”关键字的搜索结果,呈现出一种十分发散的态势;反应出这两组关键字没有被特意推广,也就是说,APK推荐或测评站点少之又少。

android app 推荐

android app 测评

3. 文字推荐

例如某个博主或围脖主钟情于某一款APK,然后写篇日志对之进行阐述;但他们并不是专门从事APK推荐,这部分比例应该很小。

综上所述,APK推荐站点的市场需求还是非常之大,前景被4J所看好。

三、示例站点:Apps Ku

大体与Apps Ku类同,在细节方面可能有一定的差别。4J感觉Apps Ku可取之处就是关注点比较专一、切入点十分准确;但上面的文章内容过于简单,基本上在菜场的APK描述一栏就能获取得到,可读性有待深究。通过阅读上面的文章,读者只能获得该App的名字,而无法知晓是否是自己需要的,因为文章写的过于官方,省略掉核心的测评感言片段;从而得进行安装试玩;如此一来推荐站点的效果就大打折扣,发挥不到应有的效应。

4J当前有些许不成形的想法,例如,测评感言、对比测评、视频测评等。本想和Herock交流交流,向前辈取取经,但一直没能通过GTalk联系上。

四、站点定位

新站最大的忌讳就是将目标制定的过于雄伟、气派,这样到最后大多的结局就是项目周期一拖再拖,最后一事无成。毕竟人的精力有限,每个人的关注点比较局限、单一;当然如果运营方有足够的财力和人力的话,那就得另当别论。接下来,将详细阐述项目的进度和一些细节。

1. 项目定位

APK Talk定位于小众,关注点十分专一,唯一专注于Android App,即APK;不会去关注iOS App,也不会去关注Android资讯;以团队博客的形式进行展示。还是举例说明:某一天,4J发现一款名为”Shazam”的APK不错,可推荐性较强;4J就会针对该APK,结合自己的使用经历撰写一篇测评文章:首先,讲述功能;接着,表述自己的使用经历;然后,测评感言等等。

接下来,就是该项目的发稿频率;计划每周3-5篇文章——过少,用户等待时间过长,会失去耐性;过多,文章质量欠佳,毕竟可推荐性强的APK为数不多。

然后,就是项目的人员配置,3-5人,每人每周发布一篇文章即可,不宜超过两篇;主要是考虑到人的精力有限,吸引人眼球的APK同样稀少;在数亿计的APK中淘出一个心仪的对象谈何容易。

2. 项目进度

项目所需的域名、主机、Twitter帐号、Weibo帐号都已经布局完毕,粘贴于下:

6月1日之前,我会独自将该项目开展起来;后期再拉人入伙,共同努力。

五、盈利模式

现阶段没去考虑盈利模式,只是觉得这项目可行性很高;当然,也不太可能有实际的盈利;但在其他方面可能会得到一些回报;例如,会结识一波具共同癖好的折腾者;可能会让撰稿者在“行内”有一定的知名度;4J将此理解成人脉,可以为以后做事提供便捷。

多说两句,人脉在成事中可说举足轻重;古语有云:天时、地利、人和。就拿iPad导航来说,该站点的idea和具体的实现应该可以算得上so easy,况且iPad也是个非常之小众的群体,但该站点能生存下来,并且健康状况还算OK——4J的猜测,具体每天多少访问量不得而知;其中起着决定性因素的就应该归结于我爱水煮鱼等人的人脉。举这个例子只是为了说明人脉的重要性,并没有其他神马更深层次的用意,不要想太多;如果该站站长觉得内容欠妥的话,可以联系4J来将之删除。

简而言之,可以通过之来为撰稿者积累人脉,这也是目前唯一可能的“获利”。

六、投资成本

投资成本主要分为两部分;第一,经济成本;第二,时间成本。初期的经济成本基本上可以忽略,域名续费和主机续费;因为初期的访问量不可能大,一般的共享主机都能吃消。反倒,时间成本占据一个可怕的比重,要发布可读性强的APK测评文章,撰稿者必须花足够多的时间去试用大量的APK,然后从中进行筛选,这方面的代价可是巨大的。

七、存在问题

最大的问题就是欠缺人手,目前有强烈意向的唯独我一人。尝试着去拉人入伙,可终究没能成功;只好在此广发英雄帖。目前还需2-4人,一定是要对该idea认可的,且具有强烈意愿的,千万别抱着试一试的态度;因为这是一个持久战,且没有任何物资回报。

如果有意愿者,可通过以下方式联系4J>> http://about.me/vincent4j

4JPlayer.Ver1.1-软件评测

一、欢迎界面

双击app图标之后,首先显示的是欢迎界面(如下左图所示),该界面上含有1张静态图片和1个动态点状进度条;当进度条循环完2轮之后,跳转到程序主界面中的Local Tab(如下右图所示)。
4JPlayer 欢迎界面 welcome  4JPlayer Play Tab 播放页签

二、初始化界面

上右图展示的是初始化时Local Tab的界面,该界面罗列出SDCard上的音频文件。接下来展示该状态下Play Tab(如下左图所示)和Remote Tab界面(如下右图所示);该状态下Remote Tab罗列出服务器上的音频文件。

4JPlayer 初始化PlayTab界面  4JPlayer 初始化RemoteTab界面

三、音乐播放界面

当用户点击Local Tab中某一首歌曲,例如选择标题一的右图中“Vincent.mp3”时;传来歌曲声音,该歌曲后多出一个播放按钮(如下左图所示);音乐播放一段时间之后,将界面切换到Play Tab,该界面中的数字计时器以1s为单位进行增加,进度条也随之向前挪动,中间的控制键显示的是暂停图标(如下右图所示)。
4JPlayer 播放时Local Tab界面  4JPlayer 播放时Play Tab界面

四、音乐暂停界面

继续播放一段时间后,点击Play Tab中的暂停按钮,音乐停止播放,数字计时器停止,滚动条停止,中间的控制键变更成播放图标(如下左图所示)。切换到Local Tab,此时“Vincent.mp3”后面显示为暂停图标(如下右图所示)。一段时间之后,点击“Vincent.mp3”,传出音乐,变成播放图标;切换到Play Tab,数字计时器和滚动条都动起来。
4JPlayer 暂停时Play Tab界面  4JPlayer 暂停时Local Tab界面

五、音乐切换界面

在标题四的基础之上,点击“下一首”,传来“Nobody”歌声,数字计时器和滚动条从零开始变化。此时切换到Local Tab,“Nobody.mp3”后面显示一个播放图标。

在上文的基础上,点击“Secret.mp3”,Secret音乐传出,该列后面出现播放图标。切换到Play Tab,此时展示着Secret的播放进度。

六、下载Mp3

切换到Remote Tab,点击“Hero.mp3”后面的下载图标,弹出下载提示(如下左图所示);一段时间之后浏览sdcard\mp3路径,发现“Hero.mp3”文件。
4JPlayer Download mp3 下载mp3  4JPlayer mp3 notify

七、Notification

当退出Activity时,Status Bar里出现个播放图标和文字“4JPlayer”,滑开,如上右图所示。

八、总结

1. 不足之处

  • 拖动播放功能未实现
  • 歌词同步功能未实现
  • 界面布局过于简单

2. 可行之处

  • 基本功能已实现
  • 通过该demo进一步学习Android SDK

九、APK和源码下载

http://7xl53s.com1.z0.glb.clouddn.com/download/2015/08/src/4jplayer-1.1.zip

4JPlayer.Ver1.1-需求分析与实现方法

一、需求来源

为完善上一版本(4JPlayer.Ver1.0)中存在的不足和增加一些新的功能,从而来进一步学习Android SDK的相关知识点。

二、功能概要和涉及知识点

  1. 欢迎界面显示图片和动态点状进度条,并停留一段时间之后跳转到主界面
    • Activity:调用finish()方法来结束当前Activity和startActivity()来开启一个新的Activity。
    • Handler:调用sendEmptyMessageDelayed()方法来延迟发送消息和重写handleMessage()方法来接收消息,并用switch-case进行分类处理。
    • Thread:定义一个线程类,该类extends Thread,通过传递Handler对象进行实例化,在run()方法中调用Handler对象的sendEmptyMessageDelayed()方法发送消息。
  2. 主界面由4个Tab组成
    • 组件:TabHost、TabWidget和FrameLayout组件。
    • 类:TabActivity、TabHost和TabHost.TabSpec类
  3. 其中2个Tab(Local Tab和Remote Tab)的内容由ListView来进行布局,并且ListItem中包含文字、图片和按钮(需监听onClick事件)
    • 组件:ListVew、TextView、ImageView和ImageButton组件。
    • 类:SimpleAdapter和通过extends BaseAdapter来自定义一个Adapter类,并监听ImageButton的onClick事件。
  4. 从服务器上获取mp3文件列表
    • 下载XML文件:涉及到URL、HttpURLConnection类
    • 解析XML文件:通过extends DefaultHandler来自定义一个Handler类,并重写startElement()、endElement()和characters()方法来进行标签解析;还涉及到SAXParserFactory和XMLReader类。
  5. 后台从服务器下载mp3文件,并写入到SDCard中去(给予下载开始提示信息)
    • 下载开始提示信息:通过Toast.makeText()实现。
    • 后台从服务器下载mp3文件:通过extends Service来自定义一个Service类,并重写onStartCommand()方法,在该方法中开启一个Thread来进行下载。当然,同样也涉及到URL、HttpURLConnection类。
    • 将mp3文件写入到SDCard中去:涉及到一些文件和IO流的操作。
  6. 罗列SDCard中mp3文件
    • 当前的实现方法:直接遍历sdcard\mp3目录下后缀为“.mp3”的文件。
    • 更完善的实现方法:通过getContentResolver().query()方法返回一个承载sdcard上所有音频文件记录的Cursor对象,音频文件信息存放在data\data\com.android.providers.media\databases\external-**.db中,对应的Uri为:MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;具体实现请参考>>http://blog.csdn.net/Android_Tutor/archive/2010/07/18/5743183.aspx(由于我编码完之后才知晓有这种更完备的方案,但改动太大,所以这个版本暂时就没做修改,下一版本可能会修改成这种方案。)
  7. 音乐播放控制:播放、暂停、上一首和下一首(可以循环播放)
    • Service:通过extends Service定义一个Serivce,用于后台进行歌曲播放控制。
    • MediaPlayer:reset()、prepare()、start()、pause()
  8. 播放音乐时有动态的SeekBar和动态的数字计时器
    • 组件:SeekBar
    • Thread:在用于播放音乐Service中,新建一个线程,该线程中通过Handler每隔1000ms发送一次消息。
    • Handler:在音乐播放控制Activity中,通过Handler的handleMessage()方法接收消息,并移动进度条和设置数字计时器。
  9. 当按后退键退出Activity之后,会在Status Bar显示播放信息
    • 涉及到Notification和PendingIntent类的相关方法。

三、尚欠缺的功能

  1. 拖动播放
  2. 多播放列表
  3. 歌词同步

四、源码

http://7xl53s.com1.z0.glb.clouddn.com/download/2015/08/src/4jplayer-1.1.zip