标签归档:基础概念

微信小程序 开发教程 第六课

摘要: 完结篇来了!

大家好!博卡君原计划是能在国庆假期前把小程序的开发教程做完,给大家一套完整、系统的东西,不过由于最近小程序开发工具的拍照组件尚未完善,很多功能还不能顺利实现。我考虑了一下,觉得不如把拍照部分的一些代码展示出来,一来是给大家一个思路,二来也让大家看看目前开发工具存在的一些问题,咱们一起研究研究。

第九章:微信小程序拍照收纳开发以及删除名片等

还是先来看看我们今天的主题——拍照收纳。

拍照收纳分为:上传图片/识别名片、手动填写名片信息两个路径,这里只说下拍照识别,手动填写和前面的编辑名片是同样的页面,大家可以翻翻之前的教程。

这个布局很快,wxml 没多少内容。

拍照收纳的原理是收纳名片功能,步骤是打开微信的拍照 API,同时支持选择本地图片 wx.chooseImage 接口。

取到图片路径需上传图片文件到后台服务器,这里参照文档打印三个回调。

但是我发现在开发者工具测试的时候,没有任何打印信息,后台同样也没有接到我上传过去的图片。

选择一张图片,点击打开后。控制台的 console 没有成功或者失败的回调。

由于尚处内测版本,暂时还不确定是开发者工具上的 bug 还是 API 接口问题,总之这里影响了小程序开发,我已经写邮件给微信做了反馈。

如果图片上传成功,后台取到图片会去调一个识别信息操作,最后把识别到的信息传给我们前台进行渲染,最后把信息保存到名片夹里面,收纳名片流程才算走通。

再补充下名片夹页面的名片删除吧:

每个名片夹后面都带个删除功能,这个功能是通过左滑出现。

点击删除,出现是否确定删除弹框(使用自带的模态框组件)。

确定与取消事件。

由于这里名片分为,线上收纳与线下收纳。故而多了个 if 判断,走的删除 request 不是一个接口,其他都相差不多。这里最重要的是要获取到 cardId,才能知道被删除的是哪张名片。

名片的父元素绑定的全部事件,以及需要用到的一些自定义参数。Id 用在左滑上data-card_id 用在页面跳转与删除上,data-card_type 用在判断线上与线下名片。

cardId 可以在 bindtouchstart 上取到,因为左滑事件是发生 bindtouchstart 事件上。

当然后面需要用到的值还是先定义个 var 存起来。

删除完之后还需重新刷新下首页。

并且提升下用户体验,告诉用户名片删除成功。



最后一提,当列表很多时,我们需要确定点击到的是那个信息。

上面的信息都是 block 循环出来的,bindtap 点击事件,然后就是 id=”{{pms.type}}”,重点就是这个 id 来判断被点击的对象。

操作数据,我们一定要先熟悉好数据的结构,故而在开发前和后台一定要约定好数据结构,熟悉数据结构后,其实开发起来就如庖丁解牛,非常快速(大家开发了小程序后,有没有对 zepoto 与 jQuery 产生反感?哈哈!)

好了,关于拍照组件的相关内容就写到这里吧!这一章内容就算是博卡君教程的完结篇了。虽然写的内容中包含小程序开发工具的问题,不过我还是推荐大家都尝试调用一下拍照相关功能,看看这些问题在你的机器上会不会出现。按照微信的官方文档,我尝试给 weixin_developer@qq.com 那个邮箱发了邮件报告 bug。没想到很快收到了官方回复,确认了这点。希望尽快得到更新版,一起期待吧😄

参考阅读
微信小程序 开发教程 第一课
微信小程序 开发教程 第二课
微信小程序 开发教程 第三课
微信小程序 开发教程 第四课
微信小程序 开发教程 第五课

分布式系统接口幂等性

1.幂等性定义

1.1 数学定义

在数学里,幂等有两种主要的定义:

  • 在某二元运算下,幂等元素是指被自己重复运算(或对于函数是为复合)的结果等于它自己的元素。例如,乘法下唯一两个幂等实数为0和1。
    即 s *s = s
  • 某一元运算为幂等的时,其作用在任一元素两次后会和其作用一次的结果相同。例如,高斯符号便是幂等的,即f(f(x)) = f(x)。

1.2 HTTP规范的定义

在HTTP/1.1规范中幂等性的定义是:

A request method is considered “idempotent” if the intended effect onthe server of multiple identical requests with that method is the same as the effect for a single such request. Of the request methods defined by this specification, PUT, DELETE, and safe request methods are idempotent.

HTTP的幂等性指的是一次和多次请求某一个资源应该具有相同的副作用。如通过PUT接口将数据的Status置为1,无论是第一次执行还是多次执行,获取到的结果应该是相同的,即执行完成之后Status =1。

2. 何种接口提供幂等性

2.1 HTTP支持幂等性的接口

在HTTP规范中定义GET,PUT和DELETE方法应该具有幂等性。

  • GET方法

The GET method requests transfer of a current selected representatiofor the target resourceGET is the primary mechanism of information retrieval and the focus of almost all performance optimizations. Hence, when people speak of retrieving some identifiable information via HTTP, they are generally referring to making a GET request.

GET方法是向服务器查询,不会对系统产生副作用,具有幂等性(不代表每次请求都是相同的结果)

  • PUT方法

T he PUT method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message payload.

也就是说PUT方法首先判断系统中是否有相关的记录,如果有记录则更新该记录,如果没有则新增记录。

  • DELETE 方法

The DELETE method requests that the origin server remove the association between the target resource and its current functionality. In effect, this method is similar to the rm command in UNIX: it expresses a deletion operation on the URI mapping of the origin server rather than an expectation that the previously associated information be deleted.

DELETE方法是删除服务器上的相关记录。

2.2 实际业务

现在简化为这样一个系统,用户购买商品的订单系统与支付系统;订单系统负责记录用户的购买记录已经订单的流转状态(orderStatus),支付系统用于付款,提供

boolean pay(int accountid,BigDecimal amount) //用于付款,扣除用户的

接口,订单系统与支付系统通过分布式网络交互。

这种情况下,支付系统已经扣款,但是订单系统因为网络原因,没有获取到确切的结果,因此订单系统需要重试。
由上图可见,支付系统并没有做到接口的幂等性,订单系统第一次调用和第二次调用,用户分别被扣了两次钱,不符合幂等性原则(同一个订单,无论是调用了多少次,用户都只会扣款一次)。
如果需要支持幂等性,付款接口需要修改为以下接口:

boolean pay(int orderId,int accountId,BigDecimal amount)

通过orderId来标定订单的唯一性,付款系统只要检测到订单已经支付过,则第二次调用不会扣款而会直接返回结果:

在不同的业务中不同接口需要有不同的幂等性,特别是在分布式系统中,因为网络原因而未能得到确定的结果,往往需要支持接口幂等性。

3.分布式系统接口幂等性

随着分布式系统及微服务的普及,因为网络原因而导致调用系统未能获取到确切的结果从而导致重试,这就需要被调用系统具有幂等性。
例如上文所阐述的支付系统,针对同一个订单保证支付的幂等性,一旦订单的支付状态确定之后,以后的操作都会返回相同的结果,对用户的扣款也只会有一次。这种接口的幂等性,简化到数据层面的操作:

update userAmount set amount = amount - 'value' ,paystatus = 'paid' where orderId= 'orderid' and paystatus = 'unpay'

其中value是用户要减少的订单,paystatus代表支付状态,paid代表已经支付,unpay代表未支付,orderid是订单号。
在上文中提到的订单系统,订单具有自己的状态(orderStatus),订单状态存在一定的流转。订单首先有提交(0),付款中(1),付款成功(2),付款失败(3),简化之后其流转路径如图:

