apkの新しいインストールプロセス


## Lollipopで実装された新しいapkのインストール形式

5.0からinstall sessionという新しいインストール方法が実装されている。

どういうものかというと、
- apkインストール時に一時コピーはつくらずに、フラッシュでバイスに直接書き込む
- その結果、いろいろ効率的になって良い

InstallSessionとかいうらしい。
詳細は別ポストで書く予定。とりあえず試し方。


## 簡単な試し方

### pmコマンド

pmコマンドもすでに対応していて、`pm install hogehoge.apk`ではなくて以下のようにする。

やってることは、

- セッションをつくって、
- インスコしたいapk登録して、
- セッションコミット！
 
の3段階。簡単。

コマンドの例は以下。

```shell
$ pm install-create -i ringo
Success: created install session [1914420212]

$ pm install-write 1914420212 gorira <インスコしたいapkのパス>
Success: streamed 279430 bytes

$ pm install-commit 1914420212
Success
```

Successと出たらインスコ成功。

あと、`$ pm install-abandon セッションID` でセッションの破棄。
詳細はpmコマンドのソース見ればわかると思います。

### API

ちなみにinstallsessionは公開APIです。
https://developer.android.com/reference/android/content/pm/PackageInstaller.html

ただしandroid.permission.INSTALL_PACKAGES（signature|system）が必要っぽいので、以下試される方は、priv-appにつっこんでくらはい。
以下スニペットです。

```Java
private int createSession() {
    final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
    params.setInstallLocation(PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);

    int sessionId = -1;
    try {
        sessionId = packageInstaller.createSession(params);
    } catch (IOException e) {}

    PackageInstaller.SessionCallback sessionCallback = new
            PackageInstaller.SessionCallback() {
                @Override
                public void onCreated(int i) {
                    Log.v("hoge", "SessionCallback#onCreate : " + i);
                }

                @Override
                public void onBadgingChanged(int i) {
                    Log.v("hoge", "SessionCallback#onBadgingChanged : " + i);
                }

                @Override
                public void onActiveChanged(int i, boolean b) {
                    Log.v("hoge", "SessionCallback#onActiveChanged : "
                            + i + ", boolean :" + b);
                }

                @Override
                public void onProgressChanged(int i, float v) {
                    Log.v("hoge", "SessionCallback#onProgressChanged :" + i + ", " + v);
                }

                @Override
                public void onFinished(int i, boolean b) {
                    Log.v("hoge", "SessionCallback#onFinished : " + i + ", boolean :" + b);
                }
            };

    packageInstaller.registerSessionCallback(sessionCallback);

    return sessionId;
}

private int writeSession(final int sessionId) {
    long sizeBytes = -1;
    final String splitName = 適当な文字列;
    final String apkPath = インスコしたいapkのパス;

    final File file = new File(apkPath);
    if (file.isFile()) {
        sizeBytes = file.length();
    }
    Log.v("hoge", "apk size :" + sizeBytes);

    PackageInstaller.Session session = null;
    InputStream in = null;
    OutputStream out = null;
    try {
        try {
            session = packageInstaller.openSession(sessionId);
            in = new FileInputStream(apkPath);
            out = session.openWrite(splitName, 0, sizeBytes);
            int total = 0;
            byte[] buffer = new byte[65536];
            int c;
            while ((c = in.read(buffer)) != -1) {
                total += c;
                out.write(buffer, 0, c);
            }
            session.fsync(out);
        } catch (IOException e) {
        }

        return 0;
    } finally {

        try {
            out.close();
            in.close();
            session.close();
        } catch (IOException e) {}
    }
}

private void commitSession(final int sessionId) {
    PackageInstaller.Session session = null;
    try {
        try {
            session = packageInstaller.openSession(sessionId);
        } catch (IOException e) {
        }
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent sender = PendingIntent.getActivity(this, 0, intent, 0);
        session.commit(sender.getIntentSender());
    } finally {
        session.close();
    }
}
```