내 멋대로 안드로이드 - 8

2022. 7. 22. 10:03모바일프로그래밍/안드로이드

728x90

저번 시간에 보았듯이

일반적인 자바 컴파일러로 컴파일 해서 jar 파일을 만들경우에는

안드로이드 Dalvik JVM은 이를 이해 할 수가 없다는 것을 확인 하였습니다

 

“안드로이드 내멋대로 탐험하기 – 3”에서 말씀 드렸듯이 자바 파일을 안드로이드 머신이 알 수 있는 만한 놈으로 바꾸어 주어야 하는 데요.

이 파일형식을 DEX 파일이라고 부르고 이를 위해서 DEX 파일 만들어 주는 유틸 인 dx.bat 파일도 존재 하고 있었습니다.

 

다시 이 과정을 상기 해 보면

R.java 파일을 만들고 (이 파일에 대해서도 알아보아야 할 듯…) 

자바 파일을 컴파일하고

Dalvik JVM용으로 변환하고

패키지 만들고 등등….

 

의 일련의 과정을 거쳐서야 만 진정 우리가 원하는 아니 안드로이드에서 돌아가는 뭔가를 만들 수 있다는 것이죠

 

지금까지의 내용을 정리해 보면 안드로이드의 동작방식에서 볼 수 있듯이 애플리케이션 마다 일정 유저 권하을 부여하고

그 애플리케이션 유저가 사용할 수 있는 디렉토리를 만들어 주고 여기에 애플리케이션의 리소스를 넣어서 사용할 수 있는 것 같다는 것이 제 결론 입니다. 

 

물론 결론이라기 보다는 조심스럽게 그렇게 판단 한다는 것입니다 왜냐하면 그것이 정확이 이론적으로 그러냐는 것은 잘 모르는 얘기인데다가 실질적인 디바이스 종류 마다 그 환경이 별반 차이는 없겠으나 그 미묘한 차이가 또 다른 큰 차이가 될 수도 있으니까 말입니다 .

 

그러면 여기서 우리의 온도변환 프로그램이 받은 app_24 사용자의 계정을 사용해서 임의의 애플리케이션 즉 안드로이드용 리소스 번들 같은 파일을 올리고 이를 동적 로딩해서 사용하는 것으로 위의 정리 내용을 마무리 하기로 하겠습니다

 

우선 Jetty 소스를 항상 참고 하는 것 같은데(저번에도 서버 만들때 참고 했음^^) AndroidClassLoader.java를 만드신 고수님께 감사 드리고 시작 하겠습니다. 

 

우선 이 구현 방식 이외에도 dalvik.system.DexFile을 동적으로 호출해서 사용하는 구현 방식도 있는 데 이 클래스 구현 방식을 사용해 보았으나…아쉽게도 실패 하였습니다.(ㅡ.ㅡ;;)

 

우선 pulgin_ext 라는 폴더를 만들고 ConvertTemp.java를 옮기고 위의 절차 대로 컴파일 하도록 하겠습니다

툴 사용 방법은 저도 자세히 모르니 안드로이드 소스 사이트와 구글 사이트를 참고하시면(구글이 만들었으니…) 될 거 같습니다

 

 

 

1.     자바 컴파일 하기

javac -d classes *.java

 

2.     Dex 파일 만들기

dx --dex --output="path\plug-in.jar" --positions=lines "path\classes"

(여기서 한가지 plug-in.xml 파일을 classes/com/aa/lec/plugin 폴더로 옮겨 주어야 한다.)

 

3.     에뮬로 전송

adb push plug-in.jar /data/data/com.aa.lec.container

위의 plug-in.jar를 풀어 보면 classes.dex 파일이 존재 하고 있음을 확인 하실 수 있습니다

그리고 우리의 컨테이너 클래스는 커스터마이징 된 AndroidClassLoader(사실 안 쓰는 거 하고 리소스 부분만 약간 손봤지만…)를 부르게 되고 이벤트가 일어 났을 때 동적으로 CovertTemp.class를 로딩하여 사용 하게 되는 것입니다.

 

해당 부분 소스를 확인 하면 우선 프로그램을 간단히 하기 위해서 우리가 불러야 할 것을 다음과 같이 정의 했습니다

 

/*내부적으로 DexClassLoader가 찾아야 할 압축파일 이름*/
private static final String DEX_FILE = "/data/data/com.aa.lec.container/plug-in.jar";

/*내부적으로 DexClassLoader가 풀어야 할 압축파일 이름*/
private static final String TMP_PATH = "/data/data/com.aa.lec.container/tmp";

/*내부적으로 DexClassLoader가 찾아야 할 리소스 이름*/
private static final String ENV_FILE = "com/aa/lec/plugin/plug-in.xml";

 

그러면 에뮬레이터 내부적인 모습은 다음과 같은 디렉토리 구조를 가지게 됩니다.

 

 

 

프로그램이 구동이 되면 이 클래스로더는 일단 Dex 파일을 추출하여 사용하는 데 이를 위해서 temp 디렉토리가 필요 한 데 tmp 디렉토리에 보면 다음과 같이 같은 이름으로 풀어져 있는 dex, 확장자 파일을 볼 수 있습니다.

 

 

 

onCreate 메소드에서 클래스로더를 생성하고 생성된 클래스 로더에서 xml리소스를 받아서 데이터를 세팅 하도록 합니다.

 