当orderStatus = 1 时,其前置状态只能是0,也就是说将orderStatus由0->1 是需要幂等性的

update Order set orderStatus = 1 where OrderId = 'orderid' and orderStatus = 0

当orderStatus 处于0,1两种状态时,对订单执行0->1 的状态流转操作应该是具有幂等性的。
这时候需要在执行update操作之前检测orderStatus是否已经=1,如果已经=1则直接返回true即可。

但是如果此时orderStatus = 2,再进行订单状态0->1 时操作就无法成功,但是幂等性是针对同一个请求的,也就是针对同一个requestid保持幂等。

这时候再执行

update Order set orderStatus = 1 where OrderId = 'orderid' and orderStatus = 0

接口会返回失败,系统没有产生修改,如果再发一次,requestid是相同的,对系统同样没有产生修改。

References

幂等
HTTP幂等性概念和应用
What Is Idempotent in REST?
REST之中的幂等指的是什么?
Hypertext Transfer Protocol (HTTP/1.1)

 

利用Android的 IntentService完成后台异步任务

初学android比较纠结的一点就是service和activity的交互,在service中不能进行耗时的操作,否则会阻塞UI进程,我们可以在service中new一个Thread出来进行耗时的操作,但毕竟有点麻烦。所以android还提供了IntentService类来帮助解决这个问题。

IntentService是继承至Service子类,用于处理异步的请求,你可以通过startService(Intent)来发送你需要的请求。在IntentService中使用了一个ServiceHandler(继承至Handler)来处理接收到的请求。对于每个异步的startService请求,IntentService会处理完成一个之后再处理第二个。每一个请求都会在一个单独的worker thread中处理,不会阻塞应用程序的主线程。

IntentService的特点如下:

(1)它创建了一个独立的工作线程来处理所有的通过onStartCommand()传递给服务的intents。

(2)创建了一个工作队列,来逐个发送intent给onHandleIntent()。

(3)不需要主动调用stopSelft()来结束服务。因为,在所有的intent被处理完后,系统会自动关闭服务。

(4)默认实现的onBind()返回null

(5)默认实现的onStartCommand()的目的是将intent插入到工作队列中。

IntentService是一个基于消息的服务,每次启动该服务并不是马上处理你的工作,而是首先会创建对应的Looper,Handler并且在MessageQueue中添加的附带客户Intent的Message对象,当Looper发现有Message的时候接着得到Intent对象通过在onHandleIntent((Intent)msg.obj)中调用你的处理程序.处理完后即会停止自己的服务.意思是Intent的生命周期跟你的处理的任务是一致的.所以这个类用下载任务中非常好,下载任务结束后服务自身就会结束退出.

使用方法也十分简单,所需要做的就是实现 onHandleIntent() 方法,在该方法内实现你想进行的操作。另外,继承IntentService时,你必须提供一个无参构造函数,且在该构造函数内,你需要调用父类的构造函数

启动服务:

findViewById(R.id.intentService).setOnClickListener(

new OnClickListener() {

@Override

public void onClick(View v) {

startService( new Intent(Main.this,

IntentServiceExm. class));

}

});

IntentService服务:

