Jersey Client on Android - NullPointerException

前端 未结 2 1484
忘掉有多难
忘掉有多难 2020-12-05 11:04

I am trying to create a simple Android client for a web service I have built, but something goes wrong at runtime.

Here is the Client:

public class M         


        
2条回答
  •  孤城傲影
    2020-12-05 11:45

    Paul's suggestion is correct. I got a little bit deeper in that subject and found a reason.

    Android apk packaging tool (APKBuilder class from sdklib.jar) ignores different folders while it creates the package (source). One of those folders is META-INF - no matter if its in the attached library or in the project's source folder.

    Workaround for version 1.8 (and probably other also, but I didn't test it) of Jersey is to provide custom (default one looks up META-INF/services/ which is not present in android apk) implementation for ServiceFinder$ServiceIteratorProvider which has hard coded provider class names. I based my implementation on this implementation proposed by Lucas:

    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Iterator;
    
    import android.util.Log;
    
    import com.sun.jersey.spi.service.ServiceFinder.ServiceIteratorProvider;
    
    public class AndroidServiceIteratorProvider extends ServiceIteratorProvider {
    
        private static final String TAG = AndroidServiceIteratorProvider.class.getSimpleName();
        private static final String MESSAGE = "Unable to load provider";
    
        private static final HashMap SERVICES = new HashMap();
    
        private static final String[] com_sun_jersey_spi_HeaderDelegateProvider = {
                "com.sun.jersey.core.impl.provider.header.MediaTypeProvider",
                "com.sun.jersey.core.impl.provider.header.StringProvider"
        };
    
        private static final String[] com_sun_jersey_spi_inject_InjectableProvider = { 
        };
    
        private static final String[] javax_ws_rs_ext_MessageBodyReader = {
                "com.sun.jersey.core.impl.provider.entity.StringProvider",
                "com.sun.jersey.core.impl.provider.entity.ReaderProvider"
        };
    
        private static final String[] javax_ws_rs_ext_MessageBodyWriter = {
                "com.sun.jersey.core.impl.provider.entity.StringProvider",
                "com.sun.jersey.core.impl.provider.entity.ReaderProvider"
        };
    
        static {
            SERVICES.put("com.sun.jersey.spi.HeaderDelegateProvider",
                    com_sun_jersey_spi_HeaderDelegateProvider);
            SERVICES.put("com.sun.jersey.spi.inject.InjectableProvider",
                    com_sun_jersey_spi_inject_InjectableProvider);
            SERVICES.put("javax.ws.rs.ext.MessageBodyReader",
                    javax_ws_rs_ext_MessageBodyReader);
            SERVICES.put("javax.ws.rs.ext.MessageBodyWriter",
                    javax_ws_rs_ext_MessageBodyWriter);
            SERVICES.put("jersey-client-components", new String[]{});
            SERVICES.put("com.sun.jersey.client.proxy.ViewProxyProvider", new String[]{});
        }
    
        @SuppressWarnings("unchecked")
        @Override
        public Iterator> createClassIterator(Class service,
                String serviceName, ClassLoader loader,
                boolean ignoreOnClassNotFound) {
    
            String[] classesNames = SERVICES.get(serviceName);
            int length = classesNames.length;
            ArrayList> classes = new ArrayList>(length);
            for (int i = 0; i < length; i++) {
                try {
                    classes.add((Class) Class.forName(classesNames[i]));
                } catch (ClassNotFoundException e) {
                    Log.v(TAG, MESSAGE,e);
                }
            }
            return classes.iterator();
        }
    
        @Override
        public Iterator createIterator(Class service, String serviceName,
                ClassLoader loader, boolean ignoreOnClassNotFound) {
    
            String[] classesNames = SERVICES.get(serviceName);
            int length = classesNames.length;
            ArrayList classes = new ArrayList(length);
                for (int i = 0; i < length; i++) {
                try {
                    classes.add(service.cast(Class.forName(classesNames[i])
                            .newInstance()));
                } catch (IllegalAccessException e) {
                    Log.v(TAG, MESSAGE,e);
                } catch (InstantiationException e) {
                    Log.v(TAG, MESSAGE,e);
                } catch (ClassNotFoundException e) {
                    Log.v(TAG, MESSAGE,e);
                }
            }
    
            return classes.iterator();
        }
    }
    

    I removed most of the classes since I didn't use them and also they were causing verification errors on dalvik vm...

    NOTE: It means that with this implementation you can use only subset of headers/types supported by Jersey (I needed only String). To support other types you have to add providers located in META-INF/services folder of jersey-client.jar (or from Lucas' implementation) and check if they don't cause verification errors on Android.

    The above code should be used as follows:

    ServiceFinder.setIteratorProvider(new AndroidServiceIteratorProvider());
    Client client = Client.create();
    

    EDIT:

    It seems that the problem have been fixed in android-maven-plugin.

    mosa...@gmail.com wrote:

    The pull request with the fix was merged into master and will be released with 3.2.1:

提交回复
热议问题