2013년 6월 18일 화요일

Android Jersey 사용 시 WebResource type 설정 시 NullPointerException

이전 블로그에서 이전 함 (원본 글 2013/06/18 작성)

Android 상에서 Jersey(버전은 1.17.1)를 사용하여 REST를 처리 시
WebResoruce의 type을 설정하는데 NullPointerException이 발생하여 찾다 보니
아래와 같은 article 검색

Android 의 APK packaging 문제로 META-INF/service 들을 못찾는다는 얘기... 사실 잘 모르겠다.. :(

Reply | Threaded | More     star

Re: java.lang.NullPointerException on Android

Lucas Ventura
6 posts
Paul Sandoz wrote
On May 28, 2010, at 11:21 AM, Lucas Ventura wrote: I would be surprised if ClassLoader.getResources ceased to function correctly to load resources (that method is used to load the META-INF/ services files). But a quick search does indicate others are having similar issues with packaging and META-INF/services.
The problem after inspecting a little bit, is that the ClassLoader created by Google neither can't find services files, nor the class implementations.
Paul Sandoz wrote
There might be a way. It is possible to override the META-INF/services lookup by using the following method: https://jersey.dev.java.net/nonav/apidocs/latest/jersey/com/sun/jersey/spi/service/ServiceFinder.html #setIteratorProvider %28com.sun.jersey.spi.service.ServiceFinder.ServiceIteratorProvider%29 So you could provide your own implementation. Paul.
So I've provided a hardcoded implementation with the names of the classes indicateds in the META-INF/services files. The question is: when I should do this call to serIteratorProvider()? Because if I do before the Client creation it is looking for servicejersey-client-components. If I do after, it works, but... will be called again with a that kind of serviceName? Thanks a lot :)


암튼 해결책이 위쪽 thread와 아래 link에 있어 일단 추가하여 수정
그리고 추가로 아래 해결책의 update 사항에 Android maven plugin에서는 해결되어 있다니.. 나중에 참고해 보자..



up vote12down voteaccepted
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 forServiceFinder$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<T> extends ServiceIteratorProvider<T> {

    private static final String TAG = AndroidServiceIteratorProvider.class.getSimpleName();
    private static final String MESSAGE = "Unable to load provider";

    private static final HashMap<String, String[]> SERVICES = new HashMap<String, String[]>();

    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<Class<T>> createClassIterator(Class<T> service,
            String serviceName, ClassLoader loader,
            boolean ignoreOnClassNotFound) {

        String[] classesNames = SERVICES.get(serviceName);
        int length = classesNames.length;
        ArrayList<Class<T>> classes = new ArrayList<Class<T>>(length);
        for (int i = 0; i < length; i++) {
            try {
                classes.add((Class<T>) Class.forName(classesNames[i]));
            } catch (ClassNotFoundException e) {
                Log.v(TAG, MESSAGE,e);
            }
        }
        return classes.iterator();
    }

    @Override
    public Iterator<T> createIterator(Class<T> service, String serviceName,
            ClassLoader loader, boolean ignoreOnClassNotFound) {

        String[] classesNames = SERVICES.get(serviceName);
        int length = classesNames.length;
        ArrayList<T> classes = new ArrayList<T>(length);
            for (int i = 0; i &lt; 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...
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:
share|improve this answer


댓글 없음:

댓글 쓰기