public class IntentServiceExm extends IntentService {

public IntentServiceExm() {

super(“IntentServiceExm” );

}

@Override

protected void onHandleIntent(Intent intent) {

try {

// 这里模拟耗时操作,睡眠5秒

Thread.sleep(5000);

Log. i(“IntentService”, “in IntentService” );

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

在此特别需要注意的是IntentService是线性处理请求的。如果有多个Intent请求执行,则会被放在工作队列中,依次等待,顺序执行。所以若是想在Service中让多个线程并发的话,就得另想法子,比如本文开头所说的在service中新建多个Thread来解决。

Android 中 Socket的简单用法

Socket通常也称做”套接字“,用于描述IP地址和端口,废话不多说,它就是网络通信过程中端点的抽象表示。值得一提的是,Java在包java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用起来很方便!

下面将首先创建一个SocketServer的类作为服务端如下,该服务端实现了多线程机制,可以在特定端口处监听多个客户请求,一旦有客户请求,Server总是会创建一个服务纯种来服务新来的客户,而自己继续监听。程序中accept()是一个阻塞函数,所谓阻塞性方法就是说该方法被调用后将等待客户的请求,直到有一个客户启动并请求连接到相同的端口,然后accept()返回一个对应于客户的Socket。这时,客户方和服务方都建立了用于通信的Socket,接下来就是由各个Socket分别打开各自的输入、输出流。

SocketServer类,服务器实现:

package HA.Socket;

import java.io.*;
import java.net.*;

 public class SocketServer {
    
    ServerSocket sever;
    
    public SocketServer(int port){
        try{
            sever = new ServerSocket(port);
        }catch(IOException e){
            e.printStackTrace();
        }
    }
    
    public void beginListen(){
        while(true){
            try{
                final Socket socket = sever.accept();
                
                new Thread(new Runnable(){
                    public void run(){
                        BufferedReader in;
                        try{
                            in = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
                            PrintWriter out = new PrintWriter(socket.getOutputStream());
                            while (!socket.isClosed()){
                                String str;
                                str = in.readLine();
                                out.println("Hello!world!! " + str);
                                out.flush();
                                if (str == null || str.equals("end"))
                                    break;
                                System.out.println(str);
                            }
                            socket.close();
                        }catch(IOException e){
                            e.printStackTrace();
                        }
                    }
                }).start();
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
}

SocketClient类,客户端实现:

package HA.Socket;

import java.io.*;
import java.net.*;

 public class SocketClient {
    static Socket client;
    
    public SocketClient(String site, int port){
        try{
            client = new Socket(site,port);
            System.out.println("Client is created! site:"+site+" port:"+port);
        }catch (UnknownHostException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    
    public String sendMsg(String msg){
        try{
            BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
            PrintWriter out = new PrintWriter(client.getOutputStream());
            out.println(msg);
            out.flush();
            return in.readLine();
        }catch(IOException e){
            e.printStackTrace();
        }
        return "";
    }
    public void closeSocket(){
        try{
            client.close();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws Exception{
        
    }

}

接下来就是来测试Socket通信了!
先运行TestSocketServer类,打开服务端,在12345端口处监听!

package HA.Socket;

 public class TestSocketServer {
    
    public static void main(String[] argvs){
        SocketServer server = new SocketServer(12345);
        server.beginListen();
    }
}

再运行TestSocketClient类:

package HA.Socket;

 public class TestSocketClient {

    public static void main(String[] args){
        
        SocketClient client = new SocketClient("127.0.0.1",12345);
        System.out.println(client.sendMsg("nimei1"));
        client.closeSocket();
        
        SocketClient client1 = new SocketClient("127.0.0.1",12345);
        System.out.println(client1.sendMsg("nimei1111"));
        client1.closeSocket();
        
        SocketClient client11 = new SocketClient("127.0.0.1",12345);
        System.out.println(client11.sendMsg("nimei11111111"));
        client11.closeSocket();
        
        SocketClient client111 = new SocketClient("127.0.0.1",12345);
        System.out.println(client111.sendMsg("nimei11111111111111111"));
        client111.closeSocket();
        
    }
}

输出结果如下:

服务端:

Client is created! site:127.0.0.1 port:12345
Hello!world!! nimei1
Client is created! site:127.0.0.1 port:12345
Hello!world!! nimei1111
Client is created! site:127.0.0.1 port:12345
Hello!world!! nimei11111111
Client is created! site:127.0.0.1 port:12345
Hello!world!! nimei11111111111111111

客户端:

nimei1
nimei1111
nimei11111111
nimei11111111111111111

Android Service生命周期及用法!

Service概念及用途:

Android中的服务,它与Activity不同,它是不能与用户交互的,不能自己启动的,运行在后台的程序,如果我们退出应用时,Service进程并没有结束,它仍然在后台运行,那
我们什么时候会用到Service呢?比如我们播放音乐的时候,有可能想边听音乐边干些其他事情,当我们退出播放音乐的应用,如果不用Service,我
们就听不到歌了,所以这时候就得用到Service了,又比如当我们一个应用的数据是通过网络获取的,不同时间(一段时间)的数据是不同的这时候我们可以
用Service在后台定时更新,而不用每打开应用的时候在去获取。

Service生命周期
:

Android Service的生命周期并不像Activity那么复杂,它只继承了onCreate(),onStart(),onDestroy()三个方法,当我们第一次启动Service时,先后调用了onCreate(),onStart()这两个方法,当停止Service时,则执行onDestroy()方法,这里需要注意的是,如果Service已经启动了,当我们再次启动Service时,不会在执行onCreate()方法,而是直接执行onStart()方法,具体的可以看下面的实例。

Service与Activity通信:
Service后端的数据最终还是要呈现在前端Activity之上的,因为启动Service时,系统会重新开启一个新的进程,这就涉及到不同进程间通信的问题了(AIDL)这一节我不作过多描述,当我们想获取启动的Service实例时,我们可以用到bindService和onBindService方法,它们分别执行了Service中IBinder()和onUnbind()方法。

 

为了让大家 更容易理解,我写了一个简单的Demo,大家可以模仿着我,一步一步的来。

 

第一步:新建一个Android工程,我这里命名为ServiceDemo.

第二步:修改main.xml代码,我这里增加了四个按钮,代码如下:

 

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:orientation=“vertical”
  4.     android:layout_width=“fill_parent”
  5.     android:layout_height=“fill_parent”
  6.     >
  7.     <TextView
  8.         android:id=“@+id/text”
  9.         android:layout_width=“fill_parent”
  10.         android:layout_height=“wrap_content”
  11.         android:text=“@string/hello”
  12.         />
  13.     <Button
  14.         android:id=“@+id/startservice”
  15.         android:layout_width=“fill_parent”
  16.         android:layout_height=“wrap_content”
  17.         android:text=“startService”
  18.     />
  19.     <Button
  20.         android:id=“@+id/stopservice”
  21.         android:layout_width=“fill_parent”
  22.         android:layout_height=“wrap_content”
  23.         android:text=“stopService”
  24.     />
  25.     <Button
  26.         android:id=“@+id/bindservice”
  27.         android:layout_width=“fill_parent”
  28.         android:layout_height=“wrap_content”
  29.         android:text=“bindService”
  30.     />
  31.     <Button
  32.         android:id=“@+id/unbindservice”
  33.         android:layout_width=“fill_parent”
  34.         android:layout_height=“wrap_content”
  35.         android:text=“unbindService”
  36.     />
  37. </LinearLayout>

第三步:新建一个Service,命名为MyService.java代码如下:

 

  1. package com.tutor.servicedemo;
  2. import android.app.Service;
  3. import android.content.Intent;
  4. import android.os.Binder;
  5. import android.os.IBinder;
  6. import android.text.format.Time;
  7. import android.util.Log;
  8. public class MyService extends Service {
  9.     //定义个一个Tag标签
  10.     private static final String TAG = “MyService”;
  11.     //这里定义吧一个Binder类,用在onBind()有方法里,这样Activity那边可以获取到
  12.     private MyBinder mBinder = new MyBinder();
  13.     @Override
  14.     public IBinder onBind(Intent intent) {
  15.         Log.e(TAG, “start IBinder~~~”);
  16.         return mBinder;
  17.     }
  18.     @Override
  19.     public void onCreate() {
  20.         Log.e(TAG, “start onCreate~~~”);
  21.         super.onCreate();
  22.     }
  23.     @Override
  24.     public void onStart(Intent intent, int startId) {
  25.         Log.e(TAG, “start onStart~~~”);
  26.         super.onStart(intent, startId);
  27.     }
  28.     @Override
  29.     public void onDestroy() {
  30.         Log.e(TAG, “start onDestroy~~~”);
  31.         super.onDestroy();
  32.     }
  33.     @Override
  34.     public boolean onUnbind(Intent intent) {
  35.         Log.e(TAG, “start onUnbind~~~”);
  36.         return super.onUnbind(intent);
  37.     }
  38.     //这里我写了一个获取当前时间的函数,不过没有格式化就先这么着吧
  39.     public String getSystemTime(){
  40.         Time t = new Time();
  41.         t.setToNow();
  42.         return t.toString();
  43.     }
  44.     public class MyBinder extends Binder{
  45.         MyService getService()
  46.         {
  47.             return MyService.this;
  48.         }
  49.     }
  50. }

第四步:修改ServiceDemo.java,代码如下:

 

  1. package com.tutor.servicedemo;
  2. import android.app.Activity;
  3. import android.content.ComponentName;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.content.ServiceConnection;
  7. import android.os.Bundle;
  8. import android.os.IBinder;
  9. import android.view.View;
  10. import android.view.View.OnClickListener;
  11. import android.widget.Button;
  12. import android.widget.TextView;
  13. public class ServiceDemo extends Activity implements OnClickListener{
  14.     private MyService  mMyService;
  15.     private TextView mTextView;
  16.     private Button startServiceButton;
  17.     private Button stopServiceButton;
  18.     private Button bindServiceButton;
  19.     private Button unbindServiceButton;
  20.     private Context mContext;
  21.     //这里需要用到ServiceConnection在Context.bindService和context.unBindService()里用到
  22.     private ServiceConnection mServiceConnection = new ServiceConnection() {
  23.         //当我bindService时,让TextView显示MyService里getSystemTime()方法的返回值 
  24.         public void onServiceConnected(ComponentName name, IBinder service) {
  25.             // TODO Auto-generated method stub
  26.             mMyService = ((MyService.MyBinder)service).getService();
  27.             mTextView.setText(“I am frome Service :” + mMyService.getSystemTime());
  28.         }
  29.         public void onServiceDisconnected(ComponentName name) {
  30.             // TODO Auto-generated method stub
  31.         }
  32.     };
  33.     public void onCreate(Bundle savedInstanceState) {
  34.         super.onCreate(savedInstanceState);
  35.         setContentView(R.layout.main);
  36.         setupViews();
  37.     }
  38.     public void setupViews(){
  39.         mContext = ServiceDemo.this;
  40.         mTextView = (TextView)findViewById(R.id.text);
  41.         startServiceButton = (Button)findViewById(R.id.startservice);
  42.         stopServiceButton = (Button)findViewById(R.id.stopservice);
  43.         bindServiceButton = (Button)findViewById(R.id.bindservice);
  44.         unbindServiceButton = (Button)findViewById(R.id.unbindservice);
  45.         startServiceButton.setOnClickListener(this);
  46.         stopServiceButton.setOnClickListener(this);
  47.         bindServiceButton.setOnClickListener(this);
  48.         unbindServiceButton.setOnClickListener(this);
  49.     }
  50.     public void onClick(View v) {
  51.         // TODO Auto-generated method stub
  52.         if(v == startServiceButton){
  53.             Intent i  = new Intent();
  54.             i.setClass(ServiceDemo.this, MyService.class);
  55.             mContext.startService(i);
  56.         }else if(v == stopServiceButton){
  57.             Intent i  = new Intent();
  58.             i.setClass(ServiceDemo.this, MyService.class);
  59.             mContext.stopService(i);
  60.         }else if(v == bindServiceButton){
  61.             Intent i  = new Intent();
  62.             i.setClass(ServiceDemo.this, MyService.class);
  63.             mContext.bindService(i, mServiceConnection, BIND_AUTO_CREATE);
  64.         }else{
  65.             mContext.unbindService(mServiceConnection);
  66.         }
  67.     }
  68. }

第五步:修改AndroidManifest.xml代码(将我们新建的MyService注册进去如下代码第14行:)

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <manifest xmlns:android=“http://schemas.android.com/apk/res/android”
  3.       package=“com.tutor.servicedemo”
  4.       android:versionCode=“1”
  5.       android:versionName=“1.0”>
  6.     <application android:icon=“@drawable/icon” android:label=“@string/app_name”>
  7.         <activity android:name=“.ServiceDemo”
  8.                   android:label=“@string/app_name”>
  9.             <intent-filter>
  10.                 <action android:name=“android.intent.action.MAIN” />
  11.                 <category android:name=“android.intent.category.LAUNCHER” />
  12.             </intent-filter>
  13.         </activity>
  14.         <service android:name=“.MyService” android:exported=“true”></service>
  15.     </application>
  16.     <uses-sdk android:minSdkVersion=“7” />
  17. </manifest>

 

第六步:执行上述工程,效果图如下:

点击startServie按钮时先后执行了Service中onCreate()->onStart()这两个方法,打开Logcat视窗效果如下图:

我们这时可以按HOME键进入Settings(设置)->Applications(应用)->Running Services(正在运行的服务)看一下我们新启动了一个服务,效果如下:

点击stopService按钮时,Service则执行了onDestroy()方法,效果图如下所示:

这时候我们再次点击startService按钮,然后点击bindService按钮(通常bindService都是bind已经启动的Service),我们看一下Service执行了IBinder()方法,以及TextView的值也有所变化了,如下两张图所示:

 

最后点击unbindService按钮,则Service执行了onUnbind()方法,如下图所示:

 

Android中定时执行任务的3种实现方法

在Android开发中,定时执行任务的4种实现方法:

  • 采用Handler与线程的sleep(long)方法(不建议使用,java的实现方式)
  • 采用Handler的postDelayed(Runnable, long)方法(最简单的android实现)
  • 采用Handler与timer及TimerTask结合的方法(比较多的任务时建议使用)
  • AlarmManager与PendingIntent

下面逐一介绍:

一、采用Handle与线程的sleep(long)方法

Handler主要用来处理接受到的消息。这只是最主要的方法,当然Handler里还有其他的方法供实现,有兴趣的可以去查API,这里不过多解释。

  1. 定义一个Handler类,用于处理接受到的Message。
Handler handler = new Handler() {  
    public void handleMessage(Message msg) {  
        // 要做的事情  
        super.handleMessage(msg);  
    }  
};  

2.新建一个实现Runnable接口的线程类,如下:

public class MyThread implements Runnable {  
    @Override  
    public void run() {  
        // TODO Auto-generated method stub  
        while (true) {  
            try {  
                Thread.sleep(10000);// 线程暂停10秒,单位毫秒  
                Message message = new Message();  
                message.what = 1;  
                handler.sendMessage(message);// 发送消息  
            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
        }  
    }  
}  

3.在需要启动线程的地方加入下面语句:

new Thread(new MyThread()).start();  

4.启动线程后,线程每10s发送一次消息。

二、采用Handler的postDelayed(Runnable, long)方法

这个实现比较简单一些。

  1. 定义一个Handler类
Handler handler=new Handler();  
Runnable runnable=new Runnable() {  
    @Override  
    public void run() {  
        // TODO Auto-generated method stub  
        //要做的事情  
        handler.postDelayed(this, 2000);  
    }  
};  

2.启动计时器

handler.postDelayed(runnable, 2000);//每两秒执行一次runnable.  

3.停止计时器

handler.removeCallbacks(runnable);   

三、采用Handler与timer及TimerTask结合的方法

1.定义定时器、定时器任务及Handler句柄

private final Timer timer = new Timer();  
private TimerTask task;  
Handler handler = new Handler() {  
    @Override  
    public void handleMessage(Message msg) {  
        // TODO Auto-generated method stub  
        // 要做的事情  
        super.handleMessage(msg);  
    }  
};  

2.初始化计时器任务

task = new TimerTask() {  
    @Override  
    public void run() {  
        // TODO Auto-generated method stub  
        Message message = new Message();  
        message.what = 1;  
        handler.sendMessage(message);  
    }  
};   

3.启动定时器

timer.schedule(task, 2000, 2000);   

4.停止计时器

timer.cancel();  

四、AlarmManager与PendingIntent

AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = createAlarmIntent();//启动自身服务的Intent
long nextTime = SystemClock.elapsedRealtime() + NEXT_DELAY;
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, pi);

简要说一下上面三步提到的一些内容:

  1. 定时器任务(TimerTask)顾名思义,就是说当定时器到达指定的时间时要做的工作,这里是想Handler发送一个消息,由Handler类进行处理。
  2. java.util.Timer.schedule(TimerTask task, long delay):这个方法是说,dalay/1000秒后执行task.只执行一次。
    java.util.Timer.schedule(TimerTask task, long delay, long period):这个方法是说,delay/1000秒后执行task,然后进过period/1000秒再次执行task,这个用于循环任务,执行无数次,当然,你可以用timer.cancel();取消计时器的执行。

每一个Timer仅对应唯一一个线程。
Timer不保证任务执行的十分精确。
Timer类的线程安全的。

 

Android – 线程同步

什么是线程同步?

    当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。

实现同步机制有两个方法:
1、同步代码块:
synchronized(同一个数据){} 同一个数据:就是N条线程同时访问一个数据。

 

2、同步方法:
public synchronized 数据返回类型方法名(){}

 

通过使用同步方法,可非常方便的将某类变成线程安全的类,具有如下特征:
1
,该类的对象可以被多个线程安全的访问。
2
,每个线程调用该对象的任意方法之后,都将得到正确的结果。
3
,每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。
注:synchronized关键字可以修饰方法,也可以修饰代码块,但不能修饰构造器,属性等

 

※不要对线程安全类的所有方法都进行同步,只对那些会改变共享资源方法的进行同步。

线程通讯:

    当使用synchronized 来修饰某个共享资源时(分同步代码块和同步方法两种情况),当某个线程获得共享资源的锁后就可以执行相应的代码段,直到该线程运行完该代码段后才释放对该共享资源的锁,让其他线程有机会执行对该共享资源的修改。当某个线程占有某个共享资源的锁时,如果另外一个线程也想获得这把锁运行就需要使用wait()
和notify()/notifyAll()方法来进行线程通讯了。
Java.lang.object 里的三个方法wait() notify()  notifyAll()


wait()

导致当前线程等待,直到其他线程调用同步监视器的notify方法或notifyAll方法来唤醒该线程。

wait(mills)

都是等待指定时间后自动苏醒,调用wait方法的当前线程会释放该同步监视器的锁定,可以不用notify或notifyAll方法把它唤醒。

notify()
唤醒在同步监视器上等待的单个线程,如果所有线程都在同步监视器上等待,则会选择唤醒其中一个线程,选择是任意性的,只有当前线程放弃对该同步监视器的锁定后,也就是使用wait方法后,才可以执行被唤醒的线程。

notifyAll()

唤醒在同步监视器上等待的所有的线程。只用当前线程放弃对该同步监视器的锁定后,也就是使用wait方法后,才可以执行被唤醒的线程。

 

 

==================================================================================================

 

 

原子操作:根据Java规范,对于基本类型的赋值或者返回值操作,是原子操作。但这里的基本数据类型不包括longdouble, 因为JVM看到的基本存储单位是32位,而long double都要用64位来表示。所以无法在一个时钟周期内完成。 

自增操作(++)不是原子操作,因为它涉及到一次读和一次写。 

原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。 

同步代码块:为了保证每个线程能够正常执行原子操作,Java引入了同步机制,具体的做法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。 

同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。 

当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的命运。 
1
 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。 
2
 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。 (一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁 如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中 


线程同步的特征: 
1
 如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制) 
2
 每个对象都有唯一的同步锁 
3
 在静态方法前面可以使用synchronized修饰符。 
4
 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。 
5
 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。 

释放对象的锁: 
1
 执行完同步代码块就会释放对象的锁 
2
 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放 
3
 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池。 

死锁:

线程1独占(锁定)资源A,等待获得资源B后,才能继续执行
同时
线程2独占(锁定)资源B,等待获得资源A后,才能继续执行
这样就会发生死锁,程序无法正常执行

 

如何避免死锁 
一个通用的经验法则是:当几个线程都要访问共享资源ABC 时,保证每个线程都按照同样的顺序去访问他们。

 

 

==================================================================================================

注意:

1、线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。

2、只有共享资源的读写访问才需要同步。如果不是共享资源,那么就根本没有同步的必要。
3、只有“变量”才需要同步访问。如果共享的资源是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修改共享资源,这样的情况下,线程之间就需要同步。
4、多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。

同步锁:

    我们可以给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权利访问该共享资源。

同步锁不是加在共享资源上,而是加在访问共享资源的代码段上。

访问同一份共享资源的不同代码段,应该加上同一个同步锁;如果加的是不同的同步锁,那么根本就起不到同步的作用,没有任何意义。
这就是说,
同步锁本身也一定是多个线程之间的共享对象。

public static final Object lock1 = new Object();

… f1() {

synchronized(lock1){ // lock1 是公用同步锁
  // 代码段 A
// 访问共享资源 resource1
// 需要同步
}

你不一定要把同步锁声明为static或者public,但是你一定要保证相关的同步代码之间,一定要使用同一个同步锁。
任何一个Object Reference都可以作为同步锁。我们可以把Object Reference理解为对象在内存分配系统中的内存地址。因此,要保证同步代码段之间使用的是同一个同步锁,我们就要保证这些同步代码段的synchronized关键字使用的是同一个Object Reference,同一个内存地址。这也是为什么我在前面的代码中声明lock1的时候,使用了final关键字,这就是为了保证lock1的Object
Reference在整个系统运行过程中都保持不变。

竞争同步锁失败的线程进入的是该同步锁的就绪(Ready)队列,而不是后面要讲述的待召队列(Waiting Queue,也可以翻译为等待队列)。就绪队列里面的线程总是时刻准备着竞争同步锁,时刻准备着运行。而待召队列里面的线程则只能一直等待,直到等到某个信号的通知之后,才能够转移到就绪队列中,准备运行。

 

同步粒度
在Java语言里面,我们可以直接把synchronized关键字直接加在函数的定义上。
比如。
… synchronized … f1() {
 // f1 代码段
}

这段代码就等价于
… f1() {
 synchronized(this){ // 同步锁就是对象本身
   // f1 代码段
 }
}

同样的原则适用于静态(static)函数
比如。
… static synchronized … f1() {
 // f1 代码段
}

这段代码就等价于
…static … f1() {
 synchronized(Class.forName(…)){ // 同步锁是类定义本身
   // f1 代码段
  }
}

但是,我们要尽量避免这种直接把synchronized加在函数定义上的偷懒做法。因为我们要控制同步粒度。同步的代码段越小越好。synchronized控制的范围越小越好。

 


 

===============================附:JAVA开发实战经典 源码===============================

package com.synchronization;
class Info{ // 定义信息类
 private String name = “李兴华”;  // 定义name属性
 private String content = “JAVA讲师”  ;  // 定义content属性
 private boolean flag = false ; // 设置标志位
 
public synchronized void set(String name,String content){
 if(!flag){
  try{
   super.wait() ;
  }catch(InterruptedException e){
   e.printStackTrace() ;
  }
 }
 this.setName(name) ; // 设置名称
  try{
  Thread.sleep(300) ;
 }catch(InterruptedException e){
  e.printStackTrace() ;
 }
 this.setContent(content) ; // 设置内容
  flag  = false ; // 改变标志位,表示可以取走
  super.notify() ;
}

public synchronized void get(){
 if(flag){
  try{
   super.wait() ;
  }catch(InterruptedException e){
   e.printStackTrace() ;
  }
 }
 try{
  Thread.sleep(300) ;
 }catch(InterruptedException e){
  e.printStackTrace() ;
 }
 System.out.println(this.getName() +
  ” –> ” + this.getContent()) ;
 flag  = true ; // 改变标志位,表示可以生产
  super.notify() ;
}
public void setName(String name){
 this.name = name ;
}
public void setContent(String content){
 this.content = content ;
}
public String getName(){
 return this.name ;
}
public String getContent(){
 return this.content ;
}
}

class Producer implements Runnable{ // 通过Runnable实现多线程
 private Info info = null ;  // 保存Info引用
 public Producer(Info info){
 this.info = info ;
}
public void run(){
 boolean flag = false ; // 定义标记位
  for(int i=0;i<50;i++){
  if(flag){
   this.info.set(“李兴华”,”JAVA讲师”) ; // 设置名称
    flag = false ;
  }else{
   this.info.set(“mldn”,”
www.mldnjava.cn“)
// 设置名称
    flag = true ;
  }
 }
}
}

class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
 this.info = info ;
}
public void run(){
 for(int i=0;i<50;i++){
  this.info.get() ;
 }
}
}

public class ThreadCaseDemo{
public static void main(String args[]){
 Info info = new Info(); // 实例化Info对象
  Producer pro = new Producer(info) ; // 生产者
  Consumer con = new Consumer(info) ; // 消费者
  new Thread(pro).start() ;
 new Thread(con).start() ;
}
}

 

Android主题theme和风格style总结

用到了Android的主题和风格,感觉很多地方需要总结和记录下来。其实主题和风格是有很大的作用的,特别是界面要求比较高的客户端。

Style:是一个包含一种或者多种格式化属性的集合,我们可以将其用为一个单位用在布局XML单个元素当中。比如,我们可以定义一种风格来定义文本的字号大小和颜色,然后将其用在View元素的一个特定的实例。

如何定义style?
style也属于resource,所以要在resource下定义,就像定义string,color一样
定义style,需要指定name,style通常包含一个或多个item,每个item的name是android view的属性的名字,值则是对应相关属性的值
可以给style指定parent,从而可以继承和覆盖parent style的属性,parent取值是另外一个style,如果是继承自自己定义的style,只需要在命名style时增加前缀,这个前缀就是即将继承的style的名字

例如CodeFont是一个自己定义的style,那么下面的style,CodeFont.Red,则继承了CodeFont,只是文本的颜色修改成了红色

<style name="CodeFont.Red"> 红色  
        <item name="android:textColor">#FF0000</item>   
 </style>  
 <style name="CodeFont.Red.Big">  红色,并且大字体  
        <item name="android:textSize">30sp</item>   
 </style>

也可以继承平台的style,可继承的样式请参照绍docs/guide/topics/ui/themes.html#PlatformStyles

<style name="CodeFont" parent="@android:style/TextAppearance">  

如果父样式的值不符合你的需求,你也可以对它进行修改,和CSS中的覆盖效果一样,都是以最后的为准, 

在style中可以定义的属性
都有哪些属性在style的定义里是有效的呢?具体请参考docs/reference/android/R.attr.html
在view上使用style时,对view有效的属性起作用,无效的则会忽略
有一些属性对view无效,只对theme有效,在R.attr定义中以window开头的一些属性只对theme有效

style的使用
如果给view指定style,那么这个style只对该view有效
如果给viewgroup指定style,那么viewgroup下的元素也不会应用这个style,除非特别指定
给view指定style时,没有android:前缀,而只是style

下面是具体用法:

首先在res/values下新建一style.xml文件:

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <style name="TitleStyle">  
        <item name="android:textSize">18sp</item>  
        <item name="android:textColor">#ec9237</item>  
    </style>  
    <style name="Title" parent="@style/TitleStyle">  
        <item name="android:textSize">5sp</item>  
    </style>  
</resources>

在layout.xml中的应用:

<EditText android:layout_height="wrap_content"   
    android:text="EditText"   
    style="@style/Title"  
    android:layout_width="fill_parent"   
    android:id="@+id/editText1"></EditText> 

其实style就像是一组属性的组合, 可以看做当在view中引用style时,是顺序执行style中的item里面的每个属性,对view进行设定而已。因为可能有多个view都是需要设置相同的属性,。所以把这些view的属性单独写出,提高重用性。

theme:就像风格一样,主题依然在<style>元素里边申明,也是以同样的方式引用。不同的是你通过在Android
Manifest中定义的<application>和<activity>元素将主题添加到整个程序或者某个Activity,但是主题是
不能应用在某一个单独的View里,所以配置文件的属性也就是窗口等的主题样式。

定义一个主题:

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <style name="theme1">  
        <item name="android:windowNoTitle">true</item>  
        <item name="android:windowFullscreen">?android:windowNoTitle</item>  
    </style>  
</resources> 

 

下面代码显示在AndroidManifest.xml中如何为应用设置上面定义的主题:

<application android:icon="@drawable/iconandroid:label="@string/app_name"    
    android:theme="@style/theme1">    
    <activity android:name=".MessageShowActivity" android:label="@string/app_name"    
        android:windowSoftInputMode="adjustPan" android:screenOrientation="portrait"    
        android:theme="@style/theme2">    
    </activity>    
</application>

除了可以在AndroidManifest.xml中设置主题,同样也可以在代码中设置主题,如下:

setTheme(R.style.theme1);

注意:我们用了@符号和?符号来应用资源。@符号表明了我们应用的资源是前边定义过的(或者在前一个项目
中或者在Android 框架中)。问号?表明了我们引用的资源的值在当前的主题当中定义过。

style和theme的区别:

尽管在定义上,样式和主题基本相同,但是它们使用的地方不同。样式用在单独的View,如:EditText、TextView等;主题通过AndroidManifest.xml中的<application>和<activity>用在整个应用或者某个 Activity,主题对整个应用或某个Activity存在全局性影响。如果一个应用使用了主题,同时应用下的view也使用了样式,那么当主题与样式属性发生冲突时,样式的优先级高于主题。

另外android系统也定义了一些主题,例如:

<activity android:theme=”@android:style/Theme.Dialog”>,该主题可以让Activity看起来像一个对话框,

<activity android:theme=”@android:style/Theme.Black.NoTitleBar”>Variant of the light theme with no title bar,系统自带的黑色主题。如果需要查阅这些主题,可以在文档的reference–>android–>R.style 中查看。

android中Actionbar详解

1、什么是Action Bar
Action Bar被认为是新版Android系统中最重要的交互元素,在程序运行中一直置于顶部,主要起到的作用在于:
1)突出显示一些重要操作(如“最新”、“搜索”等)
2)在程序中保持统一的页面导航和切换方式
3)将使用频率低的功能放在Action overflow中,节省页面空间
4)一个固定区域显示程序标示

2、Action Bar分成四个区域
App Icon:可显示软件icon,也可用其他图标代替。当软件不在最高级页面时,图标左侧会显示一个左箭头,用户可以通过这个箭头向上导航。
视图切换:如果你的应用要在不同的View中显示数据,这部分允许用户来切换View。一般的作法是用一个drop-down菜单或者是Tab Controls。如果只有一个界面,那这里可以显示App Title或者更长点的商标信息
Action Buttons:这个放最重要的软件功能,放不下的按钮就自动进入Action overflow了。
Action overflow:把不常用的Actions移到Action overflow

3、屏幕旋转及不同分辨率适配
写一个应用时一个重要的UI问题就是如何适应屏幕旋转和不同尺寸的屏幕。你可以通过使用split action bars来适应这种变化,就是把action bar内容拆分到不同的bars里,拆分后一般包含三个部分:
Main action bar:用户可以通过main action
bar导航至上一级,因此这个操作条是必须的;
Top bar:当页面上有不同的内容视图时,可在这个条上使用TAB或spinner下拉菜单的形式切换;
Bottom bar:要展现更多操作和功能,在页面最下端排列

4、Contextual Action Bar (CAB)

“上下文操作栏”
(contextual action bar,CAB)
是一个浮于操作栏上的临时操作栏,用来放置一些特定的子任务。“上下文操作栏”一般在项目选择和文字选择时出现。


浏览器和
Gmail 应用中的上下文操作栏

长按可选择的内容,进入选择模式,显示上下文操作栏。

此时用户可以:

  • 通过触摸选择项目。
  • 在上下文操作栏中选择操作,并应用于所有已选项目。之后上下文操作栏自动消失。
  • 通过导航栏的“返回”按钮关闭上下文操作栏,也可以通过点击上下文操作栏的选择图标关闭它。关闭上下文操作栏的同时要取消所有的选择。

当您让用户长按来选择项目时,需要使用上下文操作栏。您可以控制上下文操作栏中的图标,使用户可以执行不同的操作。

5、ActionBar包含元素
1)Tabs
如果希望用户经常在几个部分间切换,可以使用Tabs。有两种Tabs:固定的(fixed)跟可滑动的(scrollable)
Scrollable:经常占据整个Bar的宽度,当前的活动View在中间位置,因此需要在一个专用的Bar里。可以自己水平的scroll,来显示更多其他的view。使用情况:如果有很多的Views或者你不确定有多少Views,因为可能是动态添加的。ScrollableTabs应该总是允许用户通过左右Swipe来切换Views。

Fixed Tabs:将所有标签显示在屏幕上,当方向变化时,它可能会被移动到Top bar位置去。

2)Spinners下拉框
官方给出使用spinner而不用tab的情况:
当不希望tab占据太多页面竖直方向上的空间
当用户认为不需要经常在视图之间切换时

3)Action buttons
要分清楚哪些Action是经常使用的,并根据这个来安排它们的位置。应该显示最常用的Actions,把其他不是常用的放到overflow里。
如果一个Action在当前不被使用,把它隐藏掉,而不是显示为不能用。
使用FIT表来分辨优先级。如果FIT中有一个适用,就把它放到Action bar,否则就放到Action overflow里。
F – Frequent 高频
当用户在这个页面时,是否10次中至少有7次会使用这个按钮?
用户是否通常要连续使用很多次?
如果每次使用该功能都要多一步操作,会不会很繁琐?
I– Important重要
你是否希望每个用户都能发现这个功能,或者因为它很酷或者是你的卖点?
你是否认为当需要用到这个按钮时,应该很容易触及?
T –Typical典型
在相似的软件中,这个功能是不是通常是的最重要操作?
在上下文环境下,如果这个操作按键被埋在overflow中,用户会不会惊讶?
下面的链接里包含一些系统自带操作的图标素材,针对不同屏幕分辨率,可以使用在Holo
Light和Holo Dark主题中;
另外包括图标的Adobe Illustrator源文件可供自行修改。
4)Action overflow
Action overflow中存放并不会频繁用到的操作。按照官方网页上的说法,“Overflow图标仅显示在没有MENU硬按键的手机上,而对于有MENU键的手机,
overflow图标是不显示的,当用户点击MENU按键时弹出。”这样的说法比较蹊跷,似乎和Google敦促手机厂商及软件开发商取消MENU的行为不相匹配。
6、Action bar上可以放下多少操作按钮?

如果操作按钮和软件标题等放在一排,放按钮的空间只能最多占用一半空间,如果按钮采用屏幕底部的整行action bar则可以使用整个屏幕宽度。

屏幕宽度值Density-independent Pixels(dp)决定可以放置的图标数:
少于360 dp = 2个图标
360-499 dp = 3个图标
500-599 dp = 4个图标
多于600 dp = 5个图标

本文参考:http://developer.android.com/design/patterns/actionbar.html

欢迎转载,但请注明出处与作者

出处:http://blog.sina.com.cn/staratsky

作者:流星

 

Android Fragments的使用

Fragment 表现 Activity 中用UI的一个行为或者一部分.可以组合多个fragment放在一个单独的activity中来创建一个多界面区域的UI,并可以在多个activity里重用某一个fragment.把fragment想象成一个activity的模块化区域, 有它自己的生命周期, 接收属于它的输入事件,并且可以在activity运行期间添加和删除.

Fragment 必须总是被嵌入到一个activity中, 它们的生命周期直接被其所属的宿主activity的生命周期影响.例如, 当activity被暂停,那么在其中的所有fragment也被暂停; 当activity被销毁,所有隶属于它的fragment也被销毁. 然而,当一个activity正在运行时(处于resumed状态),我们可以独立地操作每一个fragment, 比如添加或删除它们. 当处理这样一个fragment事务时,也可以将它添加到activity所管理的back stack — 每一个activity中的backstack实体都是一个发生过的fragment事务的记录. back stack允许用户通过按下 BACK按键从一个fragment事务后退(往后导航).

将一个fragment作为activity布局的一部分添加进来时, 它处在activity的viewhierarchy中的ViewGroup中,并且定义有它自己的view布局.通过在activity的布局文件中声明fragment来插入一个fragment到你的activity布局中,或者可以写代码将它添加到一个已存在的ViewGroup.然而, fragment并不一定必须是activity布局的一部分;也可以将一个fragment作为activity的隐藏的后台工作者.

本文档描述了如何使用fragment创建你的应用程序, 包括:当被添加到activity的back stack后,fragment如何维护他们的状态.在activity中,与activity和其他fragment共享事件.构建到activity的actionbar.以及更多内容.

设计哲学


Android在3.0中引入了fragments的概念,主要目的是用在大屏幕设备上–例如平板电脑上,支持更加动态和灵活的UI设计.平板电脑的屏幕要比手机的大得多,有更多的空间来放更多的UI组件,并且这些组件之间会产生更多的交互.Fragment允许这样的一种设计,而不需要你亲自来管理viewhierarchy的复杂变化. 通过将activity的布局分散到fragment中, 你可以在运行时修改activity的外观,并在由activity管理的back stack中保存那些变化.

例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输入事件. 因此, 取代使用一个activity来选择一篇文章,而另一个activity来阅读文章 的方式,用户可以在相同的activity中选择一篇文章并且阅读, 如图所示:

fragment在你的应用中应当是一个模块化和可重用的组件.即,因为fragment定义了它自己的布局, 以及通过使用它自己的生命周期回调方法定义了它自己的行为,你可以将fragment包含到多个activity中. 这点特别重要, 因为这允许你将你的用户体验适配到不同的屏幕尺寸.举个例子,你可能会仅当在屏幕尺寸足够大时,在一个activity中包含多个fragment,并且,当不属于这种情况时,会启动另一个单独的,使用不同fragment的activity.

继续之前那个新闻的例子 — 当运行在一个特别大的屏幕时(例如平板电脑),app可以在Activity A中嵌入2个fragment.然而,在一个正常尺寸的屏幕(例如手机)上,没有足够的空间同时供2个fragment用, 因此, Activity A会仅包含文章列表的fragment, 而当用户选择一篇文章时, 它会启动Activity B,它包含阅读文章的fragment. 因此, 应用可以同时支持图1中的2种设计模式.

创建Fragment


