Thread Interference

Consider a simple class called Counter

class Counter {
    private int c = 0;

    public void increment() {
        c++;
    }

    public void decrement() {
        c--;
    }

    public int value() {
        return c;
    }

}

Counter is designed so that each invocation of increment will add 1 to c, and each invocation of decrement will subtract 1 from c. However, if a Counter object is referenced from multiple threads, interference between threads may prevent this from happening as expected.

Interference happens when two operations, running in different threads, but acting on the same data, interleave. This means that the two operations consist of multiple steps, and the sequences of steps overlap.

It might not seem possible for operations on instances of Counter to interleave, since both operations on c are single, simple statements. However, even simple statements can translate to multiple steps by the virtual machine. We won’t examine the specific steps the virtual machine takes — it is enough to know that the single expression c++ can be decomposed into three steps:

  1. Retrieve the current value of c.
  2. Increment the retrieved value by 1.
  3. Store the incremented value back in c.

The expression c-- can be decomposed the same way, except that the second step decrements instead of increments.

Suppose Thread A invokes increment at about the same time Thread B invokes decrement. If the initial value of c is 0, their interleaved actions might follow this sequence:

  1. Thread A: Retrieve c.
  2. Thread B: Retrieve c.
  3. Thread A: Increment retrieved value; result is 1.
  4. Thread B: Decrement retrieved value; result is -1.
  5. Thread A: Store result in c; c is now 1.
  6. Thread B: Store result in c; c is now -1.

Thread A’s result is lost, overwritten by Thread B. This particular interleaving is only one possibility. Under different circumstances it might be Thread B’s result that gets lost, or there could be no error at all. Because they are unpredictable, thread interference bugs can be difficult to detect and fix.

Design Pattern – Singleton Pattern

Singleton pattern is one of the simplest design patterns in Java. This type of design pattern comes under creational pattern as this pattern provides one of the best ways to create an object.

This pattern involves a single class which is responsible to create an object while making sure that only single object gets created. This class provides a way to access its only object which can be accessed directly without need to instantiate the object of the class.

Implementation

We’re going to create a SingleObject class. SingleObject class have its constructor as private and have a static instance of itself.

SingleObject class provides a static method to get its static instance to outside world. SingletonPatternDemo, our demo class will use SingleObjectclass to get a SingleObject object.

Singleton Pattern UML Diagram

Step 1

Create a Singleton Class.

SingleObject.java

public class SingleObject {

   //create an object of SingleObject
   private static SingleObject instance = new SingleObject();

   //make the constructor private so that this class cannot be
   //instantiated
   private SingleObject(){}

   //Get the only object available
   public static SingleObject getInstance(){
      return instance;
   }

   public void showMessage(){
      System.out.println("Hello World!");
   }
}

Step 2

Get the only object from the singleton class.

SingletonPatternDemo.java

public class SingletonPatternDemo {
   public static void main(String[] args) {

      //illegal construct
      //Compile Time Error: The constructor SingleObject() is not visible
      //SingleObject object = new SingleObject();

      //Get the only object available
      SingleObject object = SingleObject.getInstance();

      //show the message
      object.showMessage();
   }
}

Step 3

Verify the output.

Hello World!

Wifi Direct:群组问题

1.在 4.2 以上版本,如果直连配对成功过,设置里会显示已保存的群组,以后这两台手机连接就无需确认,可以直接连上,不会弹出对话框让用户选择同意或是拒绝。4.0 里还没有群组这个东西。

2.4.2版本的 WifiP2pSettings 源码中,连接成功后,会默认自动记下这个群组。

在 WifiPpManager 中,当有两设备连成功构成组后:

1
2
3
4
5
6
case WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO:
    WifiP2pGroupList groups = (WifiP2pGroupList) message.obj;
    if (listener != null) {
        ((PersistentGroupInfoListener) listener).onPersistentGroupInfoAvailable(groups);
    }
break;

而 onPersistentGroupInfoAvailable(groups) 是这个类中的内部接口中定义的方法,在 WifiP2pSettings 中实现了这个接口,重写了方法

1
2
3
4
5
6
7
public void onPersistentGroupInfoAvailable(WifiP2pGroupList groups) {
    mPersistentGroup.removeAll();
    for (WifiP2pGroup group: groups.getGroupList()) {
        if (DBG)     Log.d(TAG, " group " + group);
        mPersistentGroup.addPreference(new WifiP2pPersistentGroup(getActivity(), group));
    }
}

得到组的列表,然后将组添加到界面上

3.群组封装的对象

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
public class WifiP2pPersistentGroup extends Preference {
    public WifiP2pGroup mGroup;
    public WifiP2pPersistentGroup(Context context, WifiP2pGroup group) {
        super(context);
        mGroup = group;
    }
    @Override
    protected void onBindView(View view) {
        setTitle(mGroup.getNetworkName());
        super.onBindView(view);
    }
    int getNetworkId() {
        return mGroup.getNetworkId();
    }
    String getGroupName() {
        return mGroup.getNetworkName();
    }
}
// 如果点击了群组,弹出对话框问是否要删除群组
else if (preference instanceof WifiP2pPersistentGroup) {
    mSelectedGroup = (WifiP2pPersistentGroup) preference;
    showDialog(DIALOG_DELETE_GROUP);
}
else if (id == DIALOG_DELETE_GROUP) {
    int stringId = R.string.wifi_p2p_delete_group_message;
    AlertDialog dialog = new AlertDialog.Builder(getActivity())
        .setMessage(getActivity().getString(stringId))
        .setPositiveButton(getActivity().getString(R.string.dlg_ok), mDeleteGroupListener)
        .setNegativeButton(getActivity().getString(R.string.dlg_cancel), null)
        .create();
    return dialog;
}

4.删除群组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//delete persistent group dialog listener
mDeleteGroupListener = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
    if (which == DialogInterface.BUTTON_POSITIVE) {
        if (mWifiP2pManager != null) {
            mWifiP2pManager.deletePersistentGroup(mChannel,
                mSelectedGroup.getNetworkId(),
                new WifiP2pManager.ActionListener() {
                    public void onSuccess() {
                        if (DBG) Log.d(TAG, " delete group success");
                    }
                    public void onFailure(int reason) {
                        if (DBG) Log.d(TAG, " delete group fail " + reason);
                    }
                });
            }
        }
    }
};

5.manager.requestGroupInfo(channel, activity) 会调用 GroupInfoListener 接口里的 onGroupInfoAvailable 方法,得到组的信息。GroupInfoListener 这个接口要实现 onGroupInfoAvailable(WifiP2pGroup group) 方法,group 里包含一个 Group Owner 和多个 Group Client 的信息。

Wifi Direct:功能测试

1.WiFi 直连中收到 WIFI_P2P_STATE_CHANGED_ACTION 广播时,更改直连状态,根据状态不同执行相应操作。如果 WiFi 未开启,要跳到设置界面去设置,本来想如果设置返回还要判断执行,所以放到 onResum 方法里执行,但这样有问题,这个界面还可能跳到其它界面,状态可能没改变,而且 onResume 比广播接收者要调用的方法执行的更早,所以 onResume 里用到的 isWifiP2pEnabled 并不是最新的。所以后来放在更改状态之后执行,这样只要更改状态就执行。

        广播接收者收到广播调用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
