如何处理Android中不推荐的类以保持兼容性
我正在重新开发一款应用程序,这是我不久前开发的,当时我的一切都是围绕Android 2.2 Froyo构建的 我已经为最新的API更新了我的SDK,并注意到我使用的ClipboardManager功能已被弃用。我更新了代码以使用更新的ClipData模型,并在我的Froyo手机上试用了它,当然,我在新代码中得到了NoClassDefFoundError 我环顾了一下,没有发现任何关于维护向后兼容性的一般策略的真正讨论 我不完全确定我应该如何处理这个和其他API不同的情况,因为我希望所有版本的用户都能使用我的应用程序 我应该做如下检查吗如何处理Android中不推荐的类以保持兼容性,android,compatibility,deprecated,backwards-compatibility,Android,Compatibility,Deprecated,Backwards Compatibility,我正在重新开发一款应用程序,这是我不久前开发的,当时我的一切都是围绕Android 2.2 Froyo构建的 我已经为最新的API更新了我的SDK,并注意到我使用的ClipboardManager功能已被弃用。我更新了代码以使用更新的ClipData模型,并在我的Froyo手机上试用了它,当然,我在新代码中得到了NoClassDefFoundError 我环顾了一下,没有发现任何关于维护向后兼容性的一般策略的真正讨论 我不完全确定我应该如何处理这个和其他API不同的情况,因为我希望所有版本的用户
if(version == old){
use old API;
} else {
use new API;
}
如果是这样,我的类中有不推荐使用的代码,Eclipse将永远在那里发出警告
另一方面,我可以根据旧版本的API进行构建,并希望新版本能够很好地处理它。但是,当有更好的替代方案可用时,我就面临着构建错误或低性能代码的风险
处理这个问题的最佳方法是什么?您已经正确地确定了两种可能的解决方案:在运行时决定使用哪种API,或者始终使用旧的API 如果有帮助的话,可能只需要一年左右的时间,使用旧API的设备就可以在安装群中占很小的比例,这样您就可以完全切换到新API,而不用担心失去太多用户 您可以这样做(检查API版本) 您还可以使用反射来调用较新的类 我不担心使用不推荐的方法,因为所有的Android版本都是向后兼容的,说你想看看3.0蜂巢的情况,因为它们有点不同 这里有一个关于如何使用反射的解释:(是的,它以前是开着的,所以可能搜索反射) 我正在考虑使该项目可用,但在此之前,这里有一些代码: (您可以在扩展应用程序的类中执行此操作,即一次性设置) 希望这对谷歌搜索有所帮助和帮助:-)
另外,如果在else中您仍然想使用您不推荐的方法,只需在其上方添加
@SuppressWarnings(“deprecation”)
注释,这将消除警告,并且您这样做的原因是正确的,因为您尽可能使用最新的API。首先,@Graham Borland是正确的。您可以选择使用旧的API,这完全解决了问题。然而,您的软件不会随着API的改进而发展,最终将与不再受支持的android版本相匹配
我将要提出的设计模式是基于内省的,但它提供了比@Blundell提出的解决方案更好的编程接口。
我认为它足够强大,可以激发出解决这个常见问题的标准方法。它基于Stack Over Flow和其他论坛的许多帖子
首先,您需要为要实现的服务定义一个接口。您将能够使用感兴趣的API的不同版本实现此服务的不同版本
实际上,当我们在这里共享一些代码来加载不同的实现时,我们选择使用一个抽象类。它将公共方法签名定义为一个接口,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
如果为null,则没有匹配的服务。如果不为null,则通过MessageManager定义的接口使用
通过包含实现所基于的版本号,并构建一个小型总线以按正确顺序逐个加载类,可以扩展此技术,甚至可以使其更干净
欢迎所有反馈
问候,,
斯泰芬尼以下是一个例子:
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;
}
仅适用于Android 3.0及更高版本,如果您希望此代码至少适用于Jelly Bean(Android 4.1),请使用:
VERSION.SDK_INT框架的用户可见SDK版本;有可能
值在Build.VERSION\u代码中定义
更多关于:
你可以在这里看到版本代码constats:在谷歌,有人提到多个版本将是Android的常见问题,因此值得处理多个版本的弃用问题,因为这似乎将是一个持续的问题。简单而优雅。这样,您就可以对包含不推荐使用的代码(本例中为getWidth(Context mContext))的特定方法使用@SuppressWarnings(“不推荐使用”)注释来抑制不推荐使用警告。
/**
* 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
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
MessageManager messageManager = MessageManager.getInstance( this );
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;
}
if(VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN){
Point size = new Point();
display.getSize(size);
width = size.x;
}