    要创建一个fragment, 必须创建一个 Fragment 的子类 (或者继承自一个已存在的它的子类). Fragment类的代码看起来很像 Activity .它包含了和activity类似的回调方法, 例如 onCreate(), onStart(),onPause, 以及 onStop(). 事实上, 如果你准备将一个现成的Android应用转换到使用fragment,你可能只需简单的将代码从你的activity的回调函数分别移动到你的fragment的回调方法.

    通常, 应当至少实现如下的生命周期方法:

 

 

  • onCreate()
    当创建fragment时, 系统调用此方法.
    在实现代码中,应当初始化想要在fragment中保持的必要组件, 当fragment被暂停或者停止后可以恢复.
  • onCreateView()
    fragment第一次绘制它的用户界面的时候, 系统会调用此方法. 为了绘制fragment的UI,此方法必须返回一个View, 这个view是你的fragment布局的根view. 如果fragment不提供UI, 可以返回null.
  • onPause()
    用户将要离开fragment时,系统调用这个方法作为第一个指示(然而它不总是意味着fragment将被销毁.) 在当前用户会话结束之前,通常应当在这里提交任何应该持久化的变化(因为用户有可能不会返回).

    大多数应用应当为每一个fragment实现至少这3个方法,但是还有一些其他回调方法你也应当用来去处理fragment生命周期的各种阶段.全部的生命周期回调方法将会在后面章节 Handlingthe Fragment Lifecycle 中讨论.

