Android, 時々Web

Androidアプリを作ったり、Webサービスを作ったりする中で困ったことや詰まったことを解決したときの備忘録です。備忘録なので、正しい答えが書いてあるとは限りません。

Android版LINEでトーク履歴のインポートに失敗した時の対処法

こんにちは。

 

スマホをリフレッシュ品に交換したので、もろもろデータを移行し、もちろんLINEのトーク履歴もバックアップをしておいたので、スムーズにトーク履歴をインポートして・・いたのですが、

一人だけ「トーク履歴のインポートに失敗しました」と表示されてインポートできない!!!一番大事なデータなのに!!!なんてこった!!!

 

こんな時はGoogle先生だ、と早速検索しても、あんまり情報が出てこない。。

 

唯一、

【LINE】「トーク履歴のインポートに失敗しました」が表示されるパターン | LINEの仕組み

がそれらしいことを書いてましたが、

バックアップファイルは、暗号化(と思われるもの)が施されており、バックアップファイルを開いて解読することもできません

なので、端末の再起動2.4万PV再インストール40.3万PVあたりが対策の候補となりますが、できることはあまりありません。

以前からこのエラーは原因不明90.2万PVなので、復元には失敗する可能性がある前提でバックアップしなければなりません。この履歴に関しては、LINEの運営も救済してくれません。

本当に消えたら困る場合は、別途テキストバックアップやスクリーンショットを保険として残しておくべきです

とのことで、要は諦めろとのこと。

 

いやいや、諦められません。

他のトークルームのトーク履歴はちゃんとインポートできたので、エクスポートとインポートの手順が間違ってるとか、そういうことではないはずです。

また、ZIPファイルもちゃんとあるので、この中にデータが入ってるはずです。

 

ですが、先程の引用サイトを参照すると、暗号化(と思われるもの)が施されていて解読できないと・・・。 

どれだけggっても同じようなことしか書いてないので、人に頼るのはやめて、自分で調べてみました。

 

原因

いろいろと触っていて気づいたのは、インポートできないバックアップファイルは相対的にファイルサイズが大きいということです。

今回私が読み込めなかったものは640MBありました。

それだけトークをしたり、画像を送ったりしていたということですね。

 

(ちなみに、400MBくらいのものはちゃんとインポートできました。)

 

バックアップファイルの構造

バックアップファイルである「LINE_Android-backup-chat-XXXXXXXXXXXX.zip」を普通に解凍すると、「linebackup」というディレクトリが出現します。

この中にバックアップファイルが入っているということです。

そのさらに中には、「chat」と「image」というディレクトリがあります。これらがトークと画像たちに違いありません。

 

結論からいうと、「chat」ディレクトリにある「chat-XXXXXXXXXXXX」というファイルと「chat-XXXXXXXXXXXX.extra」というファイルはどちらもバイナリファイルになっており、解析は諦めました。ちょっとバイナリエディタで開いてみたのですが、知識がなさすぎるので解析できそうにありません。

 

では「image」ディレクトリはどうんなんだというと、これがすごく簡単で、「image」ディレクトリ下のファイルは全てそのまま画像や動画の暗号化されていないバイナリファイルです。

数字だけで拡張子がないものがダウンロード済みの画像ファイルで、「*.thumb」となっているのがサムネイル用の画像ファイルだと思われます。あとは「*.mp4」なんてのもあったので、動画もここにあるのでしょう。

 

これらのバイナリファイルは暗号化されていないので、拡張子を「.jpg」に変更するだけで、読み込み可能になります。

ファイルの命名規則は詳しくはわかりませんが、「XX」と「XX.thumb」というファイルは、サムネイルとその本体の対応になっているようです。

(例:「70」という画像のサムネイルが「70.thumb」)

thumbしか存在しないものは、送られてきたものの開かなかったファイルだと思われます(LINEで「保存期間が終了しました」とか出る状態のアレです)。

 

