タブレット用プログラムの書き止め

android OS & iPadOS の記録。

独り言。Android studio 4.1.2 突然ビルド不能に。

2022-02-23 21:52:02 | Android studio 日記

作業通常終了の次の日にいつも通りに起動すると・・。
サーバー証明書がどうとかでダイアログ表示。
プログラムも一部が赤字表記でビルドできない状態に。

検索でサーバー証明書なんたら調べると、

File > Settings.. > Tools > Server Certificates
Accept non trusted certificates automatically のスイッチオン。

で、再起動したけど変わらず。
別の起動しない検索で .idea の削除とか。.imlファイルの削除とか。とりあえず、ファイルだけ削除。
再起動して赤字は消えたけど、エミュレーターが起動しない。
通知のないアップデートとかあるのかな~。それが原因のように思えてしかたない。
SDKマネージャーで必要そうなバージョンのツールをインストール。エミュレーターも動きビルドできた。
時々、意味不明の起動不能は勘弁してほしい。

 

 

 

 

ついでの追記:RecyclerView.adapter とRunnableの組み合わせについて。

前々回のMyRunnableData クラスの注意点。

リストデータは、AdapterとRunnableの両方に実体を置く。(コピーして利用)
RecyclerViewのアイテム更新メソッドを使う場合は、Adapterデータの変更があった時に更新メソッドを使わせない措置を取る。

片方が処理を継続していて、片方が別の処理を始めた場合、参照タイプだとなくなった場所をアクセスして例外停止になる。別々にデータを持っていて別々に処理をしている場合は例外停止しにくい。
無くなってしまうもの、変更されてしまうものを両方で扱う場合は両方が完了するまでデータを変更・消去させない。あるいは片方を強制終了させる工夫が必要。


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

基礎。RecyclerViewのサムネイル画像の設定。作業ディレクトリとスレッド処理。

2022-02-19 02:16:10 | Android studio 日記

1、ディレクトリ内の画像ファイルのみ処理。
2、ディレクトリ内の子ディレクトリと画像ファイルの処理。

android OS4.2 実機テスト。

1は、Runnableに画像ファイルのリストと作業用ディレクトリを渡し、別スレッドで画像変換保存を実行。
RecyclerViewで十分なスクロール速度が出せた。作業用ディレクトリ内を毎回、ファイルの全てを削除しているがストレスを感じるほどの遅延はない。

2は、1と同じ方法と、前回の処理内容にディレクトリに張り付けるサムネイル画像を作業用ディレクトリとは別の場所に、アプリでは消さないキャッシュ保存を付け加えた。(システムで消される可能性あり)ディレクトリ用のアイコン画像は消さずに再利用させる。実機テストでは、ディレクトリ用アイコン画像の保存が有ると無しではかなりの差があった。内部に数十のディレクトリの存在だと流石にね^^;

例えば、現在のディレクトリに画像の入ってる子ディレクトリがたくさんある。その中の1つの子ディレクトリに入り、元の場所に戻ってきたときに、入る前に見ていたディレクトリ達のアイコン画像は保存されているので、読み込むだけですぐに表示ができる。これが保存されていなければ、また1から作って表示となるので、その分のモタツキが気になる。
画像ファイルと違ってディレクトリ数は少なめだから保存し続けても問題は少ないだろう。
内容の画像が変わった時にアイコン画像は変わらないので別途のアイコン画像の更新機能は必要かな・・。
あと気になるところは、ディレクトリの取得した情報の使い回しとか。
バックグラウンドでディレクトリの情報収集ができたりするといいんだが・・・。

 

処理の流れ、

1、ディレクトリの子ディレクトリ、画像ファイル、情報取得と管理。(Fileが使いやすい。保存はString かな)
2、名前をソート。
3、画像だけなら、画像ファイルネームリスト使用。混在なら、別の配列にディレクトリを積み、次にファイルを積み配列1本にまとめる。同時に識別配列に識別子を本体と関連させて入れていく。配列2本作成。
4、RecyclerViewのアダプターにファイル名配列とキャッシュディレクトリを渡す。( 関数addAll() にて )
5、渡したときにスレッドに使うRunnableにもそれらの情報を渡して、スレッドを実行。
6、アダプターのonBindViewHolder()で画像、文字、タッチ処理を設定。キャッシュディレクトリ、ファイル名から、あるルールで保存されているキャッシュファイルの存在を確かめて、存在すれば読み込む。無ければ用意したリソースの画像を使うなど。

