最近看两个安卓程序都用到了接口(interface)将主要方法封装起来,然后利用多态确定调用的方法的具体实现。
接口——抽象方法的集合
从java教程上面都能看到接口一下特点:
- 接口以及抽象类不能直接被实例化,即不同通过new创建实例对象。
- 接口要有子类,一个类可以实现(implements)多个接口。
- 子类必须覆写(@override)接口中所有抽象方法。
- 接口中可以包含变量,但都是static final,所以必须初始化,子类不能对接口中的变量重新赋值。
- 接口对象可以通过子类可以向上转型进行实例化。(实现多态)
对于初学者来说,知道了以上特点并不能很好使用接口,因为往往被第1个特点唬到,而没有意识到第5个特点的妙处。
下面利用Java设计原则——针对接口编程,不针对实现编程,说明接口的用途。
程序代码是Google 利用安卓7.0版本增加的获取GNSS原始观测数据的相关类写的一个app,代码稍作修改。
App开源程序见:GNSSLogger
public interface GnssListener {
/**@see LocationListener#onProviderEnabled(String) */
void onProviderEnabled(String provider);
/**@see LocationListener#onProviderDisabled(String) */
void onproviderDisabled(String provider);
/**@see LocationListener#onLocationChanged(Location) */
void onLocationChanged(Location location);
/**@see LocationListener#onStatusChanged(String, int, Bundle) */
void onLocationStatusChanged(String provider, int status, Bundle extras);
/**@see GnssMeasurementsEvent.Callback#onGnssMeasurementsReceived(GnssMeasurementsEvent)*/
void onGnssMeasurementsReceived(GnssMeasurementsEvent event);
/**@see GnssMeasurementsEvent.Callback#onStatusChanged(int) */
void onGnssMeasurementsStatusChanged(int status);
/**@see GnssNavigationMessage.Callback#onGnssNavigationMessageReceived(GnssNavigationMessage) */
void onGnssNavigationMessageReceived(GnssNavigationMessage event);
/**@see GnssNavigationMessage.Callback#onStatusChanged(int) */
void onGnssNavigationMessageStatusChanged(int status);
/**@see GnssStatus.Callback#onSatelliteStatusChanged(GnssStatus) */
void onGnssStatusChanged(GnssStatus gnssStatus);
/**called when the listener is registered to listen to GNSS event*/
void onListenerRegistered(String listener,boolean result);
/**@see android.location.OnNmeaMessageListener#onNmeaMessage(String, long) */
void onNmeaMessage(long l,String s);
void onTTFFReceived(long l);
}
上面的接口将接口LocationListener、抽象内部类 GnssMeasurementsEvent.Callback、GnssNavigationMessage.Callback、GnssStatus.Callback 的主要方法都囊括其中,这些方法都是用来获取GNSS相关数据的。
第二个关键类是GnssContainer,其构造函数需要传入GnssListener的对象,所以需要有子类实现GnssListener接口,子类为FileLogger,但是从代码可以看出它并没有对接口中的方法添加其他代码,这种继承(实现也是一种继承)保持了FileLogger与GnssListener具有相同类型。
public class GnssContainer {
public static final String TAG="GnssLogger";
private boolean firstTime=true;
private boolean mLogLocations = true;
private boolean mLogMeasurements = true;
private final List<GnssListener> mLoggers;
private LocationManager mLocationManager;
public GnssContainer(Context context,GnssListener...loggers){
this.mLoggers=Arrays.asList(loggers);
mLocationManager=(LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
}
private final GnssMeasurementsEvent.Callback gnssMeasurementsEventListener=
new GnssMeasurementsEvent.Callback() {
@Override
public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
if (mLogMeasurements) {
for (GnssListener logger : mLoggers) {
logger.onGnssMeasurementsReceived(event);
}
}
}
@Override
public void onStatusChanged(int status) {
if (mLogMeasurements) {
for (GnssListener logger : mLoggers) {
logger.onGnssMeasurementsStatusChanged(status);
}
}
}
};
public void registerMeasurements() {
try{
mLocationManager.registerGnssMeasurementsCallback(gnssMeasurementsEventListener);
}catch (SecurityException e){
e.printStackTrace();
}
}
public void unregisterMeasurements() {
mLocationManager.unregisterGnssMeasurementsCallback(gnssMeasurementsEventListener);
}
}
FileLogger.java
public class FileLogger implements GnssListener {
private final Context mContext;
private final Object mFileLock = new Object();
private BufferedWriter mFileWriter;
private File mFile;
private static final String TAG="FileLogger"
private static final String FILE_PREFIX = "gnss_log";
private static final String COMMENT_START = "# ";
private static final char RECORD_DELIMITER = ',';
private static final String VERSION_TAG = "Version: ";
public FileLogger(Context context){
this.mContext=context;
}
public void startNewLog(){
synchronized (mFileLock){
File baseDirectory;
String state=Environment.getExternalStorageState();
if(Environment.MEDIA_MOUNTED.equals(state)){
baseDirectory=new File(Environment.getExternalStorageDirectory(),FILE_PREFIX);
baseDirectory.mkdirs();
}else if(Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)){
Log.e(TAG,"只读");
return;
}else {
Log.e(TAG,"不能读取存储");
return;
}
SimpleDateFormat format=new SimpleDateFormat("yyy_MM_dd_HH_mm_ss");
Date now=new Date();
String fileName=String.format("%s_%s.txt",FILE_PREFIX,format.format(now));
File currentFile=new File(baseDirectory,fileName);
String currentFilePath=currentFile.getAbsolutePath();
BufferedWriter currentFileWriter;
try{
currentFileWriter=new BufferedWriter(new FileWriter(currentFile));
}catch (IOException e){
Log.e(TAG,"不能打开文件");
return;
}
try{
currentFileWriter.write(COMMENT_START);
currentFileWriter.newLine();
currentFileWriter.write(COMMENT_START);
currentFileWriter.write("Header Description:");
currentFileWriter.newLine();
currentFileWriter.write(COMMENT_START);
currentFileWriter.newLine();
currentFileWriter.write(COMMENT_START);
currentFileWriter.write(VERSION_TAG);
String manufacturer = Build.MANUFACTURER;
String model = Build.MODEL;
String fileVersion =
mContext.getString(R.string.app_version)
+ " Platform: "
+ Build.VERSION.RELEASE
+ " "
+ "Manufacturer: "
+ manufacturer
+ " "
+ "Model: "
+ model;
currentFileWriter.write(fileVersion);
currentFileWriter.newLine();
currentFileWriter.write(COMMENT_START);
currentFileWriter.newLine();
currentFileWriter.write(COMMENT_START);
currentFileWriter.write(
"Raw,ElapsedRealtimeMillis,TimeNanos,LeapSecond,TimeUncertaintyNanos,FullBiasNanos,"
+ "BiasNanos,BiasUncertaintyNanos,DriftNanosPerSecond,DriftUncertaintyNanosPerSecond,"
+ "HardwareClockDiscontinuityCount,Svid,TimeOffsetNanos,State,ReceivedSvTimeNanos,"
+ "ReceivedSvTimeUncertaintyNanos,Cn0DbHz,PseudorangeRateMetersPerSecond,"
+ "PseudorangeRateUncertaintyMetersPerSecond,"
+ "AccumulatedDeltaRangeState,AccumulatedDeltaRangeMeters,"
+ "AccumulatedDeltaRangeUncertaintyMeters,CarrierFrequencyHz,CarrierCycles,"
+ "CarrierPhase,CarrierPhaseUncertainty,MultipathIndicator,SnrInDb,"
+ "ConstellationType,AgcDb,CarrierFrequencyHz");
currentFileWriter.newLine();
currentFileWriter.write(COMMENT_START);
currentFileWriter.newLine();
currentFileWriter.write(COMMENT_START);
currentFileWriter.write(
"Fix,Provider,Latitude,Longitude,Altitude,Speed,Accuracy,(UTC)TimeInMs");
currentFileWriter.newLine();
currentFileWriter.write(COMMENT_START);
currentFileWriter.newLine();
currentFileWriter.write(COMMENT_START);
currentFileWriter.write("Nav,Svid,Type,Status,MessageId,Sub-messageId,Data(Bytes)");
currentFileWriter.newLine();
currentFileWriter.write(COMMENT_START);
currentFileWriter.newLine();
}catch (IOException e){
Log.e(TAG,"不能初始化文件");
return;
}
if(mFileWriter!=null){
try{
mFileWriter.close();
}catch (IOException e){
return;
}
}
mFile=currentFile;
mFileWriter=currentFileWriter;
Toast.makeText(mContext,"File opened:"+currentFilePath,Toast.LENGTH_SHORT).show();
}
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onproviderDisabled(String provider) {
}
@Override
public void onLocationChanged(Location location) {
}
@Override
public void onLocationStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
synchronized (mFileLock) {
if (mFileWriter == null) {
return;
}
GnssClock gnssClock = event.getClock();
for (GnssMeasurement measurement : event.getMeasurements()) {
try {
writeGnssMeasurementToFile(gnssClock, measurement);
} catch (IOException e) {
e.printStackTrace();
// logException(ERROR_WRITING_FILE, e);
}
}
}
}
@Override
public void onGnssMeasurementsStatusChanged(int status) {
}
@Override
public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
}
@Override
public void onGnssNavigationMessageStatusChanged(int status) {
}
@Override
public void onGnssStatusChanged(GnssStatus gnssStatus) {
}
@Override
public void onListenerRegistered(String listener, boolean result) {
}
@Override
public void onNmeaMessage(long l, String s) {
}
@Override
public void onTTFFReceived(long l) {
}
private void writeGnssMeasurementToFile(GnssClock clock, GnssMeasurement measurement) throws IOException {
String clockStream = String.format("Raw,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s",
SystemClock.elapsedRealtime(),
clock.getTimeNanos(),
clock.hasLeapSecond() ? clock.getLeapSecond() : "",
clock.hasTimeUncertaintyNanos() ? clock.getTimeUncertaintyNanos() : "",
clock.getFullBiasNanos(),
clock.hasBiasNanos() ? clock.getBiasNanos() : "",
clock.hasBiasUncertaintyNanos() ? clock.getBiasUncertaintyNanos() : "",
clock.hasDriftNanosPerSecond() ? clock.getDriftNanosPerSecond() : "",
clock.hasDriftUncertaintyNanosPerSecond() ? clock.getDriftUncertaintyNanosPerSecond(): "",
clock.getHardwareClockDiscontinuityCount() + ",");
mFileWriter.write(clockStream);
String measurementStream = String.format(
"%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s",
measurement.getSvid(),
measurement.getTimeOffsetNanos(),
measurement.getState(),
measurement.getReceivedSvTimeNanos(),
measurement.getReceivedSvTimeUncertaintyNanos(),
measurement.getCn0DbHz(),
measurement.getPseudorangeRateMetersPerSecond(),
measurement.getPseudorangeRateUncertaintyMetersPerSecond(),
measurement.getAccumulatedDeltaRangeState(),
measurement.getAccumulatedDeltaRangeMeters(),
measurement.getAccumulatedDeltaRangeUncertaintyMeters(),
measurement.hasCarrierFrequencyHz() ? measurement.getCarrierFrequencyHz() : "",
measurement.hasCarrierCycles() ? measurement.getCarrierCycles() : "",
measurement.hasCarrierPhase() ? measurement.getCarrierPhase() : "",
measurement.hasCarrierPhaseUncertainty() ? measurement.getCarrierPhaseUncertainty(): "",
measurement.getMultipathIndicator(),
measurement.hasSnrInDb() ? measurement.getSnrInDb() : "",
measurement.getConstellationType(),
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
&& measurement.hasAutomaticGainControlLevelDb() ?
measurement.getAutomaticGainControlLevelDb() : "",
measurement.hasCarrierFrequencyHz() ? measurement.getCarrierFrequencyHz() : "");
mFileWriter.write(measurementStream);
mFileWriter.newLine();
}
public void end() {
if (mFileWriter != null) {
try {
mFileWriter.flush();
mFileWriter.close();
mFileWriter = null;
} catch (IOException e) {
return;
}
}
}
}
MainActivity.java部分代码
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_bt:
fileLogger=new FileLogger(this);
fileLogger.startNewLog();
gnssContainer=new GnssContainer(this,fileLogger);
gnssContainer.registerMeasurements();
break;
case R.id.end_bt:
fileLogger.end();
gnssContainer.unregisterMeasurements();
}
}
在MainActivity中,FileLogger类对象可以作为GnssContainer构造函数参数,这也就相当于告诉GnssContainer可以使用接口中的方法了。
上面的例子可能不太能说明标题,推荐阅读《HeadFirst 设计模式》,这本书很生动形象地介绍了多种设计模式和设计原则,对于理解源码十分有帮助。