さて、バックアップファイルの構造と、読み込めない原因がわかりました。

これらを解決してトーク履歴をインポートできるようにしましょう!

 

解決法

トーク履歴がインポートできない原因は、どうもファイルサイズが原因のようでした。

また、バックアップファイルに含まれる画像郡はそのままバイナリファイルでした。

 

そうです、画像を圧縮してバックアップファイルのサイズを小さくすれば、トーク履歴のインポートができるようになるのです!!

(もし、画像なんかいらねえトークだけで十分だ!!という方がいらっしゃれば、「image」ディレクトリを削除すればいいだけでもいけます。画像を圧縮する代わりに「image」ディレクトリを削除してください。)

 

画像を圧縮するのはなんでもいいのですが、今回は「Caesium」を使いました。

 

それでは、実際にインポートに失敗するバックアップファイルを修復する手順を以下に示します。

 

修復手順

  1. 「LINE_Android-backup-chat-XXXXXXXXXXXX.zip」を展開します。
  2. 展開したディレクトリ直下に「linebackup」というディレクトリがあるので開きます。
  3. linebackupの直下に「image」というディレクトリがあるので、これを開きます。
  4. 「image」ディレクトリ以下の全ての「XX」と「XX.thumb」の後ろに「.jpg」を付与します。
  5. なんらかの画像圧縮ソフトで「XX.jpg」と「XX.thumb.jpg」を圧縮したりリサイズしたりします。(お好みの画質やサイズでいいですが、最終的に全てのファイルが400MBくらいに収まるようにしましょう)
  6. 圧縮した「XX.jpg」と「XX.thumb.jpg」の全てから「.jpg」を削除して名前を元に戻します。
  7. linebackupディレクトリをzip圧縮します。7-zipで圧縮しましたが、たぶんなんでもいいです。
  8. linebackup.zipができるので、それを最初の「LINE_Android-backup-chat-XXXXXXXXXXXX.zip」にリネームします。
  9. 完成した圧縮版のバックアップファイルを、端末のLINE_Backupディレクトリにコピーします。このディレクトリはLINEアプリからトークをバックアップした時にバックアップファイルが作成されるディレクトリです。(端末の内蔵ストレージの「/storage/emulated/0/LINE_Backup」にあるはずです)
  10. LINEアプリから「トーク履歴のインポート」を実行します。
  11. インポートできたら成功です!!

 

失敗する場合

この手順に従っても、うまくいかない場合があるかもしれません。

私の場合、うまくいったということに過ぎません。

失敗した場合は、次の点をもう一度確認してください。

  • 圧縮後の「LINE_Android-backup-chat-XXXXXXXXXXXX.zip」は400MB程度以下になっているか。
  • ディレクトリ構成は次のようになっているか
    LINE_Android-backup-chat-XXXXXXXXXXXX.zip
      +-- linebackup
             +--chat
             +-- image
  • もともとあったファイル以外に変なファイルが混入していないか

また、失敗した場合は「履歴削除」でトークルームを真っ白にしてからやりなおすといいかもしれません。(その場合、前回のバックアップタイミング以降のトーク履歴は永遠に失われることになります。。)

 

それでは、皆様の幸運をお祈りします。

 

※コメントして頂いたら分かる範囲で答えるかも知れません。

LINE着せかえシミュレータを作りました

背景

LINEのクリエイターズスタンプマーケットが飽和状態の中、4月下旬あたりからクリエイターズ着せ替えが販売可能になるそうです。

私はイラストとかデザインとか苦手なので、手を出したくても出せなかったのですが、着せ替えはスタンプと違って独自のキャラクターが必須条件ではないような気がするので、ちょっと試しに作ってみたくなり、いろいろ考えていました。

いろいろ考えた結果、やっぱり実際にその着せ替えを適用してみなきゃ良いものかどうか分かんないなーと思い画像編集ソフトで切り貼りしてプレビューしてみてたのですが、どうも面倒くさい!シミュレーターは無いのか!

 