アダプターではキャッシュが有るか無いかの判断だけで良いと思う。スレッドで画像処理し、キャッシュに保存して、そのポジションをアダプターに通知する。アダプターは受け取った更新通知からもう一度そのポジションだけをonBindViewHolder()で再描画している?はず。分担できるくくりで分けた方が簡単みたい。

スレッド処理。
5-1、受け取った情報の確認。
5-2、作業用ディレクトリのファイルを全て削除。
5-3、リストから入力側ファイルと出力側ファイルを構築確認。
5-4、出力ファイルが出力場所に存在するか確認。存在すれば次のリストアイテムへ。
5-5、入力側ファイルからbitmapを作成、画像サイズを調整。
5-6、bitmapを保存ファイルに出力。
5-7、アダプターへ更新通知。(新規保存したものについて通知)

「5-3」~「5-7」をリスト数分繰り返す。

 

最初はごてごてしてぐちゃぐちゃになっていたけど、試行錯誤してスッキリした形になった。
分かりやすく他のものにも応用できそう。無駄を省いて効率を上げられたらもっと速くなるかな。

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

基礎。RecyclerView サムネイル画像の設定。反応速度を考察。(前編)

2022-02-18 20:36:10 | Android studio 日記

前回の作業ディレクトリ、画像ファイルだけのRecyclerView 使用は、1つのディレクトリに数十枚の画像であれば、十分の処理速度でストレスは感じられない。
別スレッドでのサムネイル作成では、1枚完了ごとにRecyclerViewのサムネイル画像を更新するから作業が止まって見える事はない。

今回は、1つのディレクトリに子ディレクトリとファイルを混在したサムネイルを表示するRecyclerViewの構築。
ファイル部分は前回のを利用。ディレクトリとファイルのリストを別に用意して、先にディレクトリ、後にファイルのリストで合成。
本体リストと別に識別用リストの2本を用意。

ディレクトリのアイコンはリソースからの設定が一般的だと思う。しかし、ディレクトリの中に画像が有ったら、それを使いたい。
これが意外と面倒で時間がかかる。だいぶ前のキャッシュディレクトリにディレクトリの構造をそのまま構築するのは利用価値が大きく有用。ただ、ストレージの領域を浪費する。
作業ディレクトリで1つのディレクトリのキャッシュ情報を使い捨てするともったいない。使用頻度で再利用とかできたらいいんだけれど。
ディレクトリのアイコンだけも特別に保存する方法を考えないと時間短縮は望めなさそう。今後の課題。

とりあえず、ディレクトリ内の画像をアイコンに使用する草案。


RecyclerViewのアダプターとRunnableで他は省略。

public class MyDirectoryListAdapter extends RecyclerView.Adapter< MyDirectoryListAdapter.ViewHolder> {

    public static class MyRunnableData { // Runnable データ引き渡し用クラス
        private File mTargetDir = null;
        private File mCacheDir = null;
        private List< String> mListType = null;
        private List< String> mListName = null;
        private RecyclerView myRecyclerView = null;

        MyRunnableData( File tDir, File cDir, List< String> lt, List< String> ln ) {
            mTargetDir = tDir;
            mCacheDir = cDir;
            mListType = new ArrayList<>(lt);
            mListName = new ArrayList<>(ln);
        }
        public File getTargetDir() { return mTargetDir; }
        public File getCacheDir() { return mCacheDir; }
        public List< String> getListType() { return mListType; }
        public List< String> getListName() { return mListName; }

        public void setNotifyItemChanged( int val ) {
            if ( myRecyclerView != null ) {
                myRecyclerView.post(new Runnable() {
                    @Override
                    public void run() {
                        MyDirectoryListAdapter adapter = (MyDirectoryListAdapter) myRecyclerView.getAdapter();
                        if (adapter != null) adapter.notifyItemChanged(val);
                    }
                });
            }
        }

        public void setRecyclerView(RecyclerView myRecyclerView) {
            this.myRecyclerView = myRecyclerView;
        }

        public void clear() {
            mListType.clear();
            mListType = null;
            mListName.clear();
            mListName = null;
        }
    }
    private MyRunnableData myRunnableData = null;

// アダプター本体

