TargetApi not taken into account

2019-02-04 04:10发布

问题:

In one of our methods, we use smoothScrolling in a list view. As this method is not available before API Level 8 (FROYO), we used the TargetApi annotation to prevent the method from being called in previous SDK versions.

As you can see, we do use TargetApi annotation both in class definition and in statements that use the objects of the class. This is more than needed.

Our problem is that the TargetApi annotation is not taken into account and make our emulator crash in version ECLAIR (SDK 7). By tracing, we just realize that the code that should only be executed in versions 8+ is also executed in version 7.

Are we missing something?

This code is in a listener :

@TargetApi(8)
private final class MyOnMenuExpandListener implements OnMenuExpandListener {
    @Override
    public void onMenuExpanded( int position ) {
        doScrollIfNeeded( position );
    }

    @Override
    public void onMenuCollapsed( int position ) {
        doScrollIfNeeded( position );
    }

    protected void doScrollIfNeeded( int position ) {
        if ( mListViewDocuments.getLastVisiblePosition() - 2 < position ) {
            mListViewDocuments.smoothScrollToPosition( position + 1 );
        }
    }
}

And the listener is registered this way :

@TargetApi(8)
private void allowSmothScrollIfSupported() {
    if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO ) {
        //This if should not be necessary with annotation but it is not taken into account by emulator
        Log.d( LOG_TAG, "Smooth scroll support installed." );
        folderContentAdapter.setOnMenuExpandListener( new MyOnMenuExpandListener() );
    }
}

BTW, we run the code in debug mode, so the issue is not related to obfuscation removing annotations.

回答1:

@TargetApi does not prevent any code from being run, it is merely for annotating code and preventing compiler errors for new APIs once you know you are only conditionally calling them.

You still need to add something along the lines of

if (Build.VERSION.SDK_INT > 7){
    //...
}


回答2:

With almost one year of more thinking about this, I would like to add a tiny complement to @Guykun 's answer :

The @TargetApi will be only be used by tools to say developers "Hey, don't use this method below XXX android SDK". Typically lint.

So, if you design a method like :

if (Build.VERSION.SDK_INT > 7){
    //...
}

then you should add @TargetApi( 7 ) to your method's signature.

BUT, if you add an else statement, and provide an alternative that makes it work for all versions of Android like :

if (Build.VERSION.SDK_INT > 7){
    //...
} else {
    //...
}

then you should not add @TargetApi( 7 ) to your method's signature. Otherwise, other developers will think they can't use your method belw api level 7, but indeed, it would work for them as well.

So this annotation has to be used, for static analysis, to indicate the minimum api level supported by the method. As in :

@TargetApi( 7 )
public void foo() {
   if (Build.VERSION.SDK_INT > 7){
       //...
   else if (Build.VERSION.SDK_INT > 10){
       //...
   } 
}

and even better, use constants defined in android.Build.VERSION_CODES.*.

BTW, you would have noticed that this is useless for private methods indeed, except to get a cleaner code and help to promote the method public in the future.



回答3:

To enforce lint error when using a method targeted towards higher Api Level, you can use RequiresApi instead of TargetApi and whenever you'll try to use the method without checking the version code, you'll get compilation error.

This is what the documentation says about RequiresApi

This is similar in purpose to the older @TargetApi annotation, but more clearly expresses that this is a requirement on the caller, rather than being used to "suppress" warnings within the method that exceed the minSdkVersion.