作ってみた

ググってもスタンプシミュレータしか出てきません。

おそらく、4月中旬あたりから着せ替えの審査が開始されるので、その頃から公式のシミュレータが公開されるのかな?と思いつつ、待てなかったのでやっつけですが作ってみました。

LINEの着せかえを作ろうと考えている方、是非使ってみてください!

LINE着せかえシミュレータ / LINE Theme Simulator

 

注意書き

  • カラースキンは詳細がわからないので、既存の着せ替えを参考に色が変えられそうなところを変えられるようにしてあります。
  • 画像の位置や拡大比率等、厳密には実物と異なる可能性がありますので、位置調整にはご注意下さい。
  • その他、何か意見や不満、疑問等ありましたらこちらの記事にコメント頂くか、LINE Theme Simulatorのフッターからお問い合わせ下さい。

【翻訳】Android Wearでスマホとウェアラブルの間でデータを同期する

Android Wear向けのアプリを開発していて躓くのが、Wear側とHandheld側でデータをどのように同期するかということ。

それを解決してくれるDataApiというAPIが用意されているが、日本語の情報が少なくどのようにして使うべきなのかがわかりにくい。

ということで、Android DevelopersのSyncing Data Itemsという記事を理解するために翻訳してみたので、それを共有してみます。

英語はあまり得意ではないので、誤訳などがあったらツッコんで下さい。。

 

#ここから引用、翻訳

Syncing Data Items
データアイテムの同期

A DataItem defines the data interface that the system uses to synchronize data between handhelds and wearables. A DataItem generally consists of the following items:

DataItemはシステムが手持ち端末とウェラブル端末との間でデータを同期するのに使用する、データインタフェースを定義します。DataItemは一般的に以下の項目から成ります:

  • Payload - A byte array, which you can set with whatever data you wish, allowing you to do your own object serialization and deserialization. The size of the payload is limited to 100KB.
    ペイロード‐バイト配列。あなたが希望するシリアライズとデシリアライズをすることができるどのようなデータでもセットすることができます。ペイロードの最大サイズは100KBです。
  • Path - A unique string that must start with a forward slash (for instance, "/path/to/data")
    パス‐スラッシュで始まるユニークな文字列(たとえば、"/path/to/data")

You normally don't implement DataItem directly. Instead, you:
普通、直接DataItemを実装しません。その代わりに:

  1. Create a PutDataRequest object, specifying a string path to uniquely identify the item.
    アイテムを一意に特定できるパス文字列を指定し、PutDataRequestオブジェクトを作成します。
  2. Call setData() to set the payload.
    ペイロードをセットするためにsetData()を呼びます。
  3. Call DataApi.putDataItem() to request the system to create the data item.
    データアイテムの作成をシステムにリクエストするためにDataApi.putDataItem()を呼びます。
  4. When requesting data items, the system returns objects that properly implement the DataItem interface.
    データアイテムをリクエストした時、システムはDataItemインタフェースを適切に実装したオブジェクトを返します。

However, instead of working with raw bytes using setData(), we recommend you use a data map, which exposes a data item in an easy-to-use Bundle-like interface.
しかし、setData()を使用して生のバイト配列を利用して動作させる代わりに、データマップを使用することを推奨します。

Sync Data with a Data Map
データマップを用いたデータの同期


When possible, use the DataMap class. This approach lets you work with data items in the form of an AndroidBundle, so object serialization and de-serialization is done for you, and you can manipulate data with key-value pairs.
可能であれば、DataMapクラスを使用してください。このアプローチはデータアイテムをAndroidのBundleの形で利用できます。すなわち、オブジェクトのシリアライズとデシリアライズが行われ、データをキー・バリューの対で操作することができます。

To use a data map:
データマップを使用するために:

  1. Create a PutDataMapRequest object, setting the path of the data item.
    PutDataMapRequestオブジェクトを作成し、データアイテムのパスをセットします。

    Note: The path string is a unique identifier for the data item that allows you to access it from either side of the connection. The path must begin with a forward slash. If you're using hierarchical data in your app, you should create a path scheme that matches the structure of the data.
    注意:パス文字列は、接続のどちら側からでもアクセスできるようにする、データアイテムの一意な識別子です。パスはスラッシュで始まる必要があります。あなたが階層的なデータをアプリで使用している場合、パスのスキーマをデータの構造にマッチするように作るべきです。

  2. Call PutDataMapRequest.getDataMap() to obtain a data map that you can set values on.
    値をセットできるデータマップを取得するために、PutDataMapRequest.getDataMap()を呼びます。
  3. Set any desired values for the data map using the put...() methods, such as putString().
    putString()のようなput…()メソッドを使用して、データマップに値をセットします。
  4. Call PutDataMapRequest.asPutDataRequest() to obtain a PutDataRequest object.
    PutDataRequestオブジェクトを取得するために、PutDataMapRequest.asPutDataRequest()を呼びます。
  5. Call DataApi.putDataItem() to request the system to create the data item.
    システムにデータアイテムの作成をリクエストするために、DataApi.putDataItem()を呼びます。

    Note: If the handset and wearable devices are disconnected, the data is buffered and synced when the connection is re-established.
    注意:もしスマートフォンウェアラブル端末が切断されていれば、データはバッファされ接続が再確立された時に同期されます。

The increaseCounter() method in the following example shows how to create a data map and put data in it:
以下の例のincreaseCounter()メソッドはどのようにしてデータマップを作成し、それにデータを格納するかを示します。

public class MainActivity extends Activity implements
       
DataApi.DataListener,
       
GoogleApiClient.ConnectionCallbacks,
       
GoogleApiClient.OnConnectionFailedListener {

   
private static final String COUNT_KEY = "com.example.key.count";

   
private GoogleApiClient mGoogleApiClient;
   
private int count = 0;

   
...

   
// Create a data map and put data in it
   
private void increaseCounter() {
       
PutDataMapRequest putDataMapReq = PutDataMapRequest.create("/count");
        putDataMapReq
.getDataMap().putInt(COUNT_KEY, count++);
       
PutDataRequest putDataReq = putDataMapReq.asPutDataRequest();
       
PendingResult<DataApi.DataItemResult> pendingResult =
               
Wearable.DataApi.putDataItem(mGoogleApiClient, putDataReq);
   
}

   
...
}

For more information about handling the PendingResult object, see Wait for the Status of Data Layer Calls.
PendingResultオブジェクトのハンドリングについてのさらなる情報は、Wait for the Status of Layer Callsを参照してください。

Listen for Data Item Events
データアイテムイベントを受け取る


If one side of the data layer connection changes a data item, you probably want to be notified of any changes on the other side of the connection. You can do this by implementing a listener for data item events.
データレイヤー接続の片側がデータアイテムを変更すると、あなたはおそらく接続のもう片側で変更の通知を望むでしょう。あなたはデータアイテムイベントのリスナを実装することで、これを実現できます。

The code snippet in the following example notifies your app when the value of the counter defined in the previous example changes:
以下の例のコードスニペットは、前の例で定義されたカウンタの値が変更される時、あなたのアプリに通知します:

public class MainActivity extends Activity implements
       
DataApi.DataListener,
       
GoogleApiClient.ConnectionCallbacks,
       
GoogleApiClient.OnConnectionFailedListener {

   
private static final String COUNT_KEY = "com.example.key.count";

   
private GoogleApiClient mGoogleApiClient;
   
private int count = 0;

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        setContentView
(R.layout.activity_main);

        mGoogleApiClient
= new GoogleApiClient.Builder(this)
               
.addApi(Wearable.API)
               
.addConnectionCallbacks(this)
               
.addOnConnectionFailedListener(this)
               
.build();
   
}

   
@Override
   
protected void onResume() {
       
super.onResume();
        mGoogleApiClient
.connect();
   
}

   
@Override
   
public void onConnected(Bundle bundle) {
       
Wearable.DataApi.addListener(mGoogleApiClient, this);
   
}

   
@Override
   
protected void onPause() {
       
super.onPause();
       
Wearable.DataApi.removeListener(mGoogleApiClient, this);
        mGoogleApiClient
.disconnect();
   
}

   
@Override
   
public void onDataChanged(DataEventBuffer dataEvents) {
       
for (DataEvent event : dataEvents) {
           
if (event.getType() == DataEvent.TYPE_CHANGED) {
               
// DataItem changed
               
DataItem item = event.getDataItem();
               
if (item.getUri().getPath().compareTo("/count") == 0) {
                   
DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
                    updateCount
(dataMap.getInt(COUNT_KEY));
               
}
           
} else if (event.getType() == DataEvent.TYPE_DELETED) {
               
// DataItem deleted
           
}
       
}
   
}

   
// Our method to update the count
   
private void updateCount(int c) { ... }

   
...
}

This activity implements the DataItem.DataListener interface. This activity adds itself as a listener for data item events inside the onConnected() method and removes the listener in the onPause() method.
このアクティビティはDataItem.DataListerインタフェースを実装しています。このアクティビティはデータアイテムイベントのためにonConnected()メソッドの中で自身をリスナとして追加し、onPuase()メソッドの中でリスナを削除しています。

You can also implement the listener as a service. For more information, see Listen for Data Layer Events.
あなたはリスナをサービスとして実装することもできます。さらなる情報はListen for Data Layer Eventsを参照してください。

#ここまで引用、翻訳

 

引用元の記事の著作権Googleに帰属しており、Creative Commons — Attribution 2.5 Generic — CC BY 2.5によってライセンスされています。そのためこの翻訳文も同ライセンスでライセンスされます。また、翻訳文を追加しています。

Android - ドラッグで並び替えられるListView(DragSortListView)の簡単な使い方

Androidアプリを作っていると、ListViewのアイテムをドラッグして並び替えたいということはよくあるし、実際そのような実装を行ってるアプリはいくつもありますが、そういった機能を持ったListViewは標準では存在しません(API Level 22時点)。

 

そこで実際にドラッグでアイテムを並び替えできる機能を持っているアプリがどうしてるのかと思って、ためしにGoogle Play Musicのアプリの「オープンソースライセンスについて」を見てみました。

そうすると「DragSortListView」というライブラリが見つかりました。絶対これです。

 

DragSortListView:

bauerca/drag-sort-listview · GitHub

 

DragSortListViewでggってみると、いくつか記事が見つかり、中には実装方法を解説しているブログも見つかりましたが、なんだか複雑なことをしていてとっつきにくそうです。ライブラリのGitHubの公式ページのREADMEにある程度の情報はありますが、なにせ英語ですので正しい理解ができているか不安です。

 

見つかった記事:

DragSortListViewを使ってみる | だんごやのプログラム開発の備忘録

ListViewでドラッグ&ドロップ - yokkongの日記

 

もっと簡単に、一番シンプルな形の実装が知りたいのですが、日本語で見つからないので、英語で(主にStackOverflow狙いで)検索を掛けてみると、みごとに見つかりました。最小限の使い方。

これを見ればだいたい使い方がわかると思います。

java - Bauerca drag-sort-listview simple example - Stack Overflow

 

日本語版のStackOverflowが出来ましたが、まだまだ英語版のほうが情報が豊富ですね。ユーザーの数も投稿数もかなわないので当然といえば当然ですが。

Theme.Translucentを使わずに半透明なActivityを作る。