public void setIsWifiP2pEnabled(boolean isWifiP2pEnabled) {
    this.isWifiP2pEnabled = isWifiP2pEnabled;
    // 状态变化了,调用这个方法
    testAndDiscover();
}
// 看WiFi功能是否开启,若开启,去发现设备
private void testAndDiscover() {
 
    if(!isWifiP2pEnabled) {
        AlertDialog.Builder builder = new Builder(this);
        builder.setTitle("提示")
        .setMessage("请先确认您的设备支持WiFi直连功能。如果支持,请先在设置中开启WiFi")
        .setPositiveButton("去设置"new DialogInterface.OnClickListener() {
 
            @Override
            public void onClick(DialogInterface dialog, int which) {
                startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
            }
        })
        .setNegativeButton("不弄了"new DialogInterface.OnClickListener() {
 
            @Override
            public void onClick(DialogInterface dialog, int which) {
 
            }
        }).show();
    else // 如果wifi直连功能支持并已开启
        WifiDirectUtil.discoverPeers(DeviceListActivity.this, manager, channel);
    }
}

2.广播接收者里收到的 WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 这个广播是更新自己的状态

1
2
3
4
5
if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
    // 更新自己的设备状态
    activity.updateThisDevice((WifiP2pDevice) intent
            .getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE));
}
        收到广播的意图intent中保存了自己这个设备的信息,所以通过(WifiP2pDevice) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE 来得到自己这台设备 WifiP2pDevice 对象。
        更新自己的设备状态
1
2
3
4
5
6
public void updateThisDevice(WifiP2pDevice device) {
    TextView myName = (TextView) findViewById(R.id.my_name);
    TextView myStatus  = (TextView) findViewById(R.id.my_status);
    myName.setText(device.deviceName);
    myStatus.setText(Util.getDeviceStatus(device.status));
}

其中 device.status 是int型值,所以要转变为对应文字,这样看着有意义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static String getDeviceStatus(int deviceStatus) {
    switch (deviceStatus) {
    case WifiP2pDevice.AVAILABLE:
        return "Available";
    case WifiP2pDevice.INVITED:
        return "Invited";
    case WifiP2pDevice.CONNECTED:
        return "Connected";
    case WifiP2pDevice.FAILED:
        return "Failed";
    case WifiP2pDevice.UNAVAILABLE:
        return "Unavailable";
    default:
        return "Unknown";
    }
}

3.原来 discoverPeers,requestPeers 之类的方法都是放在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
34
35
/**
   * 去发现设备
   * @param context
   * @param manager
   * @param channel
   */
public static void discoverPeers(final Context context, WifiP2pManager manager, Channel channel) {
    Log.i(TAG, "discoverPeers");
    manager.discoverPeers(channel, new ActionListener() {
 
        @Override
        public void onSuccess() {
            // 成功后,会收到对应广播
        }
 
        @Override
        public void onFailure(int reason) {
            Toast.makeText(context, "搜索设备失败,错误代码:" + reason,
                    Toast.LENGTH_SHORT).show();
        }
    });
}
 
/**
   * 更新自己这台设备的状态
   * @param activity
   * @param device
   */
public static void updateThisDevice(Activity activity, WifiP2pDevice device) {
    Log.i(TAG, "updateThisDevice");
    TextView myName = (TextView) activity.findViewById(R.id.my_name);
    TextView myStatus  = (TextView) activity.findViewById(R.id.my_status);
    myName.setText(device.deviceName);
    myStatus.setText(getDeviceStatus(device.status));
}
        这样在 Activity 和 BroadcastReceiver 中只要调用这些方法,并传入对应参数即可。
        在广播接收者里收到 WIFI_P2P_PEERS_CHANGED_ACTION 这个广播时,要调用 WifiP2pManager 的requestPeers 方法,第二个参数是 PeerListListener 对象,会自动回调里面的 onPeersAvailable 方法。原来是让设备列表的适配器直接实现 PeerListListener,但这样就写死了,所以考虑将这个 requestPeers 方法也提取到工具类里。
        在 onPeersAvailable 方法里得到更新的设备列表,应该去更新适配器的数据并刷新。原来是这样的