    除了继承基类 Fragment , 还有一些子类你可能会继承:

 

  • DialogFragment
    显示一个浮动的对话框.
    用这个类来创建一个对话框,是使用在Activity类的对话框工具方法之外的一个好的选择,
    因为你可以将一个fragment对话框合并到activity管理的fragment back stack中,允许用户返回到一个之前曾被摒弃的fragment.
  • ListFragment
    显示一个由一个adapter(例如 SimpleCursorAdapter)管理的项目的列表, 类似于ListActivity.
    它提供一些方法来管理一个list view, 例如 onListItemClick()回调来处理点击事件.
  • PreferenceFragment
    显示一个 Preference对象的层次结构的列表, 类似于PreferenceActivity.
    这在为你的应用创建一个”设置”activity时有用处.

添加一个用户界面


fragment通常用来作为一个activity的用户界面的一部分,并将它的layout提供给activity.为了给一个fragment提供一个layout,你必须实现 onCreateView()回调方法, 当到了fragment绘制它自己的layout的时候,Android系统调用它.你的此方法的实现代码必须返回一个你的fragment的layout的根view.

       注意: 如果你的fragment是ListFragment的子类,它的默认实现是返回从onCreateView()返回一个ListView,所以一般情况下不必实现它.

从onCreateView()返回的View, 也可以从一个xmllayout资源文件中读取并生成. 为了帮助你这么做, onCreateView() 提供了一个LayoutInflater 对象.

举个例子, 这里有一个Fragment的子类, 从文件 example_fragment.xml 加载了一个layout:

public static class ExampleFragment extends Fragment { 
         @Override 
         public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
             // Inflate the layout for this fragment 
             return inflater.inflate(R.layout.example_fragment, container, false); 
         } 
     }

 

传入 onCreateView() 的 container 参数是你的fragmentlayout将被插入的父ViewGroup(来自activity的layout).savedInstanceState 参数是一个Bundle, 如果fragment是被恢复的,它提供关于fragment的之前的实例的数据,

inflate() 方法有3个参数:

 

  • 想要加载的layout的resource ID.
  • 加载的layout的父ViewGroup.
    传入container是很重要的, 目的是为了让系统接受所要加载的layout的根view的layout参数,
    由它将挂靠的父view指定.
  • 布尔值指示在加载期间, 展开的layout是否应当附着到ViewGroup (第二个参数).
    (在这个例子中, 指定了false, 因为系统已经把展开的layout插入到container –传入true会在最后的layout中创建一个多余的view group.)

将fragment添加到activity
通常地, fragment为宿主activity提供UI的一部分, 被作为activity的整个viewhierarchy的一部分被嵌入. 有2种方法你可以添加一个fragment到activitylayout:

在activity的layout文件中声明fragment
你可以像为View一样, 为fragment指定layout属性.
例子是一个有2个fragment的activity:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
     <fragment android:name="com.example.news.ArticleListFragment"
             android:id="@+id/list"
             android:layout_weight="1"
             android:layout_width="0dp"
             android:layout_height="match_parent" />
     <fragment android:name="com.example.news.ArticleReaderFragment"
             android:id="@+id/viewer"
             android:layout_weight="2"
             android:layout_width="0dp"
             android:layout_height="match_parent" />