    private MyMainData mMainData = null;
    private LayoutInflater myInflater = null;
    private RecyclerView myRecyclerView = null;
    private View.OnClickListener myListener = null;

    private List< String> listType = null;
    private List< String> listName = null;
    private File myCacheDir = null;

    MyDirectoryListAdapter( MyMainData mainData ) {
        mMainData = mainData;
        myInflater = LayoutInflater.from( mainData.getContext() );
        myRecyclerView = null;
        listName = new ArrayList<>();
        listType = new ArrayList<>();

        setTempBufferDir( new File( mainData.getContext().getCacheDir(), "tempThumb" ) );
    }

  省略

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

        Bitmap bitmap = loadImage( position );
        if (bitmap != null)
            holder.imageView.setImageBitmap(bitmap);

        holder.textView.setText( listName.get(position) );
        holder.linearLayout.setId( holder.getAdapterPosition() );
        holder.linearLayout.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myListener.onClick(v);
            }
        });
    }

    public Bitmap loadImage( int position ) {
        Bitmap bitmap = null;
        if ( listType.get(position).equals("D") ) { // 識別:ディレクトリ
            String name = listName.get(position) + ".jpg";
            File cacheFile = new File( myCacheDir, name );
            if (cacheFile.exists()) {
                bitmap = BitmapFactory.decodeFile( cacheFile.getAbsolutePath() );
            }
            return ( bitmap != null )? bitmap:
                    BitmapFactory.decodeResource(mMainData.getContext().getResources(), R.drawable.folder );
        }

        if ( listType.get(position).equals("F") ) { // 識別:ファイル
            File file = new File( myCacheDir, listName.get(position) );
            if (file.exists())
                bitmap = BitmapFactory.decodeFile( file.getAbsolutePath() );

            return ( bitmap != null )? bitmap:
                    BitmapFactory.decodeResource(mMainData.getContext().getResources(), R.drawable.not_find);
        }
        return null;
    }

    public void addAll( MyDirData dirData ) {
        clear();
        listType.addAll( dirData.getDirTypeListForAdapter() );
        listName.addAll( dirData.getDirNameListForAdapter() );

        if ( myRunnableData != null ) myRunnableData.clear();

        myRunnableData = new MyRunnableData( dirData.getTargetDir(), myCacheDir, listType, listName );
        myRunnableData.setRecyclerView(myRecyclerView);

        new Thread( new MyDirectoryIconRunnable( myRunnableData ) ).start();
        notifyDataSetChanged();
    }

    public void setNotifyItemChanged( int position ) {
        if ( position < 0 || position >= listName.size() ) return;
        notifyItemChanged( position );
    }
    public void setTempBufferDir( File dir ) {
        if ( !dir.exists() && !dir.mkdirs() ) return;
        myCacheDir = dir;
    }

  省略

}


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

基礎。RecyclerView サムネイル画像の設定。反応速度を考察。(後編)

2022-02-18 20:31:16 | Android studio 日記

// Runnable 本体

MyDirectoryListAdapter.MyRunnableDataクラスをデータ受け渡しに使っている。
全てが有効に使えるか分からない。テストを繰り返して使える事を探す。

public class MyDirectoryIconRunnable implements Runnable {
    private File mTargetDir = null;
    private File mCacheDir = null;
    private List< mString> mItemeNames = null;
    private List< String> mItemType = null;
    private static final int DIR_SAVE_SIZE = 96; // ディレクトリとファイルのサイズを変えている
    private static final int FILE_SAVE_SIZE = 160;
    private int saveSize;
    private MyDirectoryListAdapter.MyRunnableData mDirectoryListAdapter = null;

    MyDirectoryIconRunnable( MyDirectoryListAdapter.MyRunnableData rData ) {
        mTargetDir = rData.getTargetDir();
        mItemType = rData.getListType();
        mItemeNames = rData.getListName();
        mCacheDir = rData.getCacheDir();
        mDirectoryListAdapter = rData;
    }