1
2
3
4
5
6
7
8
9
10
11
@Override
public void onPeersAvailable(WifiP2pDeviceList peerList) {
    // peers 就是适配器中用到的那个设备列表集合    
    peers.clear();
    peers.addAll(peerList.getDeviceList());
 
    notifyDataSetChanged(); // 刷新适配器
    if (peers.size() == 0) {
        return;
    }
}

但现在将 PeerListListener 和适配器分离,所以考虑得到最新的列表后去通过方法调用去更改适配器中内容,然后再刷新,所以要在适配器类中添加一个方法,去设置它自己的那个 List 集合,但用户开始不知道一定要有这方法,所以写一个接口

1
2
3
4
5
6
7
8
public interface BaseDeviceAdapter {
    /**
       * 更新适配器中的设备列表那个List集合。并刷新适配器内容
       * @param peers
       */
    public void updateDeviceList(List<WifiP2pDevice> peers);
}

这样自己写的 DeviceAdapter 实现接口,重写这个方法

1
2
3
4
5
6
// 更新peers并刷新适配器
@Override
public void updateDeviceList(List<WifiP2pDevice> peers) {
    this.peers = peers;
    notifyDataSetChanged();
}

在工具类里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
   * 查找设备
   * @param manager
   * @param channel
   * @param adapter 要更新内容的适配器,类型是自己定义的那个接口
   */
public static void requestPeers(WifiP2pManager manager,  Channel channel, final BaseDeviceAdapter adapter) {
    Log.i(TAG, "requestPeers");
    manager.requestPeers(channel, new PeerListListener() {
 
        @Override
        public void onPeersAvailable(WifiP2pDeviceList peerList) {
            List<WifiP2pDevice> peers = new ArrayList<WifiP2pDevice>();
            peers.clear();
            peers.addAll(peerList.getDeviceList());
 
            // 更新适配器中内容。利用参数传递的那个接口调用
            adapter.updateDeviceList(peers);
            if (peers.size() == 0) {
                return;
            }
        }
    });
}

4.连接一台设备,知道的是WifiP2pDevice,WifiP2pManager 真正去 connect 需要的 WifiP2pConfig 信息通过 WifiP2pDevice 来设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void connect(final Context context, WifiP2pManager manager,
    Channel channel, WifiP2pDevice device) {
    WifiP2pConfig config = new WifiP2pConfig();
    config.deviceAddress = device.deviceAddress;
    config.wps.setup = WpsInfo.PBC;
 
    manager.connect(channel, config, new ActionListener() {
        @Override
        public void onSuccess() {
            // WiFiDirectBroadcastReceiver will notify us. Ignore for now.
        }
 
        @Override
        public void onFailure(int reason) {
            Toast.makeText(context, "连接失败,请重试", Toast.LENGTH_SHORT).show();
        }
    });
}

5.广播接收者收到这个广播 WIFI_P2P_CONNECTION_CHANGED_ACTION 表示连接状态改变了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (manager == null) {
    return;
}
 
NetworkInfo networkInfo = (NetworkInfo) intent
                    .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
 
if (networkInfo.isConnected()) { // 成功连接上
    Log.i(TAG, "收到广播:连接成功");
    WifiDirectUtil.requestConnectionInfo(manager, channel);
else {
    // 不管哪一方主动断开连接,这里都会收到广播,在这里去重置数据
    // 比如清空一些数据,或者重新 discoverPeers 显示到界面上
    activity.resetData();
}
        
// 在Activity中,如果直连断了,重新查找设备
public void resetData() {
    WifiDirectUtil.discoverPeers(DeviceListActivity.this, manager, channel);
}
        而在 Activity 中的 onCreate 方法中每次进入都调用 adapter.clearPeers() 先清空适配器中数据,不然上一次的数据不清空会影响下一次的界面显示内容。
        在适配器中
1
2
3
4
public void clearPeers() {
    peers.clear();
    notifyDataSetChanged();
}

在 onDestory 中断开连接,如果原来是连接的,断开成功,如果原来就是断开的,断开失败,反正没影响。