Подтвердить что ты не робот

Как бороться с устаревшими классами в Android для обеспечения совместимости

Я возвращаюсь к работе над приложением, над которым я работал некоторое время назад, когда у меня было все, что было построено на Android 2.2 Froyo.

Я обновил свой SDK для новейших API и заметил, что функции ClipboardManager, которые я использовал, устарели. Я обновил код, чтобы использовать новую модель ClipData, и попробовал ее на своем телефоне Froyo, и, конечно же, я получил NoClassDefFoundError в новом коде.

Я посмотрел вокруг SO и не нашел реальных обсуждений общей стратегии поддержки обратной совместимости.

Я не совсем уверен, как я должен справиться с этой и другими ситуациями, когда API отличается, потому что я хочу, чтобы пользователи всех версий могли использовать мое приложение.

Должен ли я делать чек следующим образом?

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

Если это так, у меня есть устаревший код в моем классе, и Eclipse будет предупреждать о нем навсегда.

С другой стороны, я мог бы просто построить против старой версии API и надеяться, что новые версии справятся с этим в порядке. Но тогда я рискую строить против багги или низкопроизводительного кода, когда доступна лучшая альтернатива.

Каков наилучший способ справиться с этим?

4b9b3361

Ответ 1

Вы можете это сделать (проверяя версию API).

Вы также можете использовать отражение для вызова новых классов.

Я бы не стал беспокоиться об использовании устаревших методов, так как все версии Android поддерживают обратную совместимость, говоря, что вы хотите смотреть, когда что-то происходит для 3.0 Honeycomb, поскольку они немного отличаются.

Здесь объяснение того, как использовать отражение: (да, это было на SO раньше, поэтому, возможно, поиск отражения)

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), но в остальном мне нужен другой метод.

Теперь у меня есть мой тест на метод, который я могу сделать и попытаюсь использовать его:

  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 ;-))
  }

Надеюсь, что это поможет и сократит количество поисковых запросов: -)

P.S. если в другом месте вы хотите использовать свои исправленные методы, просто добавьте примечание @SuppressWarnings("deprecation") над ним, это избавит вас от предупреждения, и вы сделали это по правильным причинам, поскольку, по возможности, используете новейший API.

Ответ 2

во-первых, @Graham Borland прав. Вы можете либо использовать старый API, это полностью решает проблему. Тем не менее, ваше программное обеспечение не будет развиваться и следовать усовершенствованиям API и, в конечном счете, будет соответствовать версии андроида, которая больше не поддерживается.

Шаблон проектирования, который я собираюсь предложить, основан на интроспекции, но обеспечивает лучший интерфейс программирования, чем решение, предложенное @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

Затем вы можете предоставить различные реализации этого абстрактного класса, используя различные версии SDK android.

Что несколько необычно в этом методе, так это то, что это шаблон дизайна factory в сочетании с шаблоном проектирования singleton. Все подклассы запрашиваются как одиночные и предоставляют статический метод getInstanceMethod. Метод factory этого абстрактного класса попытается загрузить класс, реализующий этот интерфейс. Если это не удается, вы можете понизить требования к классам, реализующим службу, и на базе более старого 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 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

И вы могли бы предоставить и другие альтернативы. Попытка загрузить их один за другим, нисходящие номера версий Android.

Использовать услугу Messenger довольно просто:

MessageManager messageManager = MessageManager.getInstance( this );

если он равен нулю, то никакая служба не будет сопоставлена. Если он не является нулевым, используйте его через интерфейс, определенный MessageManager.

Этот метод может быть расширен и даже сделан более чистым, включая номер версии, на которой основана реализация, и создание небольшой шины для загрузки классов один за другим в правильном порядке.

Все отзывы приветствуются.

С уважением,  Stéphane

Ответ 3

Вот пример:

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 и более поздних версий, если вы хотите, чтобы этот код был доступен, по крайней мере, для 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: Build.VERSION_CODES p >

Ответ 4

Вы правильно определили два возможных решения: решить во время выполнения, какой API использовать или всегда использовать старый API.

Если это помогает, это может быть только год или около того, пока устройства со старым API не образуют такую ​​небольшую часть базы установки, что вы можете полностью переключиться на новый API и не беспокоиться о том, чтобы потерять слишком много пользователей.