    @Override
    public void run() {
        if (mCacheDir == null || !mCacheDir.exists() || !mCacheDir.isDirectory()) {
            finish();
            return;
        }

        File[] files =  mCacheDir.listFiles(); // 作業ディレクトリ内のファイル削除
        if ( files != null && files.length != 0 ) {
            boolean b;
            for ( File file: files ) {
                b = file.delete();
            }
        }

        for (int i = 0; i < mItemeNames.size(); i++) {
            switch ( mItemType.get(i) ) {
                case "D":
                    dirThumbnaile(i);
                    break;
                case "F":
                    fileThumbnaile(i);
                    break;
                default:
            }
            mDirectoryListAdapter.setNotifyItemChanged(i); // 何かの加減で問題が発生するかも
        }

        finish();
    }

    private void dirThumbnaile(int index) {
        String name = mItemeNames.get(index) + ".jpg";
        File cacheFile = new File( mCacheDir, name );
        if (cacheFile.exists()) return; // キャッシュファイルが有る

        File childDir = new File( mTargetDir, mItemeNames.get(index) );
        MyDirectory_FileCheck childDirData = new MyDirectory_FileCheck( childDir );// 画像ファイル1枚だけ取り出す

        File imageFile = childDirData.getImageFile(); // 取り出した1枚のFile
        if ( imageFile == null )
            makeThumbnailToCache( null, cacheFile ); // dummy を作る
        else {
            saveSize = DIR_SAVE_SIZE;
            makeThumbnailToCache(imageFile, cacheFile);// アイコン用サムネイル画像の変換と保存
        }
    }

    private void fileThumbnaile(int index ) {
        String name = mItemeNames.get(index);
        File cacheFile = new File( mCacheDir, name ); // create cache file
        if ( cacheFile.exists() ) return;

        File souDir = new File( mTargetDir, name );
        saveSize = FILE_SAVE_SIZE;
        makeThumbnailToCache( souDir, cacheFile ); // 子ディレクトリ内の画像ファイル & キャッシュファイル
    }

    private void makeThumbnailToCache( File souFile, File cacheFile ) {
        if ( souFile == null ) {
            try {
                boolean flag = cacheFile.createNewFile();
            } catch (Exception ignored) {
            }
            return;
        }
        Bitmap bitmap = thumbnaileImage( souFile ); // ソース画像ファイルからサムネイル画像を作成
        if ( bitmap != null && cacheFile != null ) {// サムネイル画像をキャッシュに保存
            try {
                FileOutputStream fos = new FileOutputStream( cacheFile );
                bitmap.compress( Bitmap.CompressFormat.JPEG, 100, fos );
                fos.close();
            } catch (IOException e) {
                return;
            }
            if ( !bitmap.isRecycled() ) bitmap.recycle();
        }
    }

    public Bitmap thumbnaileImage( File file ) {
        if ( file == null || !file.exists() || !file.isFile() ) return null;

        Bitmap scaleBitmap = null;
        try {
            BitmapFactory.Options op = new BitmapFactory.Options();
            op.inJustDecodeBounds = true;

            InputStream istream = new FileInputStream( file );
            BitmapFactory.decodeStream( istream, null, op );

            op.inSampleSize = calculateInSampleSize( op, saveSize, saveSize );
            op.inJustDecodeBounds = false;
            istream.close();
            istream = new FileInputStream( file );
            scaleBitmap = BitmapFactory.decodeStream( istream, null, op );
            istream.close();
            if ( scaleBitmap == null ) return null;

            Matrix matrix = new Matrix();
            float mag = calcMag( scaleBitmap.getWidth(), scaleBitmap.getHeight(),saveSize, saveSize );
            matrix.postScale( mag, mag );
            scaleBitmap = Bitmap.createBitmap( scaleBitmap, 0, 0, scaleBitmap.getWidth(), scaleBitmap.getHeight(), matrix, true);
        } catch (IOException e) {
            return null;
        }
        return scaleBitmap;
    }

    public float calcMag( int souX, int souY, int desX, int desY ) {
       省略

    }

    public int calculateInSampleSize(BitmapFactory.Options op, int reqWidth, int reqHeight ) {
        省略

    }

    public void finish() {
        mItemType.clear();
        mItemType = null;
        mItemeNames.clear();
        mItemeNames = null;
        mTargetDir = null;
        mCacheDir = null;
        mDirectoryListAdapter = null;
    }
}


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

基礎。作業ディレクトリ(キャッシュファイル)の利用について。

2022-02-17 22:21:27 | Android studio 日記

前回の複数の作業ディレクトリの切り替え利用は、整合性に問題が出て、解決も難しいので今後の課題とした。
色々と考える中でRecyclerViewのAdapterの中で、キャッシュ確保とスレッドスタートでも問題なく動くのか?
試しにやってみようと組んでみた。
アダプタ用のリストを受け取った時にリストと作業用ディレクトリをスレッドへ渡してスタート。
スレッド内で作業用ディレクトリをクリア、リストの画像ファイルをサムネイル画像にして作業用ディレクトリへ保存。
複数ディレクトリの時の例外ストップも出ず、いい感じで動いている。
実機テストで良い結果(速度)が出るといいけど、ディレクトリ移動モードのキャッシュ部分を組み込んでからのお楽しみ。

先ず、アダプタから。部分省略。


public class MyThumbnailAdapter extends RecyclerView.Adapter< MyThumbnailAdapter.ViewHolder> {

    private Context myContext = null;
    private List< String> myImageAbsPaths = null;
    private File myCacheDir = null;
    private MyThumbnailRunnable myThumbnailRunnable = null;

    public MyThumbnailAdapter( Context context ) {
        myContext = context;
        myInflater = LayoutInflater.from( context );
        myImageAbsPaths = new ArrayList<>();

        setTempBufferDir( new File( context.getCacheDir(), "tempThumb" ) );
    }

// 省略 //


    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        if ( myImageAbsPaths == null ) return;

        File file = new File( myImageAbsPaths.get(position) );
        Bitmap bitmap = loadImage( file );
        if (bitmap != null)
            holder.imageView.setImageBitmap(bitmap);
        else
            holder.imageView.setImageBitmap( BitmapFactory.decodeResource( myContext.getResources(), R.drawable.not_find) );

        holder.textView.setText( String.valueOf( position + 1 ) );
        holder.linearLayout.setId( holder.getAdapterPosition() );
        holder.linearLayout.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myListener.onClick(v);
            }
        });

    }


    public Bitmap loadImage( File file ) {
        if ( file == null || !file.exists() || !file.isFile() ) return null;

        File cf = new File( myCacheDir, file.getName() );
        Bitmap scaleBitmap = null;
        if ( cf.exists() ) {
            scaleBitmap = BitmapFactory.decodeFile(cf.getAbsolutePath());
        } else {

        // 省略 //
        }
        return scaleBitmap;
    }

    public void addAll( List< String> imageAbsPaths ) {
        myImageAbsPaths.clear();
        myImageAbsPaths.addAll( imageAbsPaths );
 
        myThumbnailRunnable = new MyThumbnailRunnable( myImageAbsPaths, myCacheDir );
        new Thread( myThumbnailRunnable ).start();
        notifyDataSetChanged();
    }

    public void setTempBufferDir( File dir ) {
        if ( !dir.exists() && !dir.mkdirs() ) return;
        myCacheDir = dir;
    }

    // 省略 //
}

 

スレッド用のRunnable。

public class MyThumbnailRunnable implements Runnable {
    private List< String> mFileAbsPathList = null;
    private File mCacheDir = null;

    MyThumbnailRunnable( List< String> absList, File cachedir ) {
        mFileAbsPathList = new ArrayList<>( absList );
        mCacheDir = cachedir;
    }

    @Override
    public void run() {
        if ( mCacheDir == null || mFileAbsPathList.size() == 0  ) {
            finish();
            return;
        }

        clearBuffer();
        createThumbnail();
        finish();
    }

    private void clearBuffer() {
        File[] files =  mCacheDir.listFiles();
        if ( files != null && files.length != 0 ) {
            boolean b;
            for ( File file: files ) {
                b = file.delete();
            }
        }
    }

    private void createThumbnail() {
        File cfile, souFile;
        for ( int i = 0; i < mFileAbsPathList.size(); i++ ) {
            souFile = new File( mFileAbsPathList.get(i) );
            cfile = new File( mCacheDir, souFile.getName() );
            if ( !cfile.exists() )
                bitmapToThumbnaile( souFile, cfile );
        }
    }


    // 省略 //
}

バッファ利用でバッファ処理が重ならないのでフラグが省ける。
画像の枚数が少なければ処理時間も短い。
ディレクトリ移動モードはどうだろう・・。

 

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする