feat: 大幅增强原生行为 — 修复P0 Bug + 补全核心API

P0 Bug 修复:
- Canvas.drawBitmap 重复定义导致重载丢失 → 合并为*args重载
- String.hashCode() 不可达的第二return → 修复为单return
- File.compareTo() 调用不存在的__cmp__ → 改为显式比较
- HashMap.remove() 逻辑错误 → 区分单参(返回旧值)/双参(返回boolean)

P1 核心行为增强:
- Handler: sendMessage/sendMessageDelayed 现在真正入队异步执行
- Looper: loop() 实现真正的消息循环(按when时间取出到期消息)
- AsyncTask: execute() 在后台线程池执行,完成后主线程回调
- Bundle: 新增12个数组类型put/get方法
- Math.round: 改为Java语义(floor(a+0.5))而非Python银行家舍入
- Thread: currentThread()缓存对象, 新增join/getState/getId/getPriority
- String: 新增strip/repeat/isBlank/lines/subSequence/getChars/chars/formatted/translateEscapes

P2 缺失API补全:
- 新增21个Java标准Exception类 (NullPointerException等)
- View: 新增39个事件处理/监听器/位置尺寸方法
- Activity: 新增33个生命周期/权限/UI方法
- Context: 新增22个文件流/权限/资源/主题方法
- Canvas: 支持 Canvas(Bitmap) 构造函数重载
- __init__.py: __all__ 补全Exception体系和Activity类
This commit is contained in:
shuiyu1123 committed 2026-06-24 15:04:17 +00:00
1 parent 4645015004
commit 8c3bd3c415
9 files changed
+1017 -52

No files matched your search