Androidで半透明なActivityを作る場合、styles.xmlを編集し、Theme.Translucentを継承したスタイルを作ると良いです。というような記事がたくさん見つかります。

Activityを透過する « Tech Booster

これは誤りではなく、たしかに半透明にすることができます。

しかし、継承元のスタイルをTheme.Translucentにしてしまった場合、ActionBarのスタイルやデフォルトのActivityの背景色、文字色などが変わってしまいます。

せっかくオリジナルなstyleを作ったり、気に入ったstyleがあるのに、半透明にしたいがためにそれらが使えなくなってしまうのは残念だと思っていました。

そこでTheme.Translucentの実装を確認すると、このようになっていました。

<style name="Theme.Translucent">
<item name="windowBackground">@color/transparent</item>
<item name="colorBackgroundCacheHint">@null</item>
<item name="windowIsTranslucent">true</item>
<!-- Note that we use the base animation style here (that is no
animations) because we really have no idea how this kind of
activity will be used. -->
<item name="windowAnimationStyle">@style/Animation</item>
</style>

4行目に注目してください。それらしい属性があります。

 

試しに現在適用しているテーマにこの属性を追加してみると、普通に半透明にできました。

つまり、この属性さえあれば良いのです。

 

今のテーマを使いつつ、背景色を半透明にしたい場合、現在のstyles.xmlを次のように変更しましょう。

<resources>
<!-- Color -->
<drawable name="translucent_background">#80000000</drawable>
<!-- Custom Theme -->
<style name="AppTheme" parent="今利用しているテーマ">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@drawable/translucent_background</item>
</style>
</resources>

 

translucent_backgroundに指定した色・透明度がこのテーマの背景に反映されます。

 

テーマの指定方法や、styles.xmlに関する詳細な説明はこちら等をご参照ください。

アクションバーをカスタマイズする [Androidアプリのプログラミング] All About

Android StudioでGradleのExecuting Tasksが終わらない

Android StudioでWear向けのアプリをテストしてみようと思い、新規プロジェクト作成からWearにもチェックを入れて、ハンドヘルドもWearもどっちもBlank Activityを選択し、Finishをクリックしてプロジェクトを作成していたのだが、プロジェクト作成後のビルドがいつまで経っても終わらない。

外出前に始めて、1時間ほど放置してから見てみたが、終わる気配なし。仕方なくstopしようとするが、止められない・・

やむを得ずAndroid Studioを強制終了。

再起動してみると、一見問題ないように見えるが、リソースがちゃんと作成されていなかったりするのでSync Project width Gradle Filesをしてみると、また「Gradle: Executing Tasks」が終わらない・・。

原因を調べようと検索するも、見つからず・・。

 

と諦めかけた時に、神サイトStack Overflowに同じような質問があり、そこにあった解答がそのまま解決につながったのでメモしておきます。

How to fix Android Studio getting stuck executing Gradle tasks? - Stack Overflow

この回答者さんも、原因はわかっていないよう。

 

とりあえずの解決策としては、Android Studioの[File] > [Settings]の順に辿って設定画面を表示。そこの絞込検索窓に「Gradle」と入力し、表示されるGradleの設定画面にある「Offline work」にチェックを入れる。

f:id:kittohosi:20150801222204p:plain

そうするとExecuting Tasksが終わらないということはなく、ちゃんとビルドできた。

failed to convert @drawable/hoge into a drawableへの対処法

Androidの画面をXMLで組んでいて、ImageViewなどにdrawableに画像を指定した時に「failed to convert @drawable/hoge into a drawable」というエラーメッセージがでることがある。

 

結論から言えば、ファイル名が悪い。

"-"(半角ハイフン)をファイル名にいれていないか?

もしくは数字で初めていないか?

こういう規則もちゃんと覚えてないと、実際につまずいた時にあれ?ってなる。

 

ということで、そういうときはファイル名を見なおしてみましょう。