如何处理Android中的弃用类以保持兼容性

我正在回到我刚刚工作的一个应用程序上,那时我的所有内容都是围绕Android 2.2 Froyo构build的。

我已经更新了我的SDK以获取最新的API,并注意到我使用的ClipboardManagerfunction已被弃用。 我更新了代码以使用更新的ClipData模型,并在我的Froyo手机上尝试了它,当然,我在新代码中得到了一个N​​oClassDefFoundError。

我曾经看过,并没有发现任何关于保持向后兼容性的一般策略的真正讨论。

我不完全确定我应该如何处理API和其他API不同的情况,因为我希望所有版本的用户都能够使用我的应用程序。

我应该如下进行检查吗?

if(version == old){ use old API; } else { use new API; } 

如果是这样,我已经弃用了我的类中的代码,Eclipse将永远有警告。

另一方面,我可以build立一个旧版本的API,并希望新版本可以处理它。 但是,如果有更好的替代scheme,那么我将面临越障或低性能代码的风险。

处理这个问题的最好方法是什么?

Solutions Collecting From Web of "如何处理Android中的弃用类以保持兼容性"

你可以这样做(检查API版本)。

您也可以使用reflection来调用较新的类。

因为所有Android版本都是向后兼容的,所以我不会担心使用已弃用的方法,因为这些内容有些不同,所以您要注意什么时候使用3.0 Honeycomb。

下面是关于如何使用reflection的解释:(是的,它已经在之前,所以也许searchreflection)

http://www.youtube.com/watch?v=zNmohaZYvPw&feature=player_detailpage#t=2087s

我正在考虑使这个项目是可用的,但在那之前这里是一些代码:

(你可以在扩展应用程序的类中进行一次性设置)

  public static Method getExternalFilesDir; static { try { Class<?> partypes[] = new Class[1]; partypes[0] = String.class; getExternalFilesDir = Context.class.getMethod("getExternalFilesDir", partypes); } catch (NoSuchMethodException e) { Log.e(TAG, "getExternalFilesDir isn't available in this devices api"); } } 

现在, getExternalFilesDir()仅在API级别8或更高版本上可用,所以如果他们有(Froyo),我想使用它,但除此之外,我需要另一种方法。

现在我有我的testing方法,我可以继续尝试使用它:

  if(ClassThatExtendsApplication.getExternalFilesDir != null){ Object arglist[] = new Object[1]; arglist[0] = null; File path = (File) ClassThatExtendsApplication.getExternalFilesDir.invoke(context, arglist); // etc etc } else { // Not available do something else (like your deprecated methods / or load a different class / or notify they should get a newer version of Android to enhance your app ;-)) } 

希望有助于和捷径很多谷歌search:-)

PS如果在其他人想要使用您的deprecated方法仍然只是在它上面添加@SuppressWarnings("deprecation")注释,这将摆脱警告,你已经做了正确的理由,因为你正在使用最新的API的时候可能。

首先,@Graham Borland是对的。 你可以select使用旧的API,这完全解决了这个问题。 然而,你的软件不会发展,并遵循API的改进,最终将匹配不再支持的Android版本。

我将要提出的devise模式是基于自省,但是比@Blundell提出的解决scheme提供了更好的编程接口。 我认为这足以激发对这个共同问题的标准方法。 它基于Stack Over Flow和其他论坛的许多post。

首先,您需要为您要实施的服务定义一个接口。 您将能够使用您感兴趣的不同版本的API来实现此服务的不同版本。

事实上,我们将在这里分享一些代码来加载我们不同的实现,我们select使用抽象类。 它将定义公共方法签名为一个接口bu也将提供一个静态方法来加载你的不同的实现。

 /** * Interface used to interact with the actual instance of MessageManager. * This inteface allows will be the type of the reference that will point * to the actual MessageMessenger, which will be loaded dynamically. * @author steff * */ public abstract class MessageManager { /** Request code used to identify mail messages.*/ public final static int FOR_MAIL = 0x3689; /** Request code used to identify SMS messages.*/ public final static int FOR_SMS = 0x3698; /** * Start an activity inside the given context. It will allow to pickup a contact * and will be given an intent code to get contact pick up. * *@param the request code. Has to be a constant : FOR_MAIL or FOR_SMS */ public abstract void pickupContact(int code);//met /** * Start an activity inside the given context. It will allow to pickup a contact * and will be given an intent code to get contact pick up. * *@param the request code. Has to be a constant : FOR_MAIL or FOR_SMS */ public abstract void sendMessage(int code, Intent data, final String body);//met /** * Static methode used as in factory design pattern to create an instance * of messageManager. Here it is combined with the singleton pattern to * get an instance of an inherited class that is supported by current android SDK. * This singleton will be created bu reflexion. * @param activity the activity that needs messaging capabilities. * @return an instance of an inherited class that is supported by current android SDK or null, if not found. */ public static MessageManager getInstance( Activity activity ) { MessageManager instance = null; try { Class<? extends MessageManager> messageManagerClass = (Class<? extends MessageManager>) activity.getClassLoader().loadClass( "ca.qc.webalterpraxis.cinedroid.message.MessageManagerSDK7" ); Method singletonMethod = messageManagerClass.getMethod("getInstance", Activity.class ); instance = (MessageManager) singletonMethod.invoke( null , activity); } catch (Throwable e) { Log.e( "CinemadroidMain", "Impossible to get an instance of class MessageManagerSDK7",e ); }//met return instance; }//met }//interface 

然后,您可以使用不同版本的android SDK提供此抽象类的不同实现。

这种方法有点不同寻常,它是一种工厂devise模式与单一devise模式的结合。 所有的子类都被要求是单例,并提供一个静态的getInstanceMethod。 这个抽象类的工厂方法将尝试加载一个实现这个接口的类。 如果失败,可以将您的要求降级到实施该服务的class级,并基于较老的APIS。

这是一个使用这个接口发送邮件和短信的例子。 它是专为Android SDK 7。

 public class MessageManagerSDK7 extends MessageManager { /** Used for logcat. */ private static final String LOG_TAG = "MessageManagerSDK7"; /** Singleton instance. */ private static MessageManagerSDK7 instance = null; /** Activity that will call messaging actions. */ private Activity context; /** Private constructor for singleton. */ private MessageManagerSDK7( Activity context ) { if( instance != null ) throw new RuntimeException( "Should not be called twice. Singleton class."); this.context = context; }//cons /** * Static method that will be called by reflexion; * @param context the activity that will enclose the call for messaging. * @return an instance of this class (if class loader allows it). */ public static MessageManagerSDK7 getInstance( Activity context ) { if( instance == null ) instance = new MessageManagerSDK7( context ); instance.context = context; return instance; }//met /* (non-Javadoc) * @see ca.qc.webalterpraxis.cinedroid.model.MessageManager#pickupContact(int) */ @Override public void pickupContact( int code ) { if( code != FOR_MAIL && code != FOR_SMS ) throw new RuntimeException( "Wrong request code, has to be either FOR_MAIL or FOR_SMS."); Intent intentContact = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI); context.startActivityForResult(intentContact, code ); }//met /* (non-Javadoc) * @see ca.qc.webalterpraxis.cinedroid.model.MessageManager#sendMessage(int, android.content.Intent, java.lang.String) */ @Override public void sendMessage( int code, Intent data, final String body ) { //System.out.println( "SendMessage"); if( code != FOR_MAIL && code != FOR_SMS ) throw new RuntimeException( "Wrong request code, has to be either FOR_MAIL or FOR_SMS."); int icon = 0; int noItemMessage = 0; int title = 0; //set the right icon and message for the dialog if( code == FOR_MAIL ) { icon=R.drawable.mail; noItemMessage = R.string.no_email_found; title = R.string.mail_error; }//if else if( code == FOR_SMS ) { icon=R.drawable.sms; noItemMessage = R.string.no_number_found; title = R.string.sms_error; }//if //compose email or sms //pick contact email address final String[] emailsOrPhoneNumbers = (code == FOR_MAIL ) ? getContactsEmails( data ) : getContactPhoneNumber( data ); if( emailsOrPhoneNumbers == null ) { new AlertDialog.Builder( context ).setIcon( icon ).setTitle(title).setMessage( noItemMessage ).show(); return; }//if //in case there are several addresses, we handle this using a dialog. //modal dialog would be usefull but it's bad UI practice //so we use an alert dialog, async .. //all this is poorly coded but not very interesting, not worth having a dedicated inner class if( emailsOrPhoneNumbers.length > 1 ) { selectMultipleAndSend( emailsOrPhoneNumbers, body, code); return; }//if if( code == FOR_MAIL ) sendMail( emailsOrPhoneNumbers, body ); else sendSMS( emailsOrPhoneNumbers, body ); }//met private void sendMail( String[] emails, String body ) { if( body == null ) { new AlertDialog.Builder( context ).setIcon( R.drawable.mail ).setTitle(R.string.mail_error).setMessage( R.string.impossible_compose_message ).show(); return; }//if //prepare email data try { Intent i = new Intent(Intent.ACTION_SEND); i.setType("message/rfc822") ; i.putExtra(Intent.EXTRA_EMAIL, emails ); //i.putExtra(Intent.EXTRA_EMAIL, emails); i.putExtra(Intent.EXTRA_SUBJECT, context.getString( R.string.showtimes ) ); i.putExtra(Intent.EXTRA_TEXT,body); context.startActivity(Intent.createChooser(i, context.getString( R.string.select_application ) ) ); } catch (Throwable e) { new AlertDialog.Builder( context ).setIcon( R.drawable.mail ).setTitle(R.string.mail_error).setMessage( R.string.no_application_mail ).show(); Log.e( LOG_TAG, "No application found", e); }//catch }//met private void sendSMS( String[] phoneNumbers, String body ) { try { Intent sendIntent= new Intent(Intent.ACTION_VIEW); if( body == null ) { new AlertDialog.Builder( context ).setIcon( R.drawable.sms ).setTitle(R.string.sms_error).setMessage( R.string.impossible_compose_message ).show(); return; }//if sendIntent.putExtra("sms_body", body); String phones = ""; for( String phoneNumber : phoneNumbers ) phones += ((phones.length() == 0) ? "" : ";") + phoneNumber; sendIntent.putExtra("address", phones ); sendIntent.setType("vnd.android-dir/mms-sms"); context.startActivity(sendIntent); } catch (Throwable e) { new AlertDialog.Builder( context ).setIcon( R.drawable.sms ).setTitle(R.string.sms_error).setMessage( R.string.no_application_sms ).show(); Log.e( LOG_TAG, "No application found", e); }//catch }//met /** * @param intent the intent returned by the pick contact activity * @return the emails of selected people, separated by a comma or null if no emails has been found; */ protected String[] getContactsEmails(Intent intent) { List<String> resultList = new ArrayList<String>(); //http://stackoverflow.com/questions/866769/how-to-call-android-contacts-list Cursor cursor = context.managedQuery(intent.getData(), null, null, null, null); while (cursor.moveToNext()) { String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)); // Find Email Addresses Cursor emails = context.getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI,null,ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = " + contactId,null, null); while (emails.moveToNext()) { resultList.add( emails.getString(emails.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA)) ); }//while emails.close(); } //while (cursor.moveToNext()) cursor.close(); if( resultList.size() == 0 ) return null; else return resultList.toArray( new String[ resultList.size() ] ); }//met /** * @param intent the intent returned by the pick contact activity * @return the phoneNumber of selected people, separated by a comma or null if no phoneNumber has been found; */ protected String[] getContactPhoneNumber(Intent intent) { List<String> resultList = new ArrayList<String>(); //http://stackoverflow.com/questions/866769/how-to-call-android-contacts-list Cursor cursor = context.managedQuery(intent.getData(), null, null, null, null); while (cursor.moveToNext()) { String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)); String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME)); String hasPhone = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)); if ( hasPhone.equalsIgnoreCase("1")) hasPhone = "true"; else hasPhone = "false" ; if (Boolean.parseBoolean(hasPhone)) { Cursor phones = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = "+ contactId,null, null); while (phones.moveToNext()) { resultList.add( phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)) ); } phones.close(); } } //while (cursor.moveToNext()) cursor.close(); if( resultList.size() == 0 ) return null; else return resultList.toArray( new String[ resultList.size() ] ); }//met private void selectMultipleAndSend( final String[] emailsOrPhoneNumbers, final String body, final int code ) { int icon = 0; int selectMessage = 0; //set the right icon and message for the dialog if( code == FOR_MAIL ) { icon=R.drawable.mail; selectMessage = R.string.select_email; }//if else if( code == FOR_SMS ) { icon=R.drawable.sms; selectMessage = R.string.select_phone; }//if final boolean[] selected = new boolean[ emailsOrPhoneNumbers.length ]; Arrays.fill( selected, true ); new AlertDialog.Builder( context ).setIcon( icon ).setTitle( selectMessage ).setMultiChoiceItems(emailsOrPhoneNumbers, selected, new OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { selected[ which ] = isChecked; } }).setPositiveButton( R.string.OK, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { int count = 0; for( int s=0; s< selected.length; s ++ ) if( selected[s] ) count ++; String[] selectedEmailsOrPhoneNumbers = new String[ count ]; int index = 0; for( int s=0; s< selected.length; s ++ ) if( selected[s] ) selectedEmailsOrPhoneNumbers[ index ++ ] = emailsOrPhoneNumbers[ s ]; if( code == FOR_MAIL ) sendMail( selectedEmailsOrPhoneNumbers, body ); else if( code == FOR_SMS ) sendSMS( selectedEmailsOrPhoneNumbers, body ); } }).setNegativeButton( R.string.cancel , null ).show(); }//met }//class 

你也可以提供其他的select。 尝试一个接一个地加载它们,降序android版本号。

使用你的Messenger服务非常简单:

 MessageManager messageManager = MessageManager.getInstance( this ); 

如果它为空,则不匹配任何服务。 如果不是null,则通过MessageManager定义的接口使用。

通过包含实现所基于的版本号,以及构build一个小型总线来按照正确的顺序一个接一个地加载类,这种技术可以被扩展,甚至变得更清晰。

所有的反馈欢迎。

问候,Stéphane

这里是一个例子:

 import android.os.Build; public static int getWidth(Context mContext){ int width=0; WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); if(VERSION.SDK_INT > VERSION_CODES.HONEYCOMB){ Point size = new Point(); display.getSize(size); width = size.x; } else{ width = display.getWidth(); // deprecated, use only in Android OS<3.0. } return width; } 

正如你所看到的代码部分:

  if(VERSION.SDK_INT > VERSION_CODES.HONEYCOMB){ Point size = new Point(); display.getSize(size); width = size.x; } 

仅适用于Android 3.0及更高版本,如果您希望此代码至less可用于Jelly Bean(Android 4.1),请使用:

  if(VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN){ Point size = new Point(); display.getSize(size); width = size.x; } 

VERSION.SDK_INT框架的用户可见的SDK版本; 其可能的值在Build.VERSION_CODES中定义。

有关更多信息: Build.VERSION

你可以在这里看到VERSION_CODES constats: Build.VERSION_CODES

您已经正确地确定了两种可能的解决scheme:在运行时决定使用哪个API,或者始终使用旧的API。

如果有帮助的话,可能只有一年左右,直到具有旧API的设备形成如此小的安装基础比例,才能完全切换到新的API,而不用担心失去太多的用户。