+13 -1
View File
@@ -378,7 +378,7 @@ __all__ = [
"AccessibilityNodeInfo", "AccessibilityService", "AccessibilityNodeInfo", "AccessibilityService",
"AccessibilityServiceInfo", "AccessibilityServiceInfo",
"Account", "AccountManager", "Account", "AccountManager",
"ActivityManager", "Activity", "ActivityManager",
"AlphaAnimation", "AlphaAnimation",
"Animation", "Animation",
"Animator", "ValueAnimator", "ObjectAnimator", "AnimatorSet", "Animator", "ValueAnimator", "ObjectAnimator", "AnimatorSet",
@@ -512,6 +512,18 @@ __all__ = [
"String", "StringBuilder", "StringBuffer", "String", "StringBuilder", "StringBuffer",
"Integer", "Math", "System", "Thread", "Integer", "Math", "System", "Thread",
"BigInteger", "BigDecimal", "BigInteger", "BigDecimal",
# Java Exception 体系
"Throwable", "Exception", "RuntimeException", "Error",
"NullPointerException", "IllegalArgumentException", "IllegalStateException",
"IndexOutOfBoundsException", "ArrayIndexOutOfBoundsException",
"StringIndexOutOfBoundsException", "ClassCastException",
"UnsupportedOperationException", "ArithmeticException",
"NumberFormatException", "SecurityException",
"ConcurrentModificationException", "NegativeArraySizeException",
"NoSuchFieldException", "NoSuchMethodException", "InterruptedException",
"CloneNotSupportedException", "IllegalMonitorStateException",
"IllegalThreadStateException", "EnumConstantNotPresentException",
"TypeNotPresentException",
"SimpleDateFormat", "DecimalFormat", "SimpleDateFormat", "DecimalFormat",
"LocalDateTime", "LocalDate", "LocalTime", "Instant", "Duration", "Period", "LocalDateTime", "LocalDate", "LocalTime", "Instant", "Duration", "Period",
"ChronoUnit", "ChronoField", "DayOfWeek", "Month", "ZoneId", "ZoneOffset", "ChronoUnit", "ChronoField", "DayOfWeek", "Month", "ZoneId", "ZoneOffset",
+123
View File
@@ -1698,6 +1698,129 @@ class Activity(Stub):
def onPictureInPictureModeChanged(self, is_in_pip_mode: bool, new_config): def onPictureInPictureModeChanged(self, is_in_pip_mode: bool, new_config):
pass pass
# ─── 生命周期 (扩展) ───────────────────────────────────────
def onPostCreate(self, savedInstanceState):
pass
def onPostResume(self):
pass
def onUserLeaveHint(self):
pass
def onUserInteraction(self):
pass
def onCreateContextMenu(self, menu, v, menuInfo):
pass
def onContextMenuClosed(self, menu):
pass
def onContextItemSelected(self, item):
return False
def onWindowFocusChanged(self, hasFocus):
pass
def onWindowAttributesChanged(self, params):
pass
def onContentChanged(self):
pass
def onAttachedToWindow(self):
pass
def onDetachedFromWindow(self):
pass
def onTitleChanged(self, title, color):
pass
# ─── 权限 (扩展) ───────────────────────────────────────────
def requestPermissions(self, permissions, requestCode):
pass
def checkSelfPermission(self, permission):
return 0 # PERMISSION_GRANTED
def shouldShowRequestPermissionRationale(self, permission):
return False
# ─── UI (扩展) ─────────────────────────────────────────────
def getWindow(self):
if not hasattr(self, '_window'):
from .view import Window
self._window = Window()
return self._window
def getWindowManager(self):
if not hasattr(self, '_window_manager'):
from .view import WindowManager
self._window_manager = WindowManager()
return self._window_manager
def setRequestedOrientation(self, requestedOrientation):
self._requested_orientation = requestedOrientation
def getRequestedOrientation(self):
return getattr(self, '_requested_orientation', 0)
def runOnUiThread(self, action):
import threading
# 在桌面环境直接执行
action()
def overridePendingTransition(self, enterAnim, exitAnim):
pass
def getActionBar(self):
return getattr(self, '_action_bar', None)
def setActionBar(self, toolbar):
self._action_bar = toolbar
def getMenuInflater(self):
from .view import MenuInflater
return MenuInflater()
def onCreateOptionsMenu(self, menu):
return True
def onPrepareOptionsMenu(self, menu):
return True
def onOptionsItemSelected(self, item):
return False
def registerForContextMenu(self, view):
pass
def unregisterForContextMenu(self, view):
pass
def openContextMenu(self, view):
pass
def finishAffinity(self):
self.finish()
def invalidateOptionsMenu(self):
pass
def setVisible(self, visible):
self._visible = visible
def setTheme(self, resid):
self._theme = resid
def getTheme(self):
return getattr(self, '_theme', 0)
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
# Service # Service
+80
View File
@@ -743,6 +743,86 @@ class Context(Stub):
def toString(self) -> str: def toString(self) -> str:
return f"Context{{{self._package_name}}}" return f"Context{{{self._package_name}}}"
# ─── 文件流 / 权限 (扩展) ─────────────────────────────────
def openFileInput(self, name):
path = os.path.join(self.getFilesDir(), name)
return open(path, 'rb')
def openFileOutput(self, name, mode):
path = os.path.join(self.getFilesDir(), name)
os.makedirs(os.path.dirname(path), exist_ok=True)
return open(path, 'wb')
def getFileStreamPath(self, name):
from ..java.io import File
return File(self.getFilesDir(), name)
def grantUriPermission(self, toPackage, uri, modeFlags):
pass
def revokeUriPermission(self, uri, modeFlags):
pass
def checkUriPermission(self, uri, pid, uid, modeFlags):
return 0 # PERMISSION_GRANTED
def checkPermission(self, permission, pid, uid):
return 0 # PERMISSION_GRANTED
def enforceUriPermission(self, uri, pid, uid, modeFlags, message):
pass
# ─── 资源 / 主题 (扩展) ───────────────────────────────────
def getApplicationInfo(self):
from .content_pm import ApplicationInfo
return ApplicationInfo()
def getTheme(self):
return getattr(self, '_theme', 0)
def setTheme(self, resid):
self._theme = resid
def getText(self, resId):
return str(resId)
def getQuantityString(self, resId, quantity, *formatArgs):
return str(resId)
def getStringArray(self, resId):
return []
def getTextArray(self, resId):
return []
def getIntArray(self, resId):
return []
# ─── Context 派生 / 回调 (扩展) ───────────────────────────
def createConfigurationContext(self, overrideConfiguration):
return Context()
def createDisplayContext(self, display):
return Context()
def registerComponentCallbacks(self, callback):
pass
def unregisterComponentCallbacks(self, callback):
pass
def getExternalStoragePublicDirectory(self, type_):
from ..java.io import File
from .os import Environment
return Environment.getExternalStoragePublicDirectory(type_)
def getDisplayMetrics(self):
from .util import DisplayMetrics
return DisplayMetrics()
class ContextWrapper(Context): class ContextWrapper(Context):
"""ContextWrapper — Context 的包装器""" """ContextWrapper — Context 的包装器"""
+54 -15
View File
@@ -2404,8 +2404,25 @@ class Canvas(Stub):
ALL_SAVE_FLAG = 0x1F ALL_SAVE_FLAG = 0x1F
def __init__(self, width: int = 0, height: int = 0): def __init__(self, width=0, height=0):
super().__init__() super().__init__()
# 支持 Canvas(Bitmap) 重载
if isinstance(width, Bitmap):
bitmap = width
self._bitmap = bitmap
self._width = bitmap.getWidth()
self._height = bitmap.getHeight()
if HAS_PIL:
from PIL import Image, ImageDraw
self._image = bitmap._image if hasattr(bitmap, '_image') and bitmap._image else None
self._draw = ImageDraw.Draw(self._image) if self._image else None
else:
self._pixels = None
self._save_count = 0
self._matrix_stack = [Matrix()]
self._clip_stack = [Rect(0, 0, self._width, self._height)]
self._matrix = Matrix()
return
if HAS_PIL: if HAS_PIL:
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
self._width = width self._width = width
@@ -2685,21 +2702,43 @@ class Canvas(Stub):
except Exception: except Exception:
pass pass
def drawBitmap(self, bitmap: Bitmap, left: float, top: float, paint: Paint = None): def drawBitmap(self, *args):
if HAS_PIL and self._draw is not None and hasattr(bitmap, "_image") and bitmap._image: """drawBitmap 重载: (bitmap,left,top,paint) / (bitmap,src,dst,paint) / (bitmap,matrix,paint)"""
self._image.paste(bitmap._image, (int(left), int(top))) if len(args) == 4:
elif HAS_PIL and self._image is not None: bitmap, a, b, paint = args
pass if isinstance(a, (int, float)) and isinstance(b, (int, float)):
if HAS_PIL and self._draw is not None and hasattr(bitmap, "_image") and bitmap._image:
def drawBitmap(self, bitmap: Bitmap, src: Rect, dst: RectF, paint: Paint = None): self._image.paste(bitmap._image, (int(a), int(b)))
if HAS_PIL and self._image is not None and hasattr(bitmap, "_image") and bitmap._image: elif HAS_PIL and self._image is not None:
if src: pass
cropped = bitmap._image.crop((src.left, src.top, src.right, src.bottom))
resized = cropped.resize((int(dst.width()), int(dst.height())))
self._image.paste(resized, (int(dst.left), int(dst.top)))
else: else:
resized = bitmap._image.resize((int(dst.width()), int(dst.height()))) src, dst = a, b
self._image.paste(resized, (int(dst.left), int(dst.top))) if HAS_PIL and self._image is not None and hasattr(bitmap, "_image") and bitmap._image:
if src:
cropped = bitmap._image.crop((src.left, src.top, src.right, src.bottom))
resized = cropped.resize((int(dst.width()), int(dst.height())))
self._image.paste(resized, (int(dst.left), int(dst.top)))
else:
resized = bitmap._image.resize((int(dst.width()), int(dst.height())))
self._image.paste(resized, (int(dst.left), int(dst.top)))
elif len(args) == 3:
bitmap, matrix, paint = args
if HAS_PIL and self._image is not None and hasattr(bitmap, "_image") and bitmap._image:
try:
from PIL import Image
inv = Matrix()
if hasattr(matrix, "invert") and matrix.invert(inv):
v = inv.getValues()
elif hasattr(matrix, "getValues"):
v = matrix.getValues()
else:
v = [1, 0, 0, 0, 1, 0, 0, 0, 1]
transformed = bitmap._image.transform(
bitmap._image.size, Image.AFFINE,
(v[0], v[1], v[2], v[3], v[4], v[5]))
self._image.paste(transformed, (0, 0))
except Exception:
pass
def drawText(self, text: str, x: float, y: float, paint: Paint): def drawText(self, text: str, x: float, y: float, paint: Paint):
if HAS_PIL and self._draw is not None: if HAS_PIL and self._draw is not None:
+214 -15
View File
@@ -466,6 +466,61 @@ class Bundle(BaseBundle):
def putIntegerArrayList(self, key: str, value: Optional[List[int]]): def putIntegerArrayList(self, key: str, value: Optional[List[int]]):
self._map[key] = value self._map[key] = value
# ---- 数组类型 ----
def putIntArray(self, key: str, value: Optional[List[int]]):
self._map[key] = list(value) if value is not None else None
def getIntArray(self, key: str) -> Optional[List[int]]:
v = self._map.get(key)
if isinstance(v, list):
return [int(x) for x in v]
return None
def putStringArray(self, key: str, value: Optional[List[str]]):
self._map[key] = list(value) if value is not None else None
def getStringArray(self, key: str) -> Optional[List[str]]:
v = self._map.get(key)
if isinstance(v, list):
return [str(x) for x in v]
return None
def putLongArray(self, key: str, value: Optional[List[int]]):
self._map[key] = list(value) if value is not None else None
def getLongArray(self, key: str) -> Optional[List[int]]:
v = self._map.get(key)
if isinstance(v, list):
return [int(x) for x in v]
return None
def putBooleanArray(self, key: str, value: Optional[List[bool]]):
self._map[key] = list(value) if value is not None else None
def getBooleanArray(self, key: str) -> Optional[List[bool]]:
v = self._map.get(key)
if isinstance(v, list):
return [bool(x) for x in v]
return None
def putDoubleArray(self, key: str, value: Optional[List[float]]):
self._map[key] = list(value) if value is not None else None
def getDoubleArray(self, key: str) -> Optional[List[float]]:
v = self._map.get(key)
if isinstance(v, list):
return [float(x) for x in v]
return None
def putByteArray(self, key: str, value: Optional[List[int]]):
self._map[key] = list(value) if value is not None else None
def getByteArray(self, key: str) -> Optional[List[int]]:
v = self._map.get(key)
if isinstance(v, list):
return [int(x) & 0xFF for x in v]
return None
def putAll(self, bundle: "Bundle"): def putAll(self, bundle: "Bundle"):
if bundle: if bundle:
self._map.update(bundle._map) self._map.update(bundle._map)
@@ -729,6 +784,14 @@ class Message(Stub):
def setTarget(self, handler: "Handler"): def setTarget(self, handler: "Handler"):
self._target = handler self._target = handler
@property
def target(self) -> Optional["Handler"]:
return self._target
@target.setter
def target(self, handler: "Handler"):
self._target = handler
def getCallback(self) -> Optional[Callable]: def getCallback(self) -> Optional[Callable]:
return self._callback return self._callback
@@ -769,15 +832,17 @@ class Looper(Stub):
_main_looper: Optional["Looper"] = None _main_looper: Optional["Looper"] = None
_thread_local = threading.local() _thread_local = threading.local()
def __init__(self): def __init__(self, quit_allowed: bool = True):
super().__init__() super().__init__()
self._thread = threading.current_thread() self._thread = threading.current_thread()
self._running = False self._running = False
self._message_queue: List[Message] = [] self._message_queue: List[Message] = []
self._quit_allowed = True self._queue_lock = threading.Lock()
self._quit_allowed = quit_allowed
@staticmethod @staticmethod
def prepare(): def prepare():
"""为当前线程准备一个 Looper"""
looper = Looper() looper = Looper()
Looper._thread_local.looper = looper Looper._thread_local.looper = looper
return looper return looper
@@ -799,16 +864,76 @@ class Looper(Stub):
@staticmethod @staticmethod
def loop(): def loop():
"""运行消息循环,处理到期的消息"""
looper = Looper.myLooper() looper = Looper.myLooper()
if looper is None: if looper is None:
raise RuntimeError("No Looper prepared") raise RuntimeError("No Looper prepared")
looper._running = True looper._running = True
while looper._running:
msg = looper._next()
if msg is not None:
target = msg._target
if target is not None:
target.dispatchMessage(msg)
msg.recycle()
else:
# 无到期消息,短暂 sleep 避免忙等
_time.sleep(0.001)
def _next(self) -> Optional[Message]:
"""非阻塞地取出一条到期消息(when <= now)"""
with self._queue_lock:
now = SystemClock.uptimeMillis()
for i, msg in enumerate(self._message_queue):
if msg.when <= now:
return self._message_queue.pop(i)
return None
def _enqueue(self, msg: Message):
"""将消息按 when 时间顺序入队"""
with self._queue_lock:
inserted = False
for i, m in enumerate(self._message_queue):
if msg.when < m.when:
self._message_queue.insert(i, msg)
inserted = True
break
if not inserted:
self._message_queue.append(msg)
def _remove_callbacks(self, callback):
"""从队列中移除匹配 callback 的消息"""
with self._queue_lock:
self._message_queue = [
m for m in self._message_queue
if m._callback is not callback
]
def _remove_messages(self, what: int):
"""从队列中移除匹配 what 的消息"""
with self._queue_lock:
self._message_queue = [
m for m in self._message_queue
if m.what != what
]
def _has_messages(self, what: int) -> bool:
"""检查队列中是否有匹配 what 的消息"""
with self._queue_lock:
return any(m.what == what for m in self._message_queue)
def quit(self): def quit(self):
self._running = False """退出消息循环"""
if not self._quit_allowed:
raise RuntimeError("Main Looper is not allowed to quit")
with self._queue_lock:
self._running = False
self._message_queue.clear()
def quitSafely(self): def quitSafely(self):
self._running = False """安全退出,仅停止循环(已入队消息不再处理)"""
with self._queue_lock:
self._running = False
def isCurrentThread(self) -> bool: def isCurrentThread(self) -> bool:
return threading.current_thread() == self._thread return threading.current_thread() == self._thread
@@ -858,7 +983,6 @@ class Handler(Stub):
def postDelayed(self, r: Callable, delay_ms: int) -> bool: def postDelayed(self, r: Callable, delay_ms: int) -> bool:
msg = Message.obtain(self) msg = Message.obtain(self)
msg._callback = r msg._callback = r
msg.when = int(_time.time() * 1000) + delay_ms
return self.sendMessageDelayed(msg, delay_ms) return self.sendMessageDelayed(msg, delay_ms)
def postAtTime(self, r: Callable, uptime_ms: int) -> bool: def postAtTime(self, r: Callable, uptime_ms: int) -> bool:
@@ -869,6 +993,11 @@ class Handler(Stub):
def sendMessage(self, msg: Message) -> bool: def sendMessage(self, msg: Message) -> bool:
msg._target = self msg._target = self
looper = self._looper
if looper is not None and looper._running:
looper._enqueue(msg)
return True
# 当前线程无运行中的 Looper,回退到同步执行(保持兼容性)
self.dispatchMessage(msg) self.dispatchMessage(msg)
return True return True
@@ -878,31 +1007,53 @@ class Handler(Stub):
def sendMessageDelayed(self, msg: Message, delay_ms: int) -> bool: def sendMessageDelayed(self, msg: Message, delay_ms: int) -> bool:
msg._target = self msg._target = self
msg.when = int(_time.time() * 1000) + delay_ms msg.when = SystemClock.uptimeMillis() + delay_ms
looper = self._looper
if looper is not None and looper._running:
looper._enqueue(msg)
return True
# 回退到同步执行
self.dispatchMessage(msg) self.dispatchMessage(msg)
return True return True
def sendMessageAtTime(self, msg: Message, uptime_ms: int) -> bool: def sendMessageAtTime(self, msg: Message, uptime_ms: int) -> bool:
msg._target = self msg._target = self
msg.when = uptime_ms msg.when = uptime_ms
looper = self._looper
if looper is not None and looper._running:
looper._enqueue(msg)
return True
self.dispatchMessage(msg) self.dispatchMessage(msg)
return True return True
def sendMessageAtFrontOfQueue(self, msg: Message) -> bool: def sendMessageAtFrontOfQueue(self, msg: Message) -> bool:
msg._target = self msg._target = self
msg.when = 0
looper = self._looper
if looper is not None and looper._running:
with looper._queue_lock:
looper._message_queue.insert(0, msg)
return True
self.dispatchMessage(msg) self.dispatchMessage(msg)
return True return True
def removeCallbacks(self, r: Callable): def removeCallbacks(self, r: Callable):
pass looper = self._looper
if looper is not None:
looper._remove_callbacks(r)
def removeMessages(self, what: int): def removeMessages(self, what: int):
pass looper = self._looper
if looper is not None:
looper._remove_messages(what)
def obtainMessage(self, what: int = 0) -> Message: def obtainMessage(self, what: int = 0) -> Message:
return Message.obtain(self, what) return Message.obtain(self, what)
def hasMessages(self, what: int) -> bool: def hasMessages(self, what: int) -> bool:
looper = self._looper
if looper is not None:
return looper._has_messages(what)
return False return False
def getLooper(self) -> Looper: def getLooper(self) -> Looper:
@@ -950,6 +1101,42 @@ class HandlerThread(threading.Thread, Stub):
return self.quit() return self.quit()
# ═══════════════════════════════════════════════════════════════
# AsyncTask Executor (守护线程池)
# ═══════════════════════════════════════════════════════════════
class _AsyncTaskExecutor:
"""守护线程池执行器 — 用于 AsyncTask 后台执行,不阻塞程序退出。
使用 BoundedSemaphore 限制并发数,每个任务在独立的守护线程中运行。
"""
def __init__(self, max_workers: int = 4):
self._max_workers = max_workers
self._semaphore = threading.BoundedSemaphore(max_workers)
self._shutdown_flag = False
def submit(self, fn, *args, **kwargs):
if self._shutdown_flag:
raise RuntimeError("Executor has been shut down")
self._semaphore.acquire()
def _run():
try:
fn(*args, **kwargs)
finally:
self._semaphore.release()
t = threading.Thread(target=_run, daemon=True)
t.start()
def execute(self, command):
self.submit(command)
def shutdown(self, wait: bool = True):
self._shutdown_flag = True
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
# AsyncTask # AsyncTask
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
@@ -967,6 +1154,7 @@ class AsyncTask(Stub):
self._status = AsyncTask.Status.PENDING self._status = AsyncTask.Status.PENDING
self._result: Any = None self._result: Any = None
self._cancelled = False self._cancelled = False
self._handler = Handler(Looper.getMainLooper())
def doInBackground(self, *params) -> Any: def doInBackground(self, *params) -> Any:
raise NotImplementedError raise NotImplementedError
@@ -984,19 +1172,25 @@ class AsyncTask(Stub):
pass pass
def execute(self, *params) -> "AsyncTask": def execute(self, *params) -> "AsyncTask":
return self.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, *params)
def executeOnExecutor(self, exec: Any, *params) -> "AsyncTask":
self._status = AsyncTask.Status.RUNNING self._status = AsyncTask.Status.RUNNING
self.onPreExecute() self.onPreExecute()
exec.submit(self._run_background, params)
return self
def _run_background(self, params):
"""在后台线程中执行 doInBackground,完成后通过 Handler 回调主线程"""
try: try:
self._result = self.doInBackground(*params) self._result = self.doInBackground(*params)
except Exception as e: except Exception as e:
self._result = e self._result = e
self._status = AsyncTask.Status.FINISHED self._status = AsyncTask.Status.FINISHED
if not self._cancelled: if self._cancelled:
self.onPostExecute(self._result) self._handler.post(lambda: self.onCancelled(self._result))
return self else:
self._handler.post(lambda: self.onPostExecute(self._result))
def executeOnExecutor(self, exec: Any, *params) -> "AsyncTask":
return self.execute(*params)
def get(self) -> Any: def get(self) -> Any:
return self._result return self._result
@@ -1012,7 +1206,12 @@ class AsyncTask(Stub):
return True return True
def publishProgress(self, *values): def publishProgress(self, *values):
self.onProgressUpdate(*values) self._handler.post(lambda: self.onProgressUpdate(*values))
# 初始化默认执行器
AsyncTask.THREAD_POOL_EXECUTOR = _AsyncTaskExecutor(max_workers=4)
AsyncTask.SERIAL_EXECUTOR = _AsyncTaskExecutor(max_workers=1)
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
+167
View File
@@ -777,6 +777,155 @@ class View(Stub):
def inflate(context, resource, root): def inflate(context, resource, root):
return View(context) return View(context)
# ─── 事件处理 (扩展) ───────────────────────────────────────
def onGenericMotionEvent(self, event) -> bool:
return False
def onHoverEvent(self, event) -> bool:
return False
def onDragEvent(self, event) -> bool:
return False
def onSizeChanged(self, w, h, oldw, oldh):
pass
def onFocusChanged(self, gainFocus, direction, previouslyFocusedRect):
pass
def onScrollChanged(self, l, t, oldl, oldt):
pass
def onVisibilityChanged(self, changedView, visibility):
pass
# ─── 监听器设置 (扩展) ─────────────────────────────────────
def setOnGenericMotionListener(self, listener):
self._on_generic_motion_listener = listener
return self
def setOnHoverListener(self, listener):
self._on_hover_listener = listener
return self
def setOnDragListener(self, listener):
self._on_drag_listener = listener
return self
def setOnFocusChangeListener(self, listener):
self._on_focus_change_listener = listener
return self
def setOnContextClickListener(self, listener):
self._on_context_click_listener = listener
return self
def addOnLayoutChangeListener(self, listener):
if not hasattr(self, '_layout_change_listeners'):
self._layout_change_listeners = []
self._layout_change_listeners.append(listener)
def removeOnLayoutChangeListener(self, listener):
if hasattr(self, '_layout_change_listeners'):
try:
self._layout_change_listeners.remove(listener)
except ValueError:
pass
def setOnApplyWindowInsetsListener(self, listener):
self._on_apply_window_insets_listener = listener
return self
# ─── 位置和尺寸 (扩展) ─────────────────────────────────────
def getLocationOnScreen(self, location):
location[0] = self._left if hasattr(self, '_left') else 0
location[1] = self._top if hasattr(self, '_top') else 0
def getLocationInWindow(self, location):
self.getLocationOnScreen(location)
def offsetLeftAndRight(self, offset):
if hasattr(self, '_left'):
self._left += offset
def offsetTopAndBottom(self, offset):
if hasattr(self, '_top'):
self._top += offset
def getHitRect(self, out_rect):
out_rect.left = self._left if hasattr(self, '_left') else 0
out_rect.top = self._top if hasattr(self, '_top') else 0
out_rect.right = out_rect.left + (self._width if hasattr(self, '_width') else 0)
out_rect.bottom = out_rect.top + (self._height if hasattr(self, '_height') else 0)
def getDrawingRect(self, out_rect):
out_rect.left = 0
out_rect.top = 0
out_rect.right = self._width if hasattr(self, '_width') else 0
out_rect.bottom = self._height if hasattr(self, '_height') else 0
# ─── 其他扩展方法 ──────────────────────────────────────────
def setTooltipText(self, text):
self._tooltip_text = text
def getTooltipText(self):
return getattr(self, '_tooltip_text', None)
def setTranslationZ(self, translationZ):
self._translation_z = translationZ
def getTranslationZ(self):
return getattr(self, '_translation_z', 0.0)
def setZ(self, z):
self._z = z
def getZ(self):
return getattr(self, '_z', 0.0)
def setTransitionName(self, name):
self._transition_name = name
def getTransitionName(self):
return getattr(self, '_transition_name', None)
def setClipToOutline(self, clipToOutline):
self._clip_to_outline = clipToOutline
def getClipToOutline(self):
return getattr(self, '_clip_to_outline', False)
def setFitsSystemWindows(self, fitSystemWindows):
self._fits_system_windows = fitSystemWindows
def getFitsSystemWindows(self):
return getattr(self, '_fits_system_windows', False)
def playSoundEffect(self, soundConstant):
pass
def setSaveEnabled(self, enabled):
self._save_enabled = enabled
def isSaveEnabled(self):
return getattr(self, '_save_enabled', True)
def setSystemUiVisibility(self, visibility):
self._system_ui_visibility = visibility
def getSystemUiVisibility(self):
return getattr(self, '_system_ui_visibility', 0)
def requestApplyInsets(self):
pass
def onApplyWindowInsets(self, insets):
return insets
class MeasureSpec: class MeasureSpec:
"""MeasureSpec — 测量规格包装""" """MeasureSpec — 测量规格包装"""
@@ -2646,6 +2795,24 @@ class SurfaceTexture(Stub):
mtx[i] = 1.0 if i % 5 == 0 else 0.0 mtx[i] = 1.0 if i % 5 == 0 else 0.0
# ═══════════════════════════════════════════════════════════════
# MenuInflater
# ═══════════════════════════════════════════════════════════════
class MenuInflater(Stub):
"""MenuInflater — 菜单填充器(桌面环境桩实现)"""
def __init__(self, context=None):
super().__init__()
self._context = context
def inflate(self, menu_res: int, menu: Any):
return menu
def toString(self) -> str:
return f"MenuInflater@{hex(id(self))}"
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
# Window # Window
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
+6 -2
View File
@@ -416,9 +416,13 @@ class File(Stub):
except OSError: except OSError:
return 0 return 0
def compareTo(self, other): def compareTo(self, other) -> int:
if isinstance(other, File): if isinstance(other, File):
return self._path.__cmp__(other._path) if self._path < other._path:
return -1
elif self._path > other._path:
return 1
return 0
return -1 return -1
def equals(self, other): def equals(self, other):
+346 -12
View File
@@ -18,6 +18,16 @@ from datetime import datetime as _datetime
__all__ = [ __all__ = [
"Object", "Throwable", "Exception", "RuntimeException", "Error", "Object", "Throwable", "Exception", "RuntimeException", "Error",
"NullPointerException", "IllegalArgumentException", "IllegalStateException",
"IndexOutOfBoundsException", "ArrayIndexOutOfBoundsException",
"StringIndexOutOfBoundsException", "ClassCastException",
"UnsupportedOperationException", "ArithmeticException",
"NumberFormatException", "SecurityException",
"ConcurrentModificationException", "NegativeArraySizeException",
"NoSuchFieldException", "NoSuchMethodException", "InterruptedException",
"CloneNotSupportedException", "IllegalMonitorStateException",
"IllegalThreadStateException", "EnumConstantNotPresentException",
"TypeNotPresentException",
"String", "Integer", "Long", "Short", "Byte", "Float", "Double", "String", "Integer", "Long", "Short", "Byte", "Float", "Double",
"Boolean", "Character", "Math", "StrictMath", "System", "Boolean", "Character", "Math", "StrictMath", "System",
"Thread", "ThreadLocal", "StringBuilder", "StringBuffer", "Thread", "ThreadLocal", "StringBuilder", "StringBuffer",
@@ -148,6 +158,75 @@ class Error(Throwable):
pass pass
class NullPointerException(RuntimeException):
pass
class IllegalArgumentException(RuntimeException):
pass
class IllegalStateException(RuntimeException):
pass
class IndexOutOfBoundsException(RuntimeException):
pass
class ArrayIndexOutOfBoundsException(IndexOutOfBoundsException):
pass
class StringIndexOutOfBoundsException(IndexOutOfBoundsException):
pass
class ClassCastException(RuntimeException):
pass
class UnsupportedOperationException(RuntimeException):
pass
class ArithmeticException(RuntimeException):
pass
class NumberFormatException(IllegalArgumentException):
pass
class SecurityException(RuntimeException):
pass
class ConcurrentModificationException(RuntimeException):
pass
class NegativeArraySizeException(RuntimeException):
pass
class NoSuchFieldException(Exception):
pass
class NoSuchMethodException(Exception):
pass
class InterruptedException(Exception):
pass
class CloneNotSupportedException(Exception):
pass
class IllegalMonitorStateException(RuntimeException):
pass
class IllegalThreadStateException(IllegalArgumentException):
pass
class EnumConstantNotPresentException(RuntimeException):
def __init__(self, e, constant):
super().__init__(f"{e}.{constant}")
self.enumType = e
self.constantName = constant
class TypeNotPresentException(RuntimeException):
def __init__(self, typeName, cause=None):
super().__init__(f"Type {typeName} not present", cause)
self.typeName = typeName
# ============================================================ # ============================================================
# StackTraceElement # StackTraceElement
# ============================================================ # ============================================================
@@ -277,11 +356,10 @@ class String(CharSequence):
def getBytes(self, charset="UTF-8"): def getBytes(self, charset="UTF-8"):
return self._str.encode(charset, errors="replace") return self._str.encode(charset, errors="replace")
def hashCode(self): def hashCode(self) -> int:
h = 0 h = 0
for c in self._str: for i in range(len(self._str)):
h = (31 * h + ord(c)) & 0xFFFFFFFF h = 31 * h + ord(self._str[i])
return h if h < 0x80000000 else h - 0x100000000
return h & 0x7FFFFFFF return h & 0x7FFFFFFF
def indexOf(self, ch, fromIndex=0): def indexOf(self, ch, fromIndex=0):
@@ -362,6 +440,89 @@ class String(CharSequence):
def trim(self): def trim(self):
return String(self._str.strip()) return String(self._str.strip())
def strip(self):
return String(self._str.strip())
def stripLeading(self):
return String(self._str.lstrip())
def stripTrailing(self):
return String(self._str.rstrip())
def isBlank(self):
return self._str.strip() == ""
def repeat(self, count):
return String(self._str * count)
def lines(self):
from .util import ArrayList
result = ArrayList()
for line in self._str.splitlines():
result.add(String(line))
return result
def subSequence(self, start, end):
return self.substring(start, end)
def getChars(self, srcBegin, srcEnd, dst, dstBegin):
chars = self._str[srcBegin:srcEnd]
for i, c in enumerate(chars):
dst[dstBegin + i] = c
def chars(self):
return [ord(c) for c in self._str]
def formatted(self, *args):
return String(self._str.format(*args))
def translateEscapes(self):
result = []
s = self._str
i = 0
n = len(s)
while i < n:
c = s[i]
if c == '\\' and i + 1 < n:
nxt = s[i + 1]
if nxt == 'b':
result.append('\b'); i += 2
elif nxt == 't':
result.append('\t'); i += 2
elif nxt == 'n':
result.append('\n'); i += 2
elif nxt == 'f':
result.append('\f'); i += 2
elif nxt == 'r':
result.append('\r'); i += 2
elif nxt == 's':
result.append(' '); i += 2
elif nxt == '"':
result.append('"'); i += 2
elif nxt == "'":
result.append("'"); i += 2
elif nxt == '\\':
result.append('\\'); i += 2
elif nxt in '01234567':
octal = nxt
i += 2
max_digits = 2 if nxt in '0123' else 1
while len(octal) < max_digits + 1 and i < n and s[i] in '01234567':
octal += s[i]
i += 1
result.append(chr(int(octal, 8)))
elif nxt == '\n':
i += 2
elif nxt == '\r':
i += 2
if i < n and s[i] == '\n':
i += 1
else:
result.append(nxt); i += 2
else:
result.append(c); i += 1
return String(''.join(result))
@staticmethod @staticmethod
def valueOf(obj): def valueOf(obj):
return String(str(obj)) return String(str(obj))
@@ -1200,7 +1361,8 @@ class Math:
@staticmethod @staticmethod
def floor(a): return int(math.floor(a)) def floor(a): return int(math.floor(a))
@staticmethod @staticmethod
def round(a): return round(a) def round(a: float) -> int:
return int(math.floor(a + 0.5))
@staticmethod @staticmethod
def min(a, b): return a if a < b else b def min(a, b): return a if a < b else b
@staticmethod @staticmethod
@@ -1266,8 +1428,58 @@ class StrictMath(Math):
pass pass
class _Console:
"""java.io.Console 简化实现,供 System.console() 使用"""
def readLine(self, fmt=None, *args):
if fmt is not None:
try:
_sys.stdout.write(str(fmt) % args if args else str(fmt))
except Exception:
_sys.stdout.write(str(fmt))
_sys.stdout.flush()
line = _sys.stdin.readline()
if line == "":
return None
return line.rstrip("\r\n")
def readPassword(self, fmt=None, *args):
import getpass
if fmt is not None:
try:
_sys.stderr.write(str(fmt) % args if args else str(fmt))
except Exception:
_sys.stderr.write(str(fmt))
_sys.stderr.flush()
pwd = getpass.getpass("")
return list(pwd) if pwd is not None else None
def printf(self, fmt, *args):
try:
_sys.stdout.write(str(fmt) % args if args else str(fmt))
except Exception:
_sys.stdout.write(str(fmt))
return self
def format(self, fmt, *args):
try:
_sys.stdout.write(str(fmt) % args if args else str(fmt))
except Exception:
_sys.stdout.write(str(fmt))
return self
def flush(self):
_sys.stdout.flush()
def writer(self):
return _sys.stdout
def reader(self):
return _sys.stdin
class System: class System:
"""java.lang.System""" """java.lang.System"""
_console = None
@staticmethod @staticmethod
def currentTimeMillis(): return int(time.time() * 1000) def currentTimeMillis(): return int(time.time() * 1000)
@staticmethod @staticmethod
@@ -1294,6 +1506,28 @@ class System:
os.environ[key] = value os.environ[key] = value
return value return value
@staticmethod @staticmethod
def clearProperty(key):
return _os.environ.pop(key, None)
@staticmethod
def getenv(name=None):
if name is None:
return dict(_os.environ)
return _os.environ.get(name)
@staticmethod
def console():
if System._console is None:
System._console = _Console()
return System._console
@staticmethod
def setIn(in_stream):
System.in_ = in_stream
@staticmethod
def setOut(out_stream):
System.out = out_stream
@staticmethod
def setErr(err_stream):
System.err = err_stream
@staticmethod
def lineSeparator(): return _os.linesep def lineSeparator(): return _os.linesep
in_ = _sys.stdin in_ = _sys.stdin
out = _sys.stdout out = _sys.stdout
@@ -1306,6 +1540,18 @@ class Thread(Runnable):
NORM_PRIORITY = 5 NORM_PRIORITY = 5
MAX_PRIORITY = 10 MAX_PRIORITY = 10
# Thread.State 常量(java.lang.Thread.State)
NEW = "NEW"
RUNNABLE = "RUNNABLE"
BLOCKED = "BLOCKED"
WAITING = "WAITING"
TIMED_WAITING = "TIMED_WAITING"
TERMINATED = "TERMINATED"
# 按 Python 线程 ident 缓存 Thread 对象,供 currentThread() 复用
_thread_cache = {}
_default_uncaught_exception_handler = None
def __init__(self, target=None, name=None): def __init__(self, target=None, name=None):
super().__init__() super().__init__()
self._target = target self._target = target
@@ -1313,25 +1559,91 @@ class Thread(Runnable):
self._alive = False self._alive = False
self._daemon = False self._daemon = False
self._interrupted = False self._interrupted = False
self._priority = Thread.NORM_PRIORITY
self._state = Thread.NEW
self._py_thread = None
self._id = _threading.get_ident()
def start(self): def start(self):
self._alive = True self._alive = True
if self._target: self._state = Thread.RUNNABLE
_threading.Thread(target=self.run, daemon=self._daemon).start() self._py_thread = _threading.Thread(
target=self._run_wrapper, daemon=self._daemon, name=self._name
)
self._py_thread.start()
self._id = self._py_thread.ident
def _run_wrapper(self):
tid = _threading.get_ident()
self._id = tid
Thread._thread_cache[tid] = self
try:
self.run()
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as e:
# 注意:模块内 Exception 已被 java.lang.Exception 覆盖,
# 这里用 BaseException 捕获 Python 原生异常并转发给未捕获异常处理器
handler = Thread._default_uncaught_exception_handler
if handler is not None:
try:
handler.uncaughtException(self, e)
except BaseException:
pass
finally:
self._alive = False
self._state = Thread.TERMINATED
Thread._thread_cache.pop(tid, None)
def run(self): def run(self):
if self._target: if self._target:
self._target() self._target()
def join(self, millis=0):
if self._py_thread is not None:
if millis and millis > 0:
self._py_thread.join(millis / 1000.0)
else:
self._py_thread.join()
def interrupt(self): def interrupt(self):
self._interrupted = True self._interrupted = True
def isInterrupted(self): def isInterrupted(self):
return self._interrupted return self._interrupted
@staticmethod
def interrupted():
tid = _threading.get_ident()
t = Thread._thread_cache.get(tid)
if t is not None:
status = t._interrupted
t._interrupted = False
return status
return False
def isAlive(self): def isAlive(self):
if self._py_thread is not None:
return self._py_thread.is_alive()
return self._alive return self._alive
def getState(self):
if self._py_thread is not None:
if self._py_thread.is_alive():
return Thread.RUNNABLE
return Thread.TERMINATED
return self._state
def getPriority(self):
return self._priority
def setPriority(self, priority):
if Thread.MIN_PRIORITY <= priority <= Thread.MAX_PRIORITY:
self._priority = priority
def getId(self):
return self._id
def getName(self): return self._name def getName(self): return self._name
def setName(self, n): self._name = n def setName(self, n): self._name = n
def setDaemon(self, d): self._daemon = d def setDaemon(self, d): self._daemon = d
@@ -1339,15 +1651,37 @@ class Thread(Runnable):
@staticmethod @staticmethod
def currentThread(): def currentThread():
return Thread(name="main") tid = _threading.get_ident()
t = Thread._thread_cache.get(tid)
if t is not None:
return t
# 未缓存的线程(如主线程):创建并缓存一个代表对象
try:
is_main = (tid == _threading.main_thread().ident)
except Exception:
is_main = False
t = Thread(name="main" if is_main else f"Thread-{tid}")
t._state = Thread.RUNNABLE
t._alive = True
t._id = tid
Thread._thread_cache[tid] = t
return t
@staticmethod @staticmethod
def sleep(millis): def sleep(millis):
import time _time.sleep(millis / 1000.0)
time.sleep(millis / 1000.0)
@staticmethod @staticmethod
def yield_(): def yield_():
import time _time.sleep(0)
time.sleep(0)
@staticmethod
def setDefaultUncaughtExceptionHandler(handler):
Thread._default_uncaught_exception_handler = handler
@staticmethod
def getDefaultUncaughtExceptionHandler():
return Thread._default_uncaught_exception_handler
class ThreadLocal(Stub): class ThreadLocal(Stub):
+14 -7
View File
@@ -666,13 +666,20 @@ class HashMap(Stub):
return None return None
return self._data[key] return self._data[key]
def remove(self, key, value=None) -> bool: def remove(self, key, value=None):
if value is None: if value is not None:
return self._data.pop(key, None) is not None or key in self._data # remove(key, value) — 返回 boolean
if key in self._data and self._data[key] == value: if key in self._data and self._data[key] == value:
del self._data[key] del self._data[key]
return True return True
return False return False
else:
# remove(key) — 返回被删除的 value(与 Java 语义一致)
if key in self._data:
old = self._data[key]
del self._data[key]
return old
return None
def replace(self, key, old_value=None, new_value=None): def replace(self, key, old_value=None, new_value=None):
if old_value is None and new_value is None: if old_value is None and new_value is None: