从上次更博到今天过了三天了,并不是因为偷懒了,而是这一课的内容对于基础较差的我来说信息量有点过于大了,隔了这么久才勉勉强强把它吃掉。那么废话不多说,直接进入今天的内容吧。
首先先看一下到目前为止的UI效果图:
除了下面多了一个“Welcome to Sina”的TextView,也没什么变化呀。哈哈,那你就错了,上次我们这两个按钮是点不动的,这次都有各自的功能了,先输入用户名和密码点下登录试试。
嗯,各位没有看错,就是将下面的TextView内容改变了。哈哈,可能有人要骂娘了,这不是小学生都会改的吗。嗯,的确,单纯的想改掉它很容易,不过我们这次的主题是主框架的搭建,这只是往框架里面填充了一点内容的结果,整个过程还是费了一点周折的,所以还请大家稍安勿喷。我们继续看UI
这是点旁边Register的按钮后的结果
这个Activity中,点击取消按钮会返回到上一个Activity中,还没有给注册按钮绑定监听器,不过我们的重点是搭建框架搭建框架搭建框架,框架搭建好了,往里面添加功能很容易的。好了,UI展示就到这里,下面开始讲解丑陋的UI背后的框架是怎么搭建的。
====================分割线====================
这就是整个主框架的工作流程了,多了几个名词,service、handle、线程。这些对于老鸟来说应该都听出老茧了吧,不过对于像我一样刚接触安卓的小白来说还是很陌生的,嗯,下面放两个链接来给小白涨涨姿势。service、handle这两个东西很重要,不懂他们我们的项目就没有办法继续进行下去了。嗯,先把这张图看上三分钟
http://www.360doc.com/content/14/0415/18/2793098_369238276.shtml
http://blog.sina.com.cn/s/blog_77c6324101016jp8.html
首先是我们的主Activit,由于在后期要对我们的UI进行刷新,所以我们Activity的定义方式也高大上了许多
public class LoginActivity extends AppCompatActivity implements IWeiboActivity
IWeiboActivity接口中定义了操作UI的方法
public interface IWeiboActivity { // 初始化数据 void init(); // 刷新UI void refresh(Object...params); }
LoginActivity的入口还是onCreate方法
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.login); loginButton = (Button)findViewById(R.id.LoginButton); registerButton = (Button)findViewById(R.id.RegisrerButton); Intent intent = new Intent(this, MainService.class); startService(intent); ButtonClickListener buttonClickListener = new ButtonClickListener(); loginButton.setOnClickListener(buttonClickListener); registerButton.setOnClickListener(buttonClickListener); MainService.addActivity(this); }
嗯,开始定义了几个Button,这些不是重点,后面再讲。重点是我红色高亮的代码。我们在这里使用Intent启动了一个Service,也就是流程图中的MainService。安卓中Activity只负责跟用户卖萌耍怪,背后的脏活累活都是Service在干。同样先看看MainService的定义方式吧
public class MainService extends Service implements Runnable
这里我们的MainService还继承了Runnable接口,但是并不能说Service是线程,它和线程半毛钱关系都没有。我上面分享的文章里面强调过了,这里在强调一遍,Service不是线程!Service不是线程!Service不是线程!好,我们继续。
同样的,Service的主入口还是onCreate方法
public void onCreate() { super.onCreate(); isRun = true; Thread thread = new Thread(this); thread.start(); }
由于我们的MainService继承了Runnable接口,所以这里可以作为参数传递给Thread构造函数。这里新开了一个线程,我们再看看run方法
public void run() { Task task = null; while (isRun) { if(!tasks.isEmpty()) { // 队列不为空则赋给task并从队列中移除 task = tasks.poll(); if(task != null) { // task不为空就执行task,以后会实现 doTask(task); } } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
这个线程的任务就是时刻检测当前有没有任务,有则去执行,没有就继续循环。其中tasks是一个任务队列,定义方式如下
private static Queue<Task> tasks = new LinkedList<Task>();
队列嘛,先进先出,数据结构都学过的,不懂的小伙伴们上网搜一搜就好了。<>这个尖括号中的东西叫做泛型,也就是这个队列中每一个元素的类型,下面是Task类的内容
public class Task { // 任务Id private int taskId; // 微博登录 public static final int WEIBO_LOGIN = 1; // 微博注册 public static final int WEIBO_REGISTER = 2; // 参数 private Map<String, Object>taskParams; public void setTaskId(int taskId) { this.taskId = taskId; } public void setTaskParams(Map<String, Object> taskParams) { this.taskParams = taskParams; } public int getTaskId() { return taskId; } public Map<String, Object> getTaskParams() { return taskParams; } public Task(int taskId, Map<String, Object>taskParams) { this.taskId = taskId; this.taskParams = taskParams; } }
别看这个Task内容多,其实就定义了一个任务ID"TaskId"和参数键值对taskParams还有一些静态常量,剩下的都是构造函数和set、get方法。
那我们检测任务的工作到这里也做完了,那么谁来给我们的Service分配任务呢?其实想想就能得出答案:app是为用户服务的,所以任务肯定也是人来下达,而app中和人交互的又是UI,所以给Service下达任务的肯定是UI啦。看我们的流程图也能得出任务是UI给出的这个结论。现在又返回到LoginActivity看看他是如何下达任务的。
在LoginActivity中定义了几个按钮,绑定了监听器,监听器内容如下
private class ButtonClickListener implements View.OnClickListener { @Override public void onClick(View v) { Task task = null; switch (v.getId()) { case R.id.LoginButton: task = new Task(Task.WEIBO_LOGIN, null); MainService.newTask(task); break; case R.id.RegisrerButton: task = new Task(Task.WEIBO_REGISTER, null); MainService.newTask(task); break; default: break; } } }
可以看到,在case中生成了一个Task对象,并使用MainService中的静态方法将它添加到了任务队列中
// 添加任务到任务队列中 public static void newTask(Task t) { tasks.add(t); }
嗯,这里因为我们的tasks成员也是静态的,否则是不能这么用的(静态方法中用到的成员变量必须都是静态的)
这边将任务添加到消息队列,还记得之前在MainService中开的那个线程吗?它检测到任务队列中有了新的任务,就会调用doTask方法去执行。
private void doTask(Task t) { Message msg = handler.obtainMessage(); msg.what = t.getTaskId(); switch (t.getTaskId()) { case Task.WEIBO_LOGIN: System.out.println("doTask >>>>>> 用户登录任务"); msg.obj = "正在登录...."; break; case Task.WEIBO_REGISTER: System.out.println("doTask >>>>>> 用户注册任务"); break; default: break; } handler.sendMessage(msg); }
在doTask中先用handler生成了一个消息实例,将Task的Id放入msg中,又根据TaskId对任务进行进一步细分,最后将消息发送给handler。注意,这个doTask是在thread线程中的,handler
则是在MainService中定义的
class Myhandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); IWeiboActivity activity = null; switch (msg.what) { case Task.WEIBO_LOGIN: // 更新UI activity = (IWeiboActivity)getActivityByName("LoginActivity"); activity.refresh(msg.what, msg.obj); break; // 跳转到注册页面 case Task.WEIBO_REGISTER: activity = (IWeiboActivity)getActivityByName("LoginActivity"); activity.refresh(msg.what); break; default: break; } } } Myhandler handler = new Myhandler();
那么问题来了,为什么明明可以在doTask中对UI进行改变,为什么又要费一番周折把它写到handleMessage函数中?答案就是:安卓不允许除了Activity中的主线程以外的线程来改变UI!还想表达的一个意思就是Service中的代码其实也是启动它的Activity的主线程中的。所以,我们不能够在doTask中刷新UI。
后面的工作就简单了,获取要改变的activity,并调用对应的refresh。这里得到的activity必须继承过IWeiboActivity接口,因为refresh等方法是在IWeiboActivity这个接口中定义的。里面用到了一个getActivityByName的方法
private Activity getActivityByName(String name) { if(!appActivities.isEmpty()) { for (Activity activity : appActivities) { if(activity != null) { if(activity.getClass().getName().indexOf(name) > 0) { System.out.println(activity.getClass().getName()); return activity; } } } } return null; }
appActivities是一个ArrayList类型的变量,也是一种组织数据的类型,定义如下
private static <Activity> appActivities = new ArrayList<Activity>();
和Queue同理,appActivities中的元素都是Activity类型的。直到这里,我们在MainService中要做的工作也就完成了。
最后还有点小尾巴,就是Activity中refresh函数的实现,这部分就很简单了,由于不同的任务传递的参数不同,所以这里使用了变参函数
public void refresh(Object... paramas) { int choose = Integer.parseInt(paramas[0].toString()); switch (choose) { case Task.WEIBO_LOGIN: Log.d("MainService", paramas[1].toString()); textView = (TextView)findViewById(R.id.textId); textView.setText(paramas[1].toString()); break; case Task.WEIBO_REGISTER: Intent intent = new Intent(this, RegisterActivity.class); startActivity(intent); default: break; } }
这就是整个主框架的设计。没错,想要单纯的改掉UI很容易,但是那只是我们模拟登录,如果真正实现登录就不会这么简单。并且有了框架我们再想加入别的功能就会特别容易,定义一个task实例添加到任务队列中,并实现具体的功能就好了,这样整个项目就很具有逻辑性,便于管理和优化。
这次的笔记就做到这里,总结一下get到的新技能:service、handler的使用,java中的foreach语句(轻喷,Java基础不扎实),还有返回上一级activity的finish方法。由于信息量对我来说是有一点大,这次讲的逻辑也挺混乱的,欢迎各位拍砖。
项目源码下载链接:http://files.cnblogs.com/files/51qianrushi/Iweibo.zip
这次的内容就这么多,下次再见。转载请通知本人或注明出处