Portar una aplicación iOS Objective-c a Android Java

Mi aplicación es un visualizador de datos médicos, donde los pacientes usan un sensor que transmite datos mediante Bluetooth de baja energía. La aplicación fue desarrollada en el Objetivo C, apuntando a la plataforma iOS. Ahora la aplicación necesita ser portada a la plataforma Android.

El layout actual y la implementación para iOS son los siguientes:

  • comunicación – Objetivo C, específico de la API Core Bluetooth
  • datos / persistencia – Objetivo C, usando FMDatabase como interfaz para SQLite
  • algorithms / lógica – Objetivo C
  • ui: JavaScript / HTML5 basado en Phonegap

Dado que la comunicación es específica de la API de Core Bluetooth, tendrá que volver a escribirse para Android. La capa de interfaz de usuario debe ser fácilmente portátil sin mucho cambio, ya que depende totalmente de Phonegap. Sin embargo, para las capas de persistencia y lógica, busco una manera de convertirlas automáticamente a Android, o reescribirlas de tal forma que sean reutilizables para ambas plataforms.

Un enfoque podría ser convertir automáticamente el código relevante del Objetivo C a Java. Hay un convertidor que va de Java al objective C para classs que no son de interfaz de usuario.

¿Hay un convertidor similar para iOS / objective C para Android / Java? ¿Existen otros enfoques o frameworks que podrían usarse?

Parece que hay: http://code.google.com/p/objc2j/

El repository debe ser accesible a través de http://objc2j.googlecode.com/svn/

No lo comprobé yo mismo, así que publica tu opinión sobre esto.

Google tiene algunos proyectos de código abierto que hacen esto.

Necesitarás usar SVN para acceder a estos repositorys. Estos son los enlaces:

Java a Objective C: http://code.google.com/p/j2objc/

Objetivo C a Java: http://code.google.com/p/objc2j/

¡Buena suerte!

Su mejor opción es usar Apportable . Es una plataforma que proporciona un puerto de clang, el time de ejecución objective-c, y la mayoría de los frameworks en iOS (incluido UIKit).

Aún no hay un envoltorio Core Bluetooth, pero puedes llamar a las API java desde su plataforma para eso. La database FM funcionará bien y, en teoría, la interfaz del espacio de teléfono funcionará bien.

Sin embargo, evitaría las sugerencias de generadores de código. Terminarán comiendo mucho time reimplementando todo lo que ya construyó si tiene un tipo de base de código significativa.

Utilicé O2J – Objective-C a Java Converter para un escenario similar y funcionó muy bien.

Hará un gran trabajo en sus algorithms / lógica sin mucho trabajo.

Es personalizable para que pueda agregar sus propias traducciones para su código de Bluetooth. Es posible que logre traducir las llamadas del método Bluetooth directamente a Java si las API funcionan igual pero probablemente no. Lo mejor es tener una capa de indirección en su código Objective-C para el bluetooth para que sea realmente fácil de suministrar una implementación específica de Android. Por ejemplo, cree un BluetoothHelper.m y un BluetoothHelper.java y la traducción será mucho más fluida.

Lo he usado para proyectos que usaban FMDatabase. ¡Para la parte FMDatabase ya tenemos FMDatabase / FMResultSet como capa de indirección! Implementé FMDatabase / FMResultSet yo mismo ya que la API para sqlite Objective-c (las funciones sqlite basadas en c) es muy diferente de Android. O2J me ayudó a comenzar a traducir FMDatabase / FMResultSet y esto es lo que terminé con …

FMDatabase:

public class FMDatabase { private SQLiteDatabase database; private HashMap<String, SQLiteStatement> compiled; public FMDatabase(SQLiteDatabase database) { this.database = database; } public FMResultSet executeQuery_arguments(String sql, Object... args) { synchronized (database) { String[] selectionArgs = objectArgsAsStrings(args); Cursor rawQuery = database.rawQuery(sql, selectionArgs); return new FMResultSet(rawQuery); } } public FMResultSet executeQuery(String sql, Object... args) { synchronized (database) { String[] selectionArgs = objectArgsAsStrings(args); Cursor rawQuery = database.rawQuery(sql, selectionArgs); return new FMResultSet(rawQuery); } } public String debugQuery(String sql, Object...args) { StringBuilder sb = new StringBuilder(); FMResultSet rs = executeQuery(sql, args); rs.setupColumnNames(); HashMap names = rs.columnNameToIndexMap(); Set ks = names.keySet(); for (Object k : ks) { sb.append(k); sb.append("\t"); } sb.append("\n"); while(rs.next()) { for (Object k : ks) { String key = k.toString(); if(rs.getType(key) == Cursor.FIELD_TYPE_STRING) { sb.append(rs.stringForColumn(key)); } else if(rs.getType(key) == Cursor.FIELD_TYPE_INTEGER) { sb.append(rs.longForColumn(key)); } else if(rs.getType(key) == Cursor.FIELD_TYPE_FLOAT) { sb.append(rs.doubleForColumn(key)); } else if(rs.getType(key) == Cursor.FIELD_TYPE_BLOB) { sb.append(rs.stringForColumn(key)); } else { sb.append("<NOT STRING>"); } sb.append("\t"); } sb.append("\n"); } return sb.toString(); } public String[] objectArgsAsStrings(Object... args) { String[] selectionArgs = new String[args.length]; for (int i = 0; i < args.length; i++) { Object o = args[i]; if(o instanceof Date) { selectionArgs[i] = Long.toString(((Date) o).getTime()); } else if(o instanceof Boolean) { selectionArgs[i] = ((Boolean) o).booleanValue() ? "TRUE" : "FALSE"; } else { selectionArgs[i] = args[i] == null ? "" : o.toString(); } } return selectionArgs; } public boolean executeUpdate_arguments(String sql, Object... args) { synchronized (database) { String[] selectionArgs = objectArgsAsStrings(args); database.execSQL(sql, selectionArgs); return true; } } public boolean executeUpdate(String sql, Object... args) { synchronized (database) { SQLiteStatement statement = bindToCachedCompiledStatement(sql, args); statement.execute(); return true; } } private SQLiteStatement bindToCachedCompiledStatement(String sql, Object... args) { HashMap<String, SQLiteStatement> statments = getCompiledStatements(); SQLiteStatement statement = statments.get(sql); if (statement == null) { statement = database.compileStatement(sql); statments.put(sql, statement); } statement.clearBindings(); // bindAllArgsAsStrings(statement, objectArgsAsStrings(args)); bindAllArgs(statement, args); return statement; } private void bindAllArgs(SQLiteStatement statement, Object[] bindArgs) { if (bindArgs == null) { return; } int size = bindArgs.length; for (int i = 0; i < size; i++) { Object arg = bindArgs[i]; int index = i + 1; if(arg == null) { statement.bindNull(index); } else if (arg instanceof String) { statement.bindString(index, (String) arg); } else if (arg instanceof Double || arg instanceof Float) { Number numArg = (Number) arg; statement.bindDouble(index, numArg.doubleValue()); } else if (arg instanceof Integer || arg instanceof Long) { Number numArg = (Number) arg; statement.bindDouble(index, numArg.longValue()); } else { statement.bindString(index, arg.toString()); } } } public long executeInsert(String string, Object... args) { synchronized (database) { SQLiteStatement statement = bindToCachedCompiledStatement(string, args); try { return statement.executeInsert(); } catch (Exception e) { Log.i("STD", "No Rows inserted", e); return 0; } } } public void bindAllArgsAsStrings(SQLiteStatement statement, String[] bindArgs) { if (bindArgs == null) { return; } int size = bindArgs.length; for (int i = 0; i < size; i++) { statement.bindString(i + 1, bindArgs[i]); } } private HashMap<String, SQLiteStatement> getCompiledStatements() { if (compiled == null) { compiled = new HashMap<String, SQLiteStatement>(); } return compiled; } public boolean rollback() { synchronized (database) { database.execSQL("ROLLBACK;"); } return true; } public boolean commit() { synchronized (database) { database.execSQL("COMMIT;"); } return true; } public boolean beginDefernetworkingTransaction() { synchronized (database) { database.execSQL("BEGIN DEFERRED TRANSACTION;"); } return true; } public boolean beginTransaction() { synchronized (database) { database.execSQL("BEGIN EXCLUSIVE TRANSACTION;"); } return true; } public boolean open() { return true; } public void setShouldCacheStatements(boolean shouldCacheStatements) { // TODO } } 

FMResultSet:

