안드로이드앱이 항상 시작되도록 하기

근 1년 전에 안드로이드앱이 항상 실행되어 있도록 하기 위해서 썼던 방법.
(그 때 당시 자동 업데이트 기능으로 업데이트 후 다시 실행하는 것도 이 방법으로 해결했음)

우선 Activity 의 인스턴스가 1개만 존재하도록 해야 하므로 메인 activity의
lanuchMode를 singleTask로 설정해 준다.

<activity
            android:name=”.SampleActivity”
            android:label=”@string/app_name” android:theme=”@android:style/Theme.NoTitleBar.Fullscreen”
            android:launchMode=”singleTask”>

그리고, Service 를 하나 추가하고, Service에서 Alarm 으로  메인 Activity에 주기적으로 Intent를 날리도록 한다.

public class SampleService extends Service
{
    private static final int ALARM_INTERVAL = 30000;
    private boolean bRunning = false;

    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!bRunning) {
            bRunning = true;                        
                       
            final Intent alarmIntent = new Intent(this, SampleActivity.class);
            final PendingIntent pender = PendingIntent.getActivity(this, 0, alarmIntent, 0);
                       
            long triggerTime = SystemClock.elapsedRealtime() + 1000*30;
                       
            final AlarmManager alarm = (AlarmManager) this
                                            .getSystemService(Context.ALARM_SERVICE);
            alarm.setRepeating(AlarmManager.ELAPSED_REALTIME, triggerTime,
                               ALARM_INTERVAL, pender);
        } else {
            Log.e(“hadpan”, “sample service thread is already running”);
        }

        return START_STICKY;
    }

}

해당 Activity가 singleTask 로 설정되었기 때문에 Activity가 존재 한다면, 화면이 활성화 되고,
아직 만들어지기 전이라면 새로 앱을 실행하게 된다.

alarm 주기는 필요에 따라 적당히 조절하면 되는데.. 사실 이런 alram을 쓰는거 자체가 별로 권장하는 방법은 아니다.
당시, 만들던 앱 자체가 단말기를 전용 단말기 처럼.. 그러니까 단말기에서 해당 앱만 동작하면 되는 형태여서..
(일종의 키오스크 형태)

이 방법을 사용했지만 그다지 좋은 방법이라고는 생각되지 않음…=_=

안드로이드 사용자 조작 없이 백그라운드에서 패키지 설치하기

개발 중인 앱에 자동업데이트 기능이 필요하다.
수십에서 수백대가 설치되는데다 사용자가 일일이 조작할 수는 없는 상황이기 때문에
프로그램이 알아서 파일을 다운로드 받고 패키지 설치까지 하는 기능이 필요한 상황.

하지만, 알아본 바로는 마켓의 자동 업데이트를 통하지 않는 다음에야 사용자 확인 없이
패키지를 설치하는 것은 불가능하다. OTL
보안상 문제 때문인거 같은데…

아무튼 그대로 좌절할 수는 없어서 좀더 알아보니 일반 사용자 앱은 불가능하지만
제조사의 key로 싸인된 패키지는 방법이 있는 듯하다.. 하지만 이것도 기기 제조사에서
key를 받기가 어렵기 때문에 불가능 OTL

그러나, 이 방법이 가능한 또다른 경우는 해당 앱이 system app 인 경우이다.
이건 좀 희망이 있다. 비록 루팅을 한다거나, 커스텀 펌웨어를 만든다던가 해야 하지만.=_=

세상에 간단한 일은 없다고 조건이 다 충족되더라도 걍 되는건 아닌데…
자세한건 다음 글을 참조.

간단히 요약하면, 안드로이드에서 패키지를 관리하는 PackageManager 에서 사용자 확인 따위의 과정은
건너띄어 버리고, 실제 설치를 하는 함수를 바로 불러 버리는 거다.

하지만, 이 함수와 관련 인터페이스가 외부에 공개가 되지 않는 관계로, reflection 이라는 좀 고급 기술을 써야한다.
머.. spring 이라던지 이런 류의 프레임워크에서 다 reflection을 사용하여 구현되어 있다지만,
일반 자바 개발자가 흔히 쓸만한 기술은 아니지=_=;

암튼, 다음과 같이 해서 작동하는 건 확인완료.

==

1. Android.Manifest.xml 파일을 열고, android.permission.INSTALL_PACKAGES 퍼미션을 추가한다.
해당 권한은 제조사 key로 싸인된 앱이나 시스템 앱 처럼 시스템 권한을 가진 경우 사용 가능하다.

