Activity 透明做弹窗时,退出动画无效

本文介绍了一种简单的方法来移除Android应用中Activity关闭时的默认动画效果,通过重写Activity的finish方法并调用overridePendingTransition方法实现。
 样式:
 <style name="ActionSheetAnimation" parent="@android:style/Animation.Dialog">

      <item name="android:windowEnterAnimation">@anim/push_up_in</item>

      <item name="android:windowExitAnimation">@anim/push_up_out</item>-->

 </style>


解决方案:

重写Activity finish()方法,在该方法中加上overridePendingTransition(0,0);即可去掉干扰的动画
@Override

public void finish() {

    super.finish();

    overridePendingTransition(0,0);

}

public class MainActivity extends AppCompatActivity { private static final String TAG = "camera2api"; private static final int REQUEST_CAMERA_PERMISSION = 100; private TextureView textureView; private Button captureButton; private CameraDevice cameraDevice; private CameraCaptureSession cameraCaptureSession; private CaptureRequest.Builder captureRequestBuilder; private String cameraId; private Handler backgroundHandler; private boolean isSessionClosed; private HandlerThread backgroundThread; private ImageReader imageReader; private boolean isTextureAvailable = false; // 跟踪Surface状态 private CameraManager manager; private volatile boolean isCapturing = false; private StreamConfigurationMap map; private long lastClickTime = 0; private static final long MIN_CLICK_INTERVAL = 1000; // 最小点击间隔1秒 private ContentResolver resolver; private ContentValues values; private Uri imageUri; private Surface previewSurface; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "onCreate: Activity创建————————"); textureView = findViewById(R.id.textureView); captureButton = findViewById(R.id.btnCapture); //初始化相机参数 initCamera(); } @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume——————————"); // 检查相机权限 // ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA):用于检测this(当前应用)是否有Manifest.permission.CAMERA权限 //有则返回PackageManager.PERMISSION_GRANTED(有权限),没有则返回PackageManager.PERMISSION_DENIED(无权限) if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "没有相机权限——>开始请求相机权限"); //ActivityCompat.requestPermissions():请求权限的方法 //this:当前应用 //new String[]{权限1,权限2,...}:请求的权限集合 //code:对应编码名,发送请求后会调用回调函数 onRequestPermissionsResult(),该回调的第一个参数就是这个code,用于给我们请求权限后的自定义操作。 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION); } //注意:以上的权限请求是异步操作 //设置预览监听 textureView.setSurfaceTextureListener(surfaceTextureListener); //设置拍照按钮监听 captureButton.setOnClickListener(v -> { long currentTime = SystemClock.elapsedRealtime(); if (currentTime - lastClickTime > MIN_CLICK_INTERVAL) {//防止 连点抛出异常 lastClickTime = currentTime; takePicture(); } else { Log.d(TAG, "点击过快,已忽略"); } }); //启动后台线程 startBackgroundThread(); // 如果Surface已经可用,重新打开相机 if (textureView.isAvailable()) { openCamera(); } } @Override protected void onPause() { super.onPause(); // 检查拍照状态,延迟释放避免冲突 if (isCapturing) { Log.w(TAG, "拍照中,延迟释放相机"); new Handler().postDelayed(() -> { if (!isCapturing) { closeCamera(); stopBackgroundThread(); } }, 1000); // 延迟1秒,实际需根据业务调整 } else { closeCamera(); stopBackgroundThread(); } } private final TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() { //触发机:SurfaceTexture 初始化后 @Override public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { Log.d(TAG, "SurfaceTexture初始完毕: SurfaceTexture的尺寸为:" + width + "x" + height); if (textureView.getWidth()!=width||textureView.getHeight()!=height){ surface.setDefaultBufferSize(width,height); } isTextureAvailable = true; // 设置可用标志 } @Override public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) { Log.d(TAG, "onSurfaceTextureSizeChanged: " + width + "x" + height); } @Override public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { Log.d(TAG, "onSurfaceTextureDestroyed"); isTextureAvailable = false; return false; } @Override public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { } }; private void initCamera() { Log.d(TAG, "initCamera: 初始化相机配置"); try { //1.初始化CameraManager,获取相机配置map manager = (CameraManager) getSystemService(CAMERA_SERVICE); cameraId = manager.getCameraIdList()[0]; Log.i(TAG, "使用相机ID: " + cameraId); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) { Log.e(TAG, "错误: StreamConfigurationMap为空!!"); } //2.图片保存参数配置 resolver = getContentResolver(); values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, "pic_" + System.currentTimeMillis() + ".jpg"); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES); } catch (CameraAccessException e) { Log.e(TAG, "相机访问异常: " + e.getMessage()); } catch (NullPointerException e) { Log.e(TAG, "NPE: " + e.getMessage()); } } private Size chooseOptimalSize(Size[] choices, int width, int height) { List<Size> bigEnough = new ArrayList<>(); for (Size option : choices) { float ratio = (float) option.getWidth() / option.getHeight(); float viewRatio = (float) width / height; if (Math.abs(ratio - viewRatio) <= 0.1 && option.getWidth() <= width && option.getHeight() <= height) { bigEnough.add(option); } } if (!bigEnough.isEmpty()) { return Collections.max(bigEnough, new CompareSizesByArea()); } Log.w(TAG, "未找到完美匹配尺寸,使用默认"); return choices[0]; } static class CompareSizesByArea implements Comparator<Size> { @Override public int compare(Size lhs, Size rhs) { return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()); } } private void openCamera() { if (!isTextureAvailable) { // 检查Surface是否可用 Log.w(TAG, "Surface不可用,延迟打开相机"); return; } Log.d(TAG, "openCamera: 尝试打开相机"); try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "1.打开相机: " + cameraId); manager.openCamera(cameraId, stateCallback, backgroundHandler); } else { Log.w(TAG, "相机权限未授予"); } } catch (CameraAccessException e) { Log.e(TAG, "打开相机失败: " + e.getMessage()); } catch (SecurityException e) { Log.e(TAG, "安全异常: " + e.getMessage()); } } private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { Log.i(TAG, "相机已打开"); cameraDevice = camera; Log.i(TAG, "2.1 开始配置预览流"); createCameraPreviewSession(); } @Override public void onDisconnected(@NonNull CameraDevice camera) { Log.w(TAG, "相机断开连接"); cameraDevice.close(); } @Override public void onError(@NonNull CameraDevice camera, int error) { Log.e(TAG, "相机错误: " + error); cameraDevice.close(); cameraDevice = null; } }; //2.1 开始配置预览流 private void createCameraPreviewSession() { if (cameraDevice == null || !isTextureAvailable) { // 双重检查 Log.e(TAG, "创建预览会话失败: 相机或Surface不可用"); return; } try { Log.i(TAG, "2.开始配流——配置预览流和拍照流"); //1.1 配置预览尺寸 Size previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), textureView.getWidth(), textureView.getHeight()); Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class); for (Size outputSize : outputSizes) { Log.i(TAG,"尺寸::————————————"+outputSize); } Log.i(TAG, "选择预览尺寸: " + previewSize.getWidth() + "x" + previewSize.getHeight()); //1.2 配置预览Surface SurfaceTexture previewTexture = textureView.getSurfaceTexture(); previewTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); previewSurface = new Surface(previewTexture); // 2.配置拍照Surface //优化尺寸选择(兼容性处理) 配置jpegSizes Size[] jpegSizes = map.getOutputSizes(ImageFormat.JPEG); Size captureSize = chooseOptimalSize(jpegSizes); Log.i(TAG, "使用拍照尺寸: " + captureSize.getWidth() + "x" + captureSize.getHeight()); // 优化:检查尺寸变化,仅当尺寸变更重建 if (imageReader == null || imageReader.getWidth() != captureSize.getWidth()) { if (imageReader != null) imageReader.close(); imageReader = ImageReader.newInstance( captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2 // 缓冲区数量 ); } // 3.配置双输出Surface(预览 + 拍照) List<Surface> outputSurfaces = new ArrayList<>(2); outputSurfaces.add(previewSurface); outputSurfaces.add(imageReader.getSurface()); //3.创建会话 cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { Log.i(TAG, "2.2 预览会话配置成功"); cameraCaptureSession = session; try { //3.下发请求添加预览流 captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.addTarget(previewSurface); //配置预览 自动对焦 captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); Log.i(TAG, "3.开始下发预览请求"); cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler); } catch (CameraAccessException e) { Log.e(TAG, "设置预览请求失败: " + e.getMessage()); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { Log.e(TAG, "预览会话配置失败"); Toast.makeText(MainActivity.this, "配置失败", Toast.LENGTH_SHORT).show(); } }, backgroundHandler); } catch (CameraAccessException e) { Log.e(TAG, "创建预览会话异常: " + e.getMessage()); } } // 优化尺寸选择 private Size chooseOptimalSize(Size[] choices) { // 优先选择16:9的比例 final double ASPECT_RATIO = 16.0 / 9.0; Size optimalSize = null; for (Size size : choices) { double ratio = (double) size.getWidth() / size.getHeight(); if (Math.abs(ratio - ASPECT_RATIO) <= 0.1) { if (optimalSize == null || size.getWidth() > optimalSize.getWidth()) { optimalSize = size; } } } // 若无匹配则选择最大尺寸 if (optimalSize == null) { for (Size size : choices) { if (optimalSize == null || size.getWidth() > optimalSize.getWidth()) { optimalSize = size; } } } return optimalSize != null ? optimalSize : new Size(1920, 1080); } private void saveImage(Image image) { // 1. 创建目标文件 ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); if (imageUri != null) { try (OutputStream os = resolver.openOutputStream(imageUri)) { os.write(bytes); Log.i(TAG, "图像保存成功, 大小: " + bytes.length + " bytes"); // 发送媒体扫描广播,写入系统相册 /storage/emulated/0/Pictures/pic_1735868378859.jpg Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); mediaScanIntent.setData(imageUri); sendBroadcast(mediaScanIntent); } catch (IOException e) { Log.e(TAG, "保存文件失败: " + e.getMessage()); } } } //触发机:用户点击授权后调用 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); Log.d(TAG, "权限请求结果: " + requestCode); if (requestCode == REQUEST_CAMERA_PERMISSION) { if (grantResults[0] == PackageManager.PERMISSION_DENIED) { Log.w(TAG, "用户拒绝相机权限"); Toast.makeText(this, "需要相机权限", Toast.LENGTH_SHORT).show(); finish(); } else { Log.i(TAG, "用户授予相机权限"); } } } private void startBackgroundThread() { if (backgroundThread == null) { backgroundThread = new HandlerThread("CameraBackground"); backgroundThread.start(); backgroundHandler = new Handler(backgroundThread.getLooper()); Log.d(TAG, "后台线程启动"); } } private void stopBackgroundThread() { if (backgroundThread != null) { Log.d(TAG, "停止后台线程"); backgroundThread.quitSafely(); try { backgroundThread.join(); backgroundThread = null; backgroundHandler = null; } catch (InterruptedException e) { Log.e(TAG, "停止线程失败: " + e.getMessage()); } } } private void closeCamera() { Log.d(TAG, "关闭相机资源"); if (isCapturing) { Log.w(TAG, "正在拍照中,等待完成或取消..."); // 可以尝试等待一段间或取消请求 try { cameraCaptureSession.abortCaptures(); // 取消所有进行中的捕获 } catch (CameraAccessException e) { throw new RuntimeException(e); } } if (cameraCaptureSession != null) { cameraCaptureSession.close(); cameraCaptureSession = null; } if (cameraDevice != null) { cameraDevice.close(); cameraDevice = null; } if (imageReader != null) { imageReader.close(); imageReader = null; } isSessionClosed = true; } private boolean checkTakePicture() { if (cameraDevice == null) { Log.w(TAG, "拍照失败: 相机未初始化"); return false; } // 1. 检查会话有效性 if (cameraCaptureSession == null) { Log.e(TAG, "拍照错误: CameraCaptureSession为空"); return false; } // 2. 检查后台Handler if (backgroundHandler == null) { Log.e(TAG, "拍照错误: backgroundHandler未初始化"); startBackgroundThread(); // 初始化方法见下方 return false; } if (isSessionClosed) { Log.e(TAG, "当前会话已关闭"); } return true; } private void takePicture() { Log.i(TAG, "4.开始拍照流程——————————"); try { //1.检查是否可以拍照 boolean checkFlag = checkTakePicture(); if (!checkFlag) { Log.i(TAG, "拍照流程————检查未通过!退出拍照!"); return; } // 2. 创建拍照请求 CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(imageReader.getSurface()); //3.设置拍照下发参数 //自动曝光 captureBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); // 图片旋转角度跟随手机默认 int rotation = getWindowManager().getDefaultDisplay().getRotation(); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, rotation); // 4. 图像可用监听器 imageReader.setOnImageAvailableListener(reader -> { Log.d(TAG, "图像数据可用"); try (Image image = reader.acquireLatestImage()) { if (image != null) { saveImage(image); } } catch (Exception e) { Log.e(TAG, "保存图像错误: " + e.getMessage()); } }, backgroundHandler); // 11. 拍照回调 CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { // 处理拍照完成 isCapturing = false; // 可以在这里进行后续操作,但注意检查资源是否仍然有效 super.onCaptureCompleted(session, request, result); Log.i(TAG, "拍照完成,保存至: " + imageUri); runOnUiThread(() -> Toast.makeText(MainActivity.this, "保存至: " + imageUri, Toast.LENGTH_SHORT).show() ); createCameraPreviewSession(); // 恢复预览 } @Override public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { super.onCaptureFailed(session, request, failure); Log.e(TAG, "拍照失败: " + failure.getReason()); } }; // 12. 执行拍照(添加会话状态检查) Log.d(TAG, "停止预览"); cameraCaptureSession.stopRepeating(); Log.d(TAG, "播放黑闪动画"); flash_mode(); Log.d(TAG, "4.下发拍照"); isCapturing = true; cameraCaptureSession.capture(captureBuilder.build(), captureCallback, backgroundHandler); } catch (CameraAccessException | IllegalStateException | SecurityException e) { Log.e(TAG, "拍照过程异常: " + e.getClass().getSimpleName(), e); } } //播放黑闪动画 private void flash_mode() { // 在拍照按钮点击事件中触发 View flashView = findViewById(R.id.flashview); flashView.setVisibility(View.VISIBLE); // 先显示黑色视图 // 创建动画透明度从0到1再到0,模拟闪烁 AlphaAnimation flashAnim = new AlphaAnimation(0.0f, 1.0f); flashAnim.setDuration(100); // 动画长100ms,根据需求调整 flashAnim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { flashView.setVisibility(View.INVISIBLE); // 动画结束后隐藏视图 // 在此处调用实际拍照逻辑(如Camera API) } @Override public void onAnimationRepeat(Animation animation) { } }); flashView.startAnimation(flashAnim); } } 首先那这个demo练手,来搞懂具体细节,有个问题,为什么onresume方法中授权是异步的,执行权限检查会有弹窗,但是如果我一直不点,那么 // 如果Surface已经可用,重新打开相机 if (textureView.isAvailable()) { openCamera(); }这段代码不就执行不了么,相机打不开,也就结束了,此程序算是结束了么
09-18
from appium import webdriver from appium.options.android import UiAutomator2Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.common.exceptions import TimeoutException, NoSuchElementException, ElementNotInteractableException import time # ==================== 目标应用配置 ==================== TARGET_PACKAGE = "com.brother.ptouch.iprintandlabel" TARGET_ACTIVITY = ".module.home.home.HomeActivity" # 主页 Activity APK_XPATH="C:/own/download/iPL5.3.10_signed.apk" MAX_RESTORE_ATTEMPTS = 2 MAX_HISTORY_REPEAT = 3 # ==================== 配置 Appium Options ==================== def create_driver(): options = UiAutomator2Options() options.set_capability("platformName", "Android") options.set_capability("deviceName", "emulator-5554") options.set_capability("automationName", "UiAutomator2") options.set_capability("noReset", False) options.set_capability("fullReset", False) options.set_capability("autoGrantPermissions", True) options.set_capability("newCommandTimeout", 600) options.set_capability("appPackage", TARGET_PACKAGE) options.set_capability("appActivity", TARGET_ACTIVITY) options.set_capability("app",APK_XPATH) driver = webdriver.Remote('http://localhost:4723', options=options) return driver class AndroidAppExplorer: def __init__(self, driver): self.driver = driver self.wait = WebDriverWait(driver, 10) self.clicked_identifiers = set() self.recovery_history = [] self.restore_attempt_count = 0 def get_current_state(self): try: current_package = self.driver.current_package current_activity = self.driver.current_activity return current_package, current_activity except: return None, None def is_in_target_app(self): pkg, _ = self.get_current_state() return pkg == TARGET_PACKAGE def wait_for_page_load(self, timeout=10): """等待页面加载完成""" try: WebDriverWait(self.driver, timeout).until( EC.presence_of_element_located(( 'xpath', '//android.view.ViewGroup[@displayed="true"] | ' '//android.widget.FrameLayout[@displayed="true"]' )) ) time.sleep(1) except Exception as e: print(f"⚠️ 页面等待超或出错: {e}") def ensure_in_target_app(self): current_pkg, current_act = self.get_current_state() if not self.is_in_target_app(): print(f"⚠️ 当前不在目标应用 ({current_pkg}/{current_act}),正在尝试恢复...") current_key = (current_pkg, current_act) recent_entries = self.recovery_history[-10:] if recent_entries.count(current_key) >= MAX_HISTORY_REPEAT: print(f"🛑 已多次回到 {current_key},疑似陷入循环,停止恢复。") return False self.recovery_history.append(current_key) # 方法1:back 返回 if self.restore_attempt_count < MAX_RESTORE_ATTEMPTS: try: self.driver.back() time.sleep(1.5) if self.is_in_target_app(): print("✅ 通过 back 成功返回目标 App") self.restore_attempt_count = 0 self.wait_for_page_load(10) return True except: pass # 方法2:activate_app if self.restore_attempt_count < MAX_RESTORE_ATTEMPTS: try: self.driver.activate_app(TARGET_PACKAGE) print("🔁 正在通过 activate_app 恢复...") time.sleep(2) self.wait_for_page_load(10) if self.is_in_target_app(): print("✅ 成功激活目标 App") self.restore_attempt_count = 0 return True except Exception as e: print(f"❌ activate_app 失败: {e}") # 方法3:冷重启 if self.restore_attempt_count >= MAX_RESTORE_ATTEMPTS: try: print("💀 执行冷重启...") self.driver.terminate_app(TARGET_PACKAGE) time.sleep(2) self.driver.activate_app(TARGET_PACKAGE) time.sleep(5) self.wait_for_page_load(10) if self.is_in_target_app(): print("✅ 冷重启成功") self.restore_attempt_count = 0 return True except Exception as e: print(f"❌ 冷重启失败: {e}") self.restore_attempt_count += 1 if self.restore_attempt_count > 5: print("🛑 恢复尝试过多,终止运行") return False return True else: self.recovery_history.append((current_pkg, current_act)) self.wait_for_page_load(1) return True def get_element_identifier(self, element): try: attrs = [ element.get_attribute("resource-id") or "", element.get_attribute("text") or "", element.get_attribute("content-desc") or "", element.tag_name or "" ] return "/".join([a for a in attrs if a.strip()]) except: return "unknown_element" def find_clickable_elements(self): xpath = """ //*[(@clickable='true' or @focusable='true') and @displayed='true' and @enabled='true'] """ try: elements = self.driver.find_elements('xpath', xpath) return [e for e in elements if e.is_displayed()] except Exception as e: print(f"❌ 查找元素失败: {e}") return [] def classify_elements(self, elements): bottom_nav_container = "com.brother.ptouch.iprintandlabel:id/bottomNavigationView" nav_ids = [ "com.brother.ptouch.iprintandlabel:id/navigation_my_label", "com.brother.ptouch.iprintandlabel:id/navigation_setting", "com.brother.ptouch.iprintandlabel:id/navigation_buy" ] nav_elements = [] non_nav_elements = [] for elem in elements: try: res_id = elem.get_attribute("resource-id") or "" content_desc = elem.get_attribute("content-desc") or "" text = elem.get_attribute("text") or "" if (res_id == bottom_nav_container or res_id in nav_ids or content_desc in ["My Labels", "Settings", "Shop"]): nav_elements.append(elem) else: non_nav_elements.append(elem) except Exception as e: print(f"⚠️ 分类元素异常: {e}") continue return nav_elements, non_nav_elements def handle_alert_popup(self): alert_xpath = "//*[contains(@text, 'OK') or contains(@text, '允许') or contains(@text, 'Allow')]" try: button = self.driver.find_element('xpath', alert_xpath) if button.is_displayed(): button.click() print("✅ 自动处理了弹窗") time.sleep(1) except: pass def is_interactive_input(self, element): """判断是否为可视且可交互的输入框""" try: if not element.is_displayed(): return False focusable = element.get_attribute("focusable") == "true" enabled = element.get_attribute("enabled") == "true" clazz = element.get_attribute("className") or "" is_edit_type = any(kw in clazz for kw in ["EditText", "TextInput"]) return focusable and enabled and is_edit_type except: return False def handle_input_field(self, input_elem): """处理输入框:显示提示、当前值,并让用户手动输入后读取结果""" hint = input_elem.get_attribute("hint") or "null" current_text = input_elem.get_attribute("text") or "" print(f"\n🔍 发现输入框") if hint != "null": print(f" 提示: '{hint}'") if current_text: print(f" 当前文本: '{current_text}'") print("请在设备上手动输入内容(3秒内完成)...") time.sleep(3) try: entered_value = input_elem.get_attribute("text") or "" # 使用 text 获取输入内容 except: entered_value = "" print(f"📝 用户在输入框中输入的内容为: '{entered_value}'") def click_element_safely(self, element): try: identifier = self.get_element_identifier(element) if identifier in self.clicked_identifiers: return False try: self.driver.execute_script("arguments[0].scrollIntoView(true);", element) except: pass time.sleep(0.5) element.click() print(f"🟢 点击成功: {identifier}") self.clicked_identifiers.add(identifier) time.sleep(1.5) return True except Exception as e: print(f"🔴 点击失败 {self.get_element_identifier(element)}: {str(e)}") return False def is_fully_active(self, button): """判断按钮是否处于完全激活状态""" try: alpha = button.get_attribute("alpha") if alpha and float(alpha) < 0.8: return False except: pass try: class_name = button.get_attribute("class") if "disabled" in class_name.lower(): return False except: pass return True def handle_user_agreement(self): print("🔍 检测是否需要接受用户协议...") try: initial_activity = self.driver.current_activity except: initial_activity = None try: # 等待协议页面出现 WebDriverWait(self.driver, 10).until( EC.presence_of_element_located(( 'xpath', '//*[contains(@text, "License") or contains(@text, "Agreement") or contains(@text, "user")]' )) ) print("📄 检测到【用户协议】页面") def find_agree_button(): candidates = [ '//*[@text="I agree"]', '//*[@text="Agree"]', '//*[contains(@resource-id, "yes")]', '//*[contains(@resource-id, "agree")]', ] for xpath in candidates: try: btn = self.driver.find_element(By.XPATH, xpath) if btn.is_displayed() and btn.is_enabled() and self.is_fully_active(btn): return btn except: continue return None # 先尝试直接点击 agree_btn = find_agree_button() if agree_btn: print("✅ 发现可点击的【I agree】按钮") current_act_before = self.driver.current_activity agree_btn.click() time.sleep(3) if self.driver.current_activity != current_act_before: print("✅ 已跳转至新页面,协议处理完成") return True else: print("🟡 点击了【I agree】但未跳转,可能需滚动") # 开始滚动 print("👇 协议未读完,开始滚动...") last_click_failed_id = None for i in range(10): size = self.driver.get_window_size() x = size['width'] // 2 y_start = int(size['height'] * 0.8) y_end = int(size['height'] * 0.3) self.driver.swipe(x, y_start, x, y_end, 600) time.sleep(1) if i<6: continue agree_btn = find_agree_button() if not agree_btn: continue # 防止重复点击同一个无效按钮 if hasattr(agree_btn, 'id') and agree_btn.id == last_click_failed_id: continue print(f"✅ 第 {i + 1} 次滑动后发现可点击的【I agree】按钮") current_act_before = self.driver.current_activity agree_btn.click() print("📌 正在等待页面跳转...") time.sleep(3) try: WebDriverWait(self.driver, 10).until( lambda d: d.current_activity != current_act_before ) print("✅ 成功跳转出协议页面") return True except: print(f"❌ 第 {i + 1} 次滑动后点击【I agree】无效,仍在原页面") last_click_failed_id = agree_btn.id # 记录失败 ID # 兜底:UiAutomator 强制查找 clickable=true 的按钮 try: final_btn = self.driver.find_element( By.ANDROID_UIAUTOMATOR, 'new UiSelector().textContains("agree").enabled(true).clickable(true)' ) final_btn.click() print("🪄 使用 UiAutomator 强制点击【I agree】") time.sleep(3) if self.driver.current_activity != initial_activity: print("✅ 强制点击后成功跳转") return True except Exception as e: print(f"❌ UiAutomator 点击失败: {e}") return False except TimeoutException: print("🟢 未检测到用户协议页面,跳过") return True except Exception as e: print(f"⚠️ 用户协议处理异常: {type(e).__name__}: {e}") return False def handle_printer_selection(self): """ 处理打印机选择页面 基于设备型号关键词自动选择一台打印机并点击 Done """ print("🖨️ 检测是否进入【打印机选择】页面...") device_keywords = ["PT-", "QL-", "TD-", "HL-", "MFC-"] try: # 等待至少一个设备型号文本出现 WebDriverWait(self.driver, 10).until( lambda d: any( any(kw in (elem.get_attribute("text") or "") for kw in device_keywords) for elem in d.find_elements( By.XPATH, '//android.widget.TextView[@text and string-length(@text) > 5]' ) ) ) print("✅ 进入打印机选择页面") # 查找所有 TextView 并匹配设备型号 candidates = self.driver.find_elements( By.XPATH, '//android.widget.TextView[@text and string-length(@text) > 5]' ) selected = False for elem in candidates: text = elem.get_attribute("text") or "" if any(kw in text for kw in device_keywords): try: elem.click() print(f"✅ 选择了打印机: {text}") time.sleep(1.5) selected = True break except Exception as e: print(f"🟡 无法点击 {text}: {str(e)}") selected = True # 可能已选中 break if not selected: print("ℹ️ 未找到新设备可点击(可能已有默认选中)") # 点击 Done 按钮(双条件定位增强兼容性) done_xpath = ( '//android.widget.Button[@text="Done"] | ' '//android.widget.Button[@resource-id="com.brother.ptouch.iprintandlabel:id/wel_download_templates"]' ) done_btn = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.XPATH, done_xpath)) ) done_btn.click() print("✅ 成功点击【Done】进入主界面") time.sleep(3) return True except TimeoutException: print("🟢 未检测到打印机选择页面,跳过") return True except Exception as e: print(f"⚠️ 打印机选择失败: {type(e).__name__}: {e}") return False def explore_page(self): print("\n🔄 开始探索当前页面...") while True: if not self.ensure_in_target_app(): print("❌ 无法恢复至目标应用,退出。") break self.handle_alert_popup() elements = self.find_clickable_elements() if not elements: print("📭 当前页面无可点击元素。") try: self.driver.back() print("🔙 返回上一页...") time.sleep(2) continue except: break nav_elements, non_nav_elements = self.classify_elements(elements) print(f"📌 导航栏元素数量: {len(nav_elements)}, 非导航栏元素数量: {len(non_nav_elements)}") # === 第一阶段:优先点击导航栏元素 === navigated = False for elem in nav_elements: if self.is_interactive_input(elem): self.handle_input_field(elem) continue if self.click_element_safely(elem): navigated = True break if navigated: continue # === 第二阶段:点击非导航栏元素 === any_clicked = False for elem in non_nav_elements: if self.is_interactive_input(elem): self.handle_input_field(elem) continue if self.click_element_safely(elem): any_clicked = True break if not any_clicked: print("✅ 当前页面所有可点击元素已处理完毕。") try: self.driver.back() print("🔙 返回上一页继续探索...") time.sleep(2) except: print("🔚 无法返回,探索结束。") break time.sleep(1) def run(self): print("🚀 启动 Android 应用探索器...") try: # === 新增:前置初始化流程 === print("\n🔧 正在处理初始化流程...") # 等待应用启动 time.sleep(5) # 处理用户协议 if not self.handle_user_agreement(): print("❌ 用户协议处理失败,但仍继续...") else: print("✅ 用户协议处理完成") # 处理打印机选择 if not self.handle_printer_selection(): print("⚠️ 打印机选择未完成,可能已在主界面") # else: # print("✅ 打印机选择完成,已进入主界面") # === 开始主探索流程 === self.explore_page() except KeyboardInterrupt: print("\n👋 用户中断执行。") except Exception as e: print(f"💥 执行过程中发生错误: {type(e).__name__}: {e}") finally: print("🔚 自动化结束。") # =============== 主程序入口 =============== if __name__ == "__main__": driver = create_driver() explorer = AndroidAppExplorer(driver) try: explorer.run() finally: driver.terminate_app(TARGET_PACKAGE) driver.remove_app(TARGET_PACKAGE) driver.quit() 每次运行点击的内容都不一样,有会有输入框有没有(运行5次可能有1次),获取元素不全,有些元素通过Bottom Sheet + ViewPager + GridView 分类懒加载的方式加载,不丢失功能的情况下解决我的问题
最新发布
10-18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值