Blog -
などなど。
そして作りました。
PC側は流石に、VisualStudioで作りこんでいます。
なかなか快適にオンラインクリップボード出来ますが
GAEが重量課金の為、リリース無料でやるのはちょっと厳しい。
月額にして使ってくれるんだろうか、悩むところ。
寄付もまた微妙だし。
こんな追加機能付けたら行けるだろうか
・特定のパスワードをコピーすると、振動や音で居場所を知らせてくれる(無くした時の発見対策)
GPS起動して座標特定するとか。
・特定のコマンドをコピーすると、写真を撮るとか(画像送信未対応ですが)
・画像のやり取りに対応するとか。
アプリは出来てるんだけど、これは身内で使うかな。
TextViewで便利なのはEllipsizingです。
文字列が長ければ末尾に...を付けたり、中間を...に置き換えたりと
いろいろ自動でやってくれますが、これはSingleLineに限っての事
複数行の最後を...に置き換える事は出来ません、検索してもバグなのか仕様なのか
実現する必要性が有ったので、とりあえずTextViewをExtendsして作ってみた
(外国のサイトを参考に修正)
※動作を保障する物ではありません。
public class EllipsizingTextView extends TextView {
private static final String ELLIPSIS = "…";
private static final Pattern DEFAULT_END_PUNCTUATION = Pattern.compile("[\\.,…;\\:\\s]*$", Pattern.DOTALL);
public interface EllipsizeListener {
void ellipsizeStateChanged(boolean ellipsized);
}
private final List<EllipsizeListener> ellipsizeListeners = new ArrayList<EllipsizeListener>();
private boolean isEllipsized;
private boolean isStale;
private boolean programmaticChange;
private String fullText;
private int maxLines;
private float lineSpacingMultiplier = 1.0f;
private float lineAdditionalVerticalPadding = 0.0f;
/**
* The end punctuation which will be removed when appending #ELLIPSIS.
*/
//private Pattern endPunctuationPattern;
public EllipsizingTextView(Context context) {
this(context, null);
}
public EllipsizingTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
super.setEllipsize(null);
TypedArray a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.maxLines });
setMaxLines(a.getInt(0, Integer.MAX_VALUE));
setEndPunctuationPattern(DEFAULT_END_PUNCTUATION);
}
public void setEndPunctuationPattern(Pattern pattern) {
//this.endPunctuationPattern = pattern;
}
public void addEllipsizeListener(EllipsizeListener listener) {
if (listener == null) {
throw new NullPointerException();
}
ellipsizeListeners.add(listener);
}
public void removeEllipsizeListener(EllipsizeListener listener) {
ellipsizeListeners.remove(listener);
}
public boolean isEllipsized() {
return isEllipsized;
}
@Override
public void setMaxLines(int maxLines) {
super.setMaxLines(maxLines);
this.maxLines = maxLines;
isStale = true;
}
public int getMaxLines() {
return maxLines;
}
public boolean ellipsizingLastFullyVisibleLine() {
return maxLines == Integer.MAX_VALUE;
}
@Override
public void setLineSpacing(float add, float mult) {
this.lineAdditionalVerticalPadding = add;
this.lineSpacingMultiplier = mult;
super.setLineSpacing(add, mult);
}
@Override
protected void onTextChanged(CharSequence text, int start, int before,
int after) {
super.onTextChanged(text, start, before, after);
if (!programmaticChange) {
fullText = text.toString();
isStale = true;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (ellipsizingLastFullyVisibleLine()) {
isStale = true;
}
}
public void setPadding(int left, int top, int right, int bottom) {
super.setPadding(left, top, right, bottom);
if (ellipsizingLastFullyVisibleLine()) {
isStale = true;
}
}
@Override
protected void onDraw(Canvas canvas) {
if (isStale) {
resetText();
}
super.onDraw(canvas);
}
private void resetText() {
//String workingText = fullText;
StringBuilder workingText = new StringBuilder(fullText);
boolean ellipsized = false;
Layout layout = createWorkingLayout(fullText);
//Layout layout = createWorkingLayout(workingText);
int linesCount = getLinesCount();
if (layout.getLineCount() > linesCount) {
// We have more lines of text than we are allowed to display.
//workingText = fullText.substring(0, layout.getLineEnd(linesCount - 1)).trim();
workingText = new StringBuilder(fullText.substring(0, layout.getLineEnd(linesCount - 1)));
//workingText = fullText.substring(0, layout.getLineEnd(linesCount - 1));
while (createWorkingLayout(workingText.toString() + ELLIPSIS).getLineCount() > linesCount) {
//int lastSpace = workingText.lastIndexOf(' ');
// int lastSpace = workingText.length() - 1;
//if (lastSpace == -1) {
if ((workingText.length() - 1) == -1) {
break;
}
//workingText = workingText.substring(0, lastSpace - 1);
workingText.delete(workingText.length() - 1, workingText.length());
}
// We should do this in the loop above, but it's cheaper this way.
if(workingText.length() > 2){
//workingText = workingText.substring(0, workingText.length() - 2);
workingText.delete(workingText.length() - 2,workingText.length());
}
//workingText = endPunctuationPattern.matcher(workingText).replaceFirst("");
//workingText = workingText + ELLIPSIS;
//workingText = new StringBuilder(endPunctuationPattern.matcher(workingText.toString()).replaceFirst(""));
workingText.append(ELLIPSIS);
ellipsized = true;
}
if (!workingText.equals(getText())) {
programmaticChange = true;
try {
setText(workingText);
} finally {
programmaticChange = false;
}
}
isStale = false;
if (ellipsized != isEllipsized) {
isEllipsized = ellipsized;
for (EllipsizeListener listener : ellipsizeListeners) {
listener.ellipsizeStateChanged(ellipsized);
}
}
}
/**
* Get how many lines of text we are allowed to display.
*/
private int getLinesCount() {
if (ellipsizingLastFullyVisibleLine()) {
int fullyVisibleLinesCount = getFullyVisibleLinesCount();
if (fullyVisibleLinesCount == -1) {
return 1;
} else {
return fullyVisibleLinesCount;
}
} else {
return maxLines;
}
}
/**
* Get how many lines of text we can display so their full height is visible.
*/
private int getFullyVisibleLinesCount() {
Layout layout = createWorkingLayout("");
int height = getHeight() - getPaddingTop() - getPaddingBottom();
int lineHeight = layout.getLineBottom(0);
return height / lineHeight;
}
private Layout createWorkingLayout(String workingText) {
return new StaticLayout(workingText, getPaint(),
getWidth() - getPaddingLeft() - getPaddingRight(),
Alignment.ALIGN_NORMAL, lineSpacingMultiplier,
lineAdditionalVerticalPadding, false /* includepad */);
}
@Override
public void setEllipsize(TruncateAt where) {
// Ellipsize settings are not respected
}
}
resetText()では表示時に表示対象の文字列を抜き出して後ろに...を足しています。
ただ、正確には日本語の場合フォントで比率などの計算をしないとずれるので2文字分引いている。
とりあえず動いたが、処理が遅いのでリファクタリングが必要かな。
※ご利用は自己責任で。
Androidで開発していると何かとListActivityを使いますが、画像を入れたり
ちょっとカッコよく表示したり、自作のLayoutを使いたくなります。
そこで、ArrayAdapterを作ってListActivityにセットしてさあと思いきやいくらクリックしても
onListItemClickが発生しないという事態になる方も多いはず。
原因は、ListActivityのItemにArrayAdapterが紐づいている訳ですので
ArrayAdapterで発生しない限り、ListActivityには通知されません。
そこで、発生させる方法として・・・
ArrayAdapter側に「OnClickListener」をインプリメントして「onClick」メソッドを追加します。
すると、ListActivityの「onListItemClick」が発生するようになります。
Androidアプリに共有機能などを付ける際に、onCreateでUriを貰って処理を行う
なんて動作を考えたりする、という事でやってみた。
画像をUriで受け取ってUriがNullだったらToastを出して終わらせる。
ACTION_SENDが渡って来たら、外部からの共有で来ている物
そう出なければアプリケーション内から呼び出して物
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.action_send); Uri mUri = null; String action = getIntent().getAction(); if (Intent.ACTION_SEND.equals(action)) { //ACTION_SENDで呼び出し Bundle bundle = getIntent().getExtras(); if (bundle != null) { mUri = (Uri)bundle.get(Intent.EXTRA_STREAM); } intent_flg = true; } else { //アプリケーション内から呼び出し Bundle bundle =getIntent().getExtras(); mUri = (Uri)bundle.get("data"); intent_flg = false; } if(mUri == null ){ if (Intent.ACTION_SEND.equals(action)) { Toast.makeText(getApplicationContext(),"Error", Toast.LENGTH_LONG).show(); finish(); } else { //Uriから画像を読み込み、ImageViewにセットする。 Intent intent = getIntent(); setResult(-1, intent); finish(); } } //UriからBitmap抽出やCopyなど }
もし受け取ったUriがNullだったら、Toastを出すかSetResultでエラーを返して finish()します。
ですが、これを実行するとエラーが発生します。
java.lang.RuntimeException: Unable to start activity
これは何故かというと 、finish()を実行した際にonDestroyが呼ばれた後にまたonCreateに戻り
finish()以降のコードが実行される為です、 onCreateが完了するとonDestroyが呼ばれて実行されます。
つまり、onDestroyはfinish()時にスケジュールされるだけだという事です。
エラーで回避したと思っていたが思わぬ処でエラーが発生しました。
その様な、ライフサイクルなのでメンバ変数などの初期化や扱いも注意しないと。
開発者にとって、リバースエンジニアリングは知的財産の侵害です。
最近では、オープンソースの大変ありがたい知識が豊富にありますが
それとはまた違った形でソースを守りたいというニーズが多様にあると思います。
特に商業的な開発は、内部に多数のブラックボックス化したいアクセスなども含みますし
新発明(あまり良い表現ではないですが)は守られて当然だと思います。
この辺は、モラルの問題もあると思いますがやはり苦労した物は守りたい物です。
AndroidのSDKは2.2からproguardという難読化のツールを使ってapkを難読化出来ます
利用すれば、完全で無いまでも解読にはそれなりの努力と投資が必要となります。
もうちょっとなんとかして欲しいなあと思いますが、Googleさんお願いします。
(OSがオープンで厳しいのは分かりますがなにとぞよろしく)
それと、もうひとつのタイトルのエラーですが
Eclipseでproguardを使うといろいろエラーが出ます、特にSDK15はお手上げでしたが
苦労の末、回避出来ました(といっても大した事ないですけど)ので備忘録として記事にします。
Proguard returned with error code 1. See console がコンソールに出てエラーになる場合
1.難読化対象のパッケージ、クラス内に参照先が無い物が含まれている。
2.プラグイン関係のエラー
3.コンパイル順(同名のパッケージ、クラスの参照が目的の物で無い)
4.バージョンが低い(proguard)
1の場合、proguard.cfgに対象として含めない様に設定する。
(Twitter4jなど必要なlibだけ組み込む場合に参照先が無い場合がある)
-dontwarn twitter4j.**
これでこのパッケージは難読化対象では無くなる様です。
2の場合、eclipse.exe -cleanで回避出来る事が多い。
3の場合、ビルド・パス→ビルドパスの構成→順序およびエクスポートで該当パッケージの優先度を上げる
4の場合、自分はこれだったんですが、SDK15の場合proguard4.4だとこれが発生するようです。
最新が4.6ですのでここで入手して(記事執筆時点では4.7が最新)解凍後のlibフォルダの中身を
Android SDKのフォルダtools/proguard/libへ上書きします。
これで回避出来ました。
WidgetのImageViewに対してServiceからRemoteViewを経由して
setImageViewBitmapを更新する処理の開発を行っている時にどうもカクカク(処理遅延の様な)したり更新されなかったりする時が有る。
そんな時LOGには
!!! FAILED BINDER TRANSACTION !!!
が連発している事が多いですが、その原因と対処法について
原因は、RemoteViewのsetImageViewBitmapを呼び出すと引数のBitmapを
シリアル化して渡す、その時のメモリが累積してIPCの許容を超えると発生します。
1.RemoteViewをService内で使いまわしている
2.大きな画像をBitmapに指定している
対処法として
1の場合は、
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.レイアウトID);
といったように毎回定義しなおすと回避できる。
2の場合は、
Bitmapのサイズを小さくするもしくは、Bitmap.Config.RGB_565を指定する
アルファチャネルが使えないのが痛いが、背景ならなんとか。
参考までに、2*2のサイズのPNGは大丈夫でした。
また、Eclipseのデバッグ(実機)などで何をしても発生する場合、実機のAPKを一度アンインストールすると直る事が有ります。
(自分はこれにハマりました)
内容は、Bluetoothを使った物になります。
詳しくは後ほど。
しかし、iPhoneもこの辺とまりでしょうね。
ハードまで握ったらPC98と同じ事になりますね
デザインとアイデアが、昔のNECよりはあるので
好きに人にはこのままの人気で行くでしょうが
開発の場に上がってくるのはAndroidになって来るでしょうね。
今のうちに習得しておきたいところです。
内容は、ANR keyDispatchingTimedOutイメージ的には、ディスパッチャが
何かを割り当てようとした時にタイムアウトしている様な意味かな?
早速、ログを解析するもまったく意味が解らない(笑)
メインのActivityで発生している模様なのは解った
デバックを実機(2.2)からエミュレータ(1.6)に変えてやってみるもアプリ画面が表示される前に「応答しません」という警告が出る
実機だと強制終了になるっぽい
デバッグで場所を確認したいが場所が判断出来ない、んー困った。
デバックのログを解析するとどうやらOnCreateで発生している模様
初期設定に重い処理(マシン依存)があるとタイムアウトが発生するのか?と仮定して、この処理をコメントアウトする。
と、エラーが出ない。場所を特定出来たので方法を考える。
何かしらOSに返さないとエラーを出すらしい。
この処理は、レイアウトのLISTVIEWに対してなので場所は変えられない
そこで、プログレスダイアログを考えるこれなら、ダイアログ自体と
処理はOSとやり取りする為タイムアウトが発生しないかも知れない
早速、重い処理を開始する前にプログレスダイアログを表示して
待ち合わせをする様に変更すると問題解消、非同期の重い処理は
これをやらないと危ない。
特に(ユーザには悪いが)Xperiaはスペックが良くないので発生する様だ
実機を買わないと行けないのか、マーケットで売れたら考えるかな。
→ 後日分かった事は、onCreate及びonStartの処理に5秒以上掛かると発生するっぽいです。
ですので、メインのUIで重い処理をする時は、Threadで非同期処理もしくはAsysncTaskで切り分けましょう
カスタムROM内のAPNの情報は設定画面に表示されないだけで(SIMで表示制限しているのか解りませんが)
結構いっぱいあります、ODだと1200件、AuraxTSenseでは500件程度いろいろな設定に対応する為でしょうかね
そんなにいっぱい有るのかと驚きます(MMSの設定とか細かく存在する模様)全て消して標準のAPN入れても動くので
不要な情報がいっぱい有るようで気になります。SIM変える人には便利なんでしょうね。
NoSQLがどのくらいのレコード件数に耐えられるか解りませんが(後で調べます)思ったよりレスポンスが良くて驚きです
Select(カーソルで1件ずつ)は意外と掛かりますがUpdateは全件に掛けても意外と速い(一瞬です)
アプリケーションで使う時もSelectの仕方に注意すればレスポンスは問題無さそうです。
ついにAPNの情報を更新出来た。
という訳で、APNDroidと同じ方式で3G通信を止める事が出来ると思う。
さて、実機でやってみよう!バックアップも忘れない様に。
ShakeSwitch FREEと次期のShakeSwitch MODEに実装しようと思いますが
まずはFREE版に実装しよう。
しかしテーブル名だとかOSのソースから拾って来るんだろうか
OSのソースから解析するのが早いのかねえ。