2. Activity 에 버튼을 하나 추가 하고, 클릭 시에 패키지 설치 코드가 실행되게한다.
직접 코드를 작성해도 되고, 위 글에 있는 소스를 사용해도 된다.

ApplicationManager am = new ApplicationManager(MyActivity.this);
am.installPackage(“/mnt/sdcard/myapp_new.apk”);

3. 버전을 다르게 하여 패키지를 2개 생성한다.

4. 루팅이된 디바이스에 두 개의 패키지 파일을 복사하고, 루트 기능을 사용할 수 있는
파일 관리자 앱을 이용하여 낮은 버전의 패키지를 /system/app 으로 복사한다.

5. 파일 권한 설정 메뉴로 해당 패키지 파일의 권한을 rw-r–r– , 즉 644 로 변경 하고,
user와 group ID는 0으로 설정한다.

6. 디바이스를 재부팅하고, 해당 앱이 어플서랍에 표시되면 시스템 앱 만들기 성공.

7. 아까 만들었던 높은 버전의 패키지 파일은 설치코드에서 지정한 것과 동일한 경로에 동일한
파일명으로 바꾼다.

8. 앱을 실행하고, 버튼을 클릭하면, 잠시 후 앱이 종료된다.

9. 어플리케이션 정보 화면에 들어가서 해당 앱의 버전이 변경되었는지 확인하면 끝!

==

이걸로 한 가지 문제는 해결이 됐는데… 업데이트 후 다시 실행하게 하는게 또 문제군=_=

Android 에서 Restlet Net extension HTTP client connector 사용 시 timeout 설정

 결론부터 말하자면 라이브러리 버그랄까…

안드로이드에서는 net extension의 HTTP client connector를 사용하면
connection timeout 과 read timeout을 설정해도 적용이 안된다.

Resetlet 문서를 보면 Internal Connector의 HTTP connector는
development only 를 추천한다고 당당히 써 있기도 한데다가
서버 다운으로 접속이 안될 때 자꾸 문제를 있으켜서 Net extention의 HTTP connector를 쓰기로 했다.

안드로이드에서는 자동으로 쓸수 있게 하는게 안된다는거 같고.. 다음과 같이 해줘야 한다.

final Engine engine = Engine.getInstance();
engine.getRegisteredClients().clear();
engine.getRegisteredClients().add(new HttpClientHelper(null));

그리고, read timeout 과 connection timeout을 설정하고, ClientResource에서
해당 client를 사용하도록 설정해 준다.

final Context context = new Context();

final Series<Parameter> parameters = context.getParameters();
parameters.add(“readTimeout”, Integer.toString(READ_TIMEOUT));

final Client client = new Client(context, Protocol.HTTP);
client.setConnectTimeout(CONNECTION_TIMEOUT);

final ClientResource cr = new ClientResource(requestUrl);
cr.setNext(client);

그런데, timeout이 전혀 안먹는다=_=
설정하는 방법이 잘못됐나 싶어 온갖 삽질을 동원하며 이리저리 테스트 해도 답이 나오질 않는다..
그래서, 결국 Restlet 소스를 뒤지기 시작했다.
(라이브러리 소스나 뒤지는 신세라니… OTL)

마침내 찾아낸 부분은… org/restlet/ext/net/internal/HttpUrlConnectionCall.java 파일의

            // These properties can only be used with Java 1.5 and upper
            // releases
            int majorVersionNumber = SystemUtils.getJavaMajorVersion();
            int minorVersionNumber = SystemUtils.getJavaMinorVersion();
            if ((majorVersionNumber > 1)
                    || ((majorVersionNumber == 1) && (minorVersionNumber >= 5))) {
                this.connection.setConnectTimeout(getHelper()
                        .getConnectTimeout());
                this.connection.setReadTimeout(getHelper().getReadTimeout());
            }

JDK 버전을 체크해서 1.5 이상 일때만 timeout을 설정하도록 하는 부분이었다.
먼가 의심스러워서, 안드로이드에서 버전값을 찍어 봤다.
… 둘다 0 이 나온다..=_=
이건 내가 잘못한건지 쟤들이 안드로이드용이라면서 안드로이드 고려를 못한건지…
도저히 판단이 안서는 상황이지만, 구국의 결단을 내려 라이브러리 소스를 직접 컴파일해 쓰기로 결정;;

            this.connection.setConnectTimeout(getHelper()
                    .getConnectTimeout());
            this.connection.setReadTimeout(getHelper().getReadTimeout());

요렇게 고쳐서 해결을 봐버렸다=_=