 public class FMResultSet { private boolean columnNamesSetup; private HashMap<String, Number> columnNameToIndexMap; private Cursor rawQuery; public FMResultSet(Cursor rawQuery) { this.rawQuery = rawQuery; } public void close() { rawQuery.close(); } public void setupColumnNames() { if (columnNameToIndexMap == null) { this.setColumnNameToIndexMap(new HashMap()); } int columnCount = rawQuery.getColumnCount(); int columnIdx = 0; for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { columnNameToIndexMap.put(rawQuery.getColumnName(columnIdx).toLowerCase(), new Integer(columnIdx)); } columnNamesSetup = true; } public boolean next() { return rawQuery.moveToNext(); } public int columnIndexForName(String columnName) { if (!columnNamesSetup) { this.setupColumnNames(); } columnName = columnName.toLowerCase(); Number n = columnNameToIndexMap.get(columnName); if (n != null) { return NumberValueUtil.intVal(n); } Log.i("StdLog", String.format("Warning: I could not find the column named '%s'.", columnName)); return -1; } public int intForColumn(String columnName) { if (!columnNamesSetup) { this.setupColumnNames(); } int columnIdx = this.columnIndexForName(columnName); if (columnIdx == -1) { return 0; } return intForColumnIndex(columnIdx); } public int intForColumnIndex(int columnIdx) { return rawQuery.getInt(columnIdx); } public long longForColumn(String columnName) { if (!columnNamesSetup) { this.setupColumnNames(); } int columnIdx = this.columnIndexForName(columnName); if (columnIdx == -1) { return 0; } return longForColumnIndex(columnIdx); } public long longForColumnIndex(int columnIdx) { return (long) rawQuery.getLong(columnIdx); } public boolean boolForColumn(String columnName) { return (this.intForColumn(columnName) != 0); } public boolean boolForColumnIndex(int columnIdx) { return (this.intForColumnIndex(columnIdx) != 0); } public double doubleForColumn(String columnName) { if (!columnNamesSetup) { this.setupColumnNames(); } int columnIdx = this.columnIndexForName(columnName); if (columnIdx == -1) { return 0; } return doubleForColumnIndex(columnIdx); } public double doubleForColumnIndex(int columnIdx) { return rawQuery.getDouble(columnIdx); } public String stringForColumnIndex(int columnIdx) { return rawQuery.getString(columnIdx); } public String stringForColumn(String columnName) { if (!columnNamesSetup) { this.setupColumnNames(); } int columnIdx = this.columnIndexForName(columnName); if (columnIdx == -1) { return null; } return this.stringForColumnIndex(columnIdx); } public Date dateForColumn(String columnName) { if (!columnNamesSetup) { this.setupColumnNames(); } int columnIdx = this.columnIndexForName(columnName); if (columnIdx == -1) { return null; } return new Date((this.longForColumn(columnName))); } public Date dateForColumnIndex(int columnIdx) { return new Date((this.longForColumnIndex(columnIdx))); } public byte[] dataForColumn(String columnName) { if (!columnNamesSetup) { this.setupColumnNames(); } int columnIdx = this.columnIndexForName(columnName); if (columnIdx == -1) { return null; } return this.dataForColumnIndex(columnIdx); } public byte[] dataForColumnIndex(int columnIdx) { return rawQuery.getBlob(columnIdx); } public HashMap columnNameToIndexMap() { return columnNameToIndexMap; } public void setColumnNameToIndexMap(HashMap value) { columnNameToIndexMap = value; } @SuppressLint("NewApi") public int getType(String string) { return rawQuery.getType(columnIndexForName(string)); } }