</LinearLayout>

<fragment> 中的 android:name属性指定了在layout中实例化的Fragment类.

当系统创建这个activity layout时,它实例化每一个在layout中指定的fragment,并调用每一个上的onCreateView()方法,来获取每一个fragment的layout.系统将从fragment返回的 View直接插入到<fragment>元素所在的地方.

        注意: 每一个fragment都需要一个唯一的标识,如果activity重启,系统可以用来恢复fragment(并且你也可以用来捕获fragment来处理事务,例如移除它.)

有3种方法来为一个fragment提供一个标识:

 

  • 为 android:id 属性提供一个唯一ID.
  • 为 android:tag 属性提供一个唯一字符串.
  • 如果以上2个你都没有提供, 系统使用容器view的ID.

撰写代码将fragment添加到一个已存在的ViewGroup.
当activity运行的任何时候, 都可以将fragment添加到activitylayout.只需简单的指定一个需要放置fragment的ViewGroup.为了在你的activity中操作fragment事务(例如添加,移除,或代替一个fragment),必须使用来自FragmentTransaction 的API.

可以按如下方法,从你的Activity取得一个 FragmentTransaction 的实例:

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

然后你可以使用 add() 方法添加一个fragment, 指定要添加的fragment, 和要插入的view.

ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();

 