@Override
public void onCreate(Bundle savedInstanceState)
{
	super.onCreate(savedInstanceState);


	try{
		//클래스로더를 생성하고
		anloader = new com.aa.lec.clsload.AndroidClassLoader(DEX_FILE,TMP_PATH, MyContainer.class.getClassLoader());
		
		//리소스를 받아 들이고
		java.io.InputStream in = anloader.getResourceStream(ENV_FILE);
		
		//xml을 파싱한다
		parser = new com.aa.lec.parser.PlugParser();
		
		parser.init(in);                                
	}
	catch(Exception ie)
	{
		System.out.println(ie);
	}

	setContentView(R.layout.main);
}

어차피 예제니까 별 내용은 없습니다 (^^);

그런 다음 버튼이 눌려 졌을 때 우리의 View 클래스인 ConvertTemp를 로딩하고 이를 테이블에 세팅합니다

 

public void plugInClickHandler(View view) {
	Class cc1  = null;
	android.view.View conTemp = null;
	
	switch (view.getId()) {
		case R.id.PlugInBtn:
		// get a reference for the TableLayout 
		android.widget.TableLayout table
			= (android.widget.TableLayout) findViewById(R.id.TableLayout01); 

		 // create a new TableRow 
		 android.widget.TableRow row = new android.widget.TableRow(this); 

		
		System.out.println(cc1);
		java.lang.reflect.Constructor ctor = null;

		try {
			cc1 = anloader.loadClass(parser.getPlugInData().getClassString());
			ctor = cc1.getConstructor(new Class[] {android.content.Context.class, String.class});
			conTemp = (android.view.View)ctor.newInstance(this,parser.getPlugInData().getArgsString()); 
			conTemp.setBackgroundColor(0x3399CC);
		}
		catch (java.lang.reflect.InvocationTargetException ite) {
			System.out.println(ite);
		}
		catch( ClassNotFoundException cnf)
		{
			System.out.println(cnf);
		} catch (NoSuchMethodException nsme) {
			System.out.println(nsme);
		}
		catch( InstantiationException ive)
		{
			System.out.println(ive);
		}
		catch( IllegalAccessException ive)
		{
			System.out.println(ive);
		}

		WindowManager.LayoutParams wlp = getWindow().getAttributes();
		wlp.gravity = Gravity.BOTTOM;
		wlp.flags = wlp.flags | indowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
		row.addView(conTemp);
		table.addView(row,new android.widget.TableLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 
		break;
	}
}

 

android.view.View conTemp = null;

 

위의 코드는 우리가 생성 되어야 할 클래스는 그냥 View 클래스로 선언합니다. 사실 컨테이너가플러그 인 내부가 어떤 클래스인지 알 필요가 없기 때문에 다음과 같이 선언해야만 합니다.

그리고는 일반적인 자바 클래스를 동적으로 부르는 로직인 데요

클래스를 선언하고 생성자를 클래스에서 다시 생성하는 일련의 과정을 거칩니다

 

여기서 아규먼트로 들어 가는 것이 우리가 선언한 xml 리소스 파일에 있는 내용을 가지고 구동 하게 됩니다.

 

<?xml version="1.0" encoding="UTF-8"?>

<plug-in>

   <name> Convert Temperature </name>

   <class>com.aa.lec.plugin.ConvertTemp</class>

   <desc> Temperature Conversion Programs </desc>

   <args> To Celsius,To Fahrenheit </args>

<plug-in>

<plug-in.xml 전문>

 

cc1 = anloader.loadClass(parser.getPlugInData().getClassString());
ctor = cc1.getConstructor(new Class[] {android.content.Context.class, String.class});
conTemp = (android.view.View)ctor.newInstance(this,parser.getPlugInData().getArgsString());

 

나머지 클래스 들은 이를 위해서 전에 설명 하였던 것이 대부분입니다 대신 PlugInParser가 약간 바뀌었는 데. 

클래스 로더의 구현에 의해서 InputStream만 받는 구조로 클래스 구조가 변경이 되었습니다

 

이제 컴파일하고 제대로 애플리케이션에서 /data/data/com.aa.lec.container 디렉토리에 있는 plug-in.jar 파일을 부르는 지 확인을 해 봅시다

 

에뮬레이터를 띄우고..

 

 

 

 

프로그램을 구동하고

 

 

실행하면,

다음과 같이 라디오 라벨이 제대로 나온 View 클래스인 ConvertTemp를 구동하였습니다

 

 

 

 

사실 이런 정도의 복잡하게 예제 프로그램을 만들 필요가 없었지만 역으로 생각해보면 아주 복잡한 컴포넌트 시스템의 경우에는 이렇게 리소스로 분리해서 각자가 개발 하고 각 각이 올려볼 수 있도록 할 수 있을 것입니다. 

이제까지의 예로 충분하다고는 할 수 없지만 적어도 왠만한 것은 받아 주지 않을까 생각 해 봅니다

 

다음 시간에는 일반 스마트 폰 용으로 개발된 소스들은 보통 확장성이나 이식성을 고려해서 C/C++ 형식으로 구현된 것이 많은데요.

자바에서 이런 것을 받아 들일 수 있습니다 소위 Java Native Interface, JNI 라는 것인데요. 

 

첫번째 탐험에서 살펴본 동적 라이브러리를 접근 해서 만들수 있는 최소한 시스템을 만들어 보았으면 합니다.

 

 

728x90