add()的第一个参数是fragment要放入的ViewGroup, 由resource ID指定,第二个参数是需要添加的fragment.一旦用FragmentTransaction做了改变,为了使改变生效,必须调用commit().

添加一个无UI的fragment

之前的例子展示了对UI的支持, 如何将一个fragment添加到activity. 然而,也可以使用fragment来为activity提供后台行为而不用展现额外的UI.

要添加一个无UI的fragment, 需要从activity使用 add(Fragment, String) 来添加fragment (为fragment提供一个唯一的字符串”tag”, 而不是一个view ID).这么做添加了fragment,但因为它没有关联到一个activity layout中的一个view, 所以不会接收到onCreateView()调用.因此不必实现此方法.

为fragment提供一个字符串tag并不是专门针对无UI的fragment的 –也可以提供字符串tag给有UI的fragment – 但是如果fragment没有UI,那么这个tag是仅有的标识它的途径.如果随后你想从activity获取这个fragment, 需要使用 findFragmentByTag().

管理Fragment


要在activity中管理fragment,需要使用FragmentManager. 通过调用activity的getFragmentManager()取得它的实例.

可以通过FragmentManager做一些事情, 包括:

 

 

  • 使用findFragmentById() (用于在activitylayout中提供一个UI的fragment)或findFragmentByTag()(适用于有或没有UI的fragment)获取activity中存在的fragment
  • 将fragment从后台堆栈中弹出, 使用 popBackStack() (模拟用户按下BACK 命令).
  • 使用addOnBackStackChangeListener()注册一个监听后台堆栈变化的listener.

处理Fragment事务


关于在activity中使用fragment的很强的一个特性是:根据用户的交互情况,对fragment进行添加,移除,替换,以及执行其他动作.提交给activity的每一套变化被称为一个事务,可以使用在 FragmentTransaction 中的 API 处理.我们也可以保存每一个事务到一个activity管理的backstack,允许用户经由fragment的变化往回导航(类似于通过activity往后导航).

从 FragmentManager 获得一个FragmentTransaction的实例 :

FragmentManager fragmentManager = getFragmentManager(); 
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

每一个事务都是同时要执行的一套变化.可以在一个给定的事务中设置你想执行的所有变化,使用诸如 add(), remove(),和 replace().然后, 要给activity应用事务, 必须调用 commit().
在调用commit()之前, 你可能想调用 addToBackStack(),将事务添加到一个fragment事务的backstack. 这个back stack由activity管理, 并允许用户通过按下 BACK按键返回到前一个fragment状态.

举个例子, 这里是如何将一个fragment替换为另一个, 并在后台堆栈中保留之前的状态:

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();

在这个例子中, newFragment替换了当前layout容器中的由R.id.fragment_container标识的fragment.通过调用addToBackStack(), replace事务被保存到back stack,因此用户可以回退事务,并通过按下BACK按键带回前一个fragment.

如果添加多个变化到事务(例如add()或remove())并调用addToBackStack(),然后在你调用commit()之前的所有应用的变化会被作为一个单个事务添加到后台堆栈, BACK按键会将它们一起回退.

添加变化到 FragmentTransaction的顺序不重要, 除以下例外:

 

 

  • 必须最后调用 commit().
  • 如果添加多个fragment到同一个容器, 那么添加的顺序决定了它们在view hierarchy中显示的顺序.

 

当执行一个移除fragment的事务时, 如果没有调用 addToBackStack(), 那么当事务提交后,那个fragment会被销毁,并且用户不能导航回到它. 有鉴于此, 当移除一个fragment时,如果调用了addToBackStack(), 那么fragment会被停止, 如果用户导航回来,它将会被恢复.

提示: 对于每一个fragment事务, 你可以应用一个事务动画,通过在提交事务之前调用setTransition()实现.

调用 commit() 并不立即执行事务.恰恰相反, 它将事务安排排期, 一旦准备好,就在activity的UI线程上运行(主线程).如果有必要, 无论如何, 你可以从你的UI线程调用executePendingTransactions()来立即执行由commit()提交的事务. 但这么做通常不必要,除非事务是其他线程中的job的一个从属.

       警告:你只能在activity保存它的状态(当用户离开activity)之前使用commit()提交事务.

如果你试图在那个点之后提交, 会抛出一个异常.这是因为如果activity需要被恢复,提交之后的状态可能会丢失.对于你觉得可以丢失提交的状况, 使用 commitAllowingStateLoss().

与Activity通信


尽管Fragment被实现为一个独立于Activity的对象,并且可以在多个activity中使用,但一个给定的fragment实例是直接绑定到包含它的activity的. 特别的,fragment可以使用 getActivity() 访问Activity实例, 并且容易地执行比如在activitylayout中查找一个view的任务.

View listView = getActivity().findViewById(R.id.list);

同样地,activity可以通过从FragmentManager获得一个到Fragment的引用来调用fragment中的方法, 使用findFragmentById() 或 findFragmentByTag().

ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);