Compare commits

..

13 Commits

Author SHA1 Message Date
ZhanGSKen
d9b99ee371 <appbase>APK 15.1.3 release Publish. 2025-03-28 18:20:22 +08:00
ZhanGSKen
6f7414b576 完成应用退出的菜单按钮逻辑 2025-03-28 18:10:31 +08:00
ZhanGSKen
2f6e297bab <libappbase>Library Release 15.1.2 2025-03-28 15:50:29 +08:00
ZhanGSKen
e35a6b9a7f <appbase>APK 15.1.2 release Publish. 2025-03-28 15:50:14 +08:00
ZhanGSKen
e71e5a852d 调整活动窗口基类为应用自己定义,增强窗口的AppCompatActivity特性引用。 2025-03-28 15:48:33 +08:00
ZhanGSKen
4ea48a051d <libappbase>Library Release 15.1.1 2025-03-28 14:53:36 +08:00
ZhanGSKen
ae680b5b48 <appbase>APK 15.1.1 release Publish. 2025-03-28 14:53:11 +08:00
ZhanGSKen
80767ca5b5 修复编译警告问题 2025-03-28 14:25:26 +08:00
ZhanGSKen
be5ed936df 设置窗口任务栏显示信息 2025-03-28 13:57:26 +08:00
ZhanGSKen
b3075f778d 修复窗口切换问题 2025-03-28 13:45:45 +08:00
ZhanGSKen
ce05d69132 修复Support风格的应用崩溃报告窗口 2025-03-28 13:27:03 +08:00
ZhanGSKen
6a9434b194 取消窗口管理模块,修复应用崩溃调试模块。 2025-03-28 13:18:55 +08:00
ZhanGSKen
6ca7b210a6 简化配置 2025-03-28 12:17:48 +08:00
40 changed files with 313 additions and 1212 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Thu Mar 27 10:59:31 GMT 2025
stageCount=1
#Fri Mar 28 18:20:22 HKT 2025
stageCount=4
libraryProject=libappbase
baseVersion=15.1
publishVersion=15.1.0
buildCount=130
baseBetaVersion=15.1.1
publishVersion=15.1.3
buildCount=0
baseBetaVersion=15.1.4

View File

@@ -32,12 +32,15 @@
</activity>
<activity android:name=".activities.NewActivity"
<activity
android:name=".activities.NewActivity"
android:label="NewActivity"
android:exported="true"
android:resizeableActivity="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
<activity android:name=".activities.New2Activity"
android:label="New2Activity"
android:exported="true"
android:resizeableActivity="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>

View File

@@ -1,12 +1,10 @@
package cc.winboll.studio.appbase;
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -16,17 +14,17 @@ import cc.winboll.studio.appbase.activities.NewActivity;
import cc.winboll.studio.appbase.services.MainService;
import cc.winboll.studio.appbase.services.TestDemoBindService;
import cc.winboll.studio.appbase.services.TestDemoService;
import cc.winboll.studio.libappbase.CrashHandler;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.GlobalCrashActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.sos.SOS;
import cc.winboll.studio.libappbase.utils.ToastUtils;
import cc.winboll.studio.libappbase.widgets.StatusWidget;
import cc.winboll.studio.libappbase.winboll.IWinBollActivity;
import cc.winboll.studio.libappbase.winboll.LogActivity;
import cc.winboll.studio.libappbase.winboll.WinBollActivityManager;
import android.support.v7.widget.Toolbar;
import cc.winboll.studio.libappbase.dialogs.YesNoAlertDialog;
public class MainActivity extends AppCompatActivity implements IWinBollActivity {
public class MainActivity extends WinBollActivityBase implements IWinBollActivity {
public static final String TAG = "MainActivity";
@@ -71,12 +69,6 @@ public class MainActivity extends AppCompatActivity implements IWinBollActivity
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == cc.winboll.studio.appbase.R.id.item_log) {
WinBollActivityManager.getInstance(this).startLogActivity(this);
return true;
} else if(item.getItemId() == cc.winboll.studio.appbase.R.id.item_minimal) {
moveTaskToBack(true);
}
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
@@ -95,6 +87,12 @@ public class MainActivity extends AppCompatActivity implements IWinBollActivity
boolean isDebuging = ((CheckBox)view).isChecked();
GlobalApplication.setIsDebuging(isDebuging);
}
public void onPreviewGlobalCrashActivity(View view) {
Intent intent = new Intent(this, GlobalCrashActivity.class);
intent.putExtra(CrashHandler.EXTRA_CRASH_INFO, "Demo log...");
startActivity(intent);
}
public void onStartCenter(View view) {
MainService.startMainService(this);
@@ -139,6 +137,8 @@ public class MainActivity extends AppCompatActivity implements IWinBollActivity
startService(intent);
}
public void onStopTestDemoService(View view) {
Intent intent = new Intent(this, TestDemoService.class);
@@ -176,7 +176,7 @@ public class MainActivity extends AppCompatActivity implements IWinBollActivity
}
public void onTestOpenNewActivity(View view) {
WinBollActivityManager.getInstance(this).startWinBollActivity(this, NewActivity.class);
GlobalApplication.getWinBollActivityManager().startWinBollActivity(this, NewActivity.class);
}

View File

@@ -0,0 +1,82 @@
package cc.winboll.studio.appbase;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/28 15:34:16
* @Describe 应用活动窗口基类
*/
import android.app.Activity;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import cc.winboll.studio.appbase.App;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.dialogs.YesNoAlertDialog;
import cc.winboll.studio.libappbase.winboll.IWinBollActivity;
import cc.winboll.studio.libappbase.winboll.WinBollActivityManager;
public class WinBollActivityBase extends AppCompatActivity implements IWinBollActivity {
public static final String TAG = "WinBollActivityBase";
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
WinBollActivityManager getWinBollActivityManager() {
return WinBollActivityManager.getInstance(GlobalApplication.getInstance());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWinBollActivityManager().add(this);
}
@Override
public void onPostCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onPostCreate(savedInstanceState, persistentState);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == cc.winboll.studio.appbase.R.id.item_log) {
GlobalApplication.getWinBollActivityManager().startLogActivity(this);
return true;
} else if(item.getItemId() == cc.winboll.studio.appbase.R.id.item_minimal) {
//moveTaskToBack(true);
exit();
}
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
void exit() {
YesNoAlertDialog.show(this, "Exit " + getString(R.string.app_name), "Close all activity and exit?", new YesNoAlertDialog.OnDialogResultListener(){
@Override
public void onYes() {
App.getWinBollActivityManager().finishAll();
}
@Override
public void onNo() {
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
getWinBollActivityManager().registeRemove(this);
}
}

View File

@@ -7,16 +7,16 @@ package cc.winboll.studio.appbase.activities;
*/
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.appbase.WinBollActivityBase;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.winboll.IWinBollActivity;
import cc.winboll.studio.libappbase.winboll.WinBollActivityManager;
public class New2Activity extends AppCompatActivity implements IWinBollActivity {
public class New2Activity extends WinBollActivityBase implements IWinBollActivity {
public static final String TAG = "New2Activity";
@@ -52,15 +52,15 @@ public class New2Activity extends AppCompatActivity implements IWinBollActivity
}
public void onCloseThisActivity(View view) {
WinBollActivityManager.getInstance(this).finish(this);
GlobalApplication.getWinBollActivityManager().finish(this);
}
public void onCloseAllActivity(View view) {
WinBollActivityManager.getInstance(this).finishAll();
GlobalApplication.getWinBollActivityManager().finishAll();
}
public void onNewActivity(View view) {
WinBollActivityManager.getInstance(this).startWinBollActivity(this, NewActivity.class);
GlobalApplication.getWinBollActivityManager().startWinBollActivity(this, NewActivity.class);
}
@@ -74,10 +74,8 @@ public class New2Activity extends AppCompatActivity implements IWinBollActivity
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == cc.winboll.studio.appbase.R.id.item_log) {
WinBollActivityManager.getInstance(this).startLogActivity(this);
GlobalApplication.getWinBollActivityManager().startLogActivity(this);
return true;
} else if(item.getItemId() == cc.winboll.studio.appbase.R.id.item_minimal) {
moveTaskToBack(true);
}
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);

View File

@@ -6,16 +6,16 @@ package cc.winboll.studio.appbase.activities;
*/
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.appbase.WinBollActivityBase;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.winboll.IWinBollActivity;
import cc.winboll.studio.libappbase.winboll.WinBollActivityManager;
public class NewActivity extends AppCompatActivity implements IWinBollActivity {
public class NewActivity extends WinBollActivityBase implements IWinBollActivity {
public static final String TAG = "NewActivity";
@@ -50,15 +50,15 @@ public class NewActivity extends AppCompatActivity implements IWinBollActivity {
}
public void onCloseThisActivity(View view) {
WinBollActivityManager.getInstance(this).finish(this);
GlobalApplication.getWinBollActivityManager().finish(this);
}
public void onCloseAllActivity(View view) {
WinBollActivityManager.getInstance(this).finishAll();
GlobalApplication.getWinBollActivityManager().finishAll();
}
public void onNew2Activity(View view) {
WinBollActivityManager.getInstance(this).startWinBollActivity(this, New2Activity.class);
GlobalApplication.getWinBollActivityManager().startWinBollActivity(this, New2Activity.class);
}
@@ -72,10 +72,8 @@ public class NewActivity extends AppCompatActivity implements IWinBollActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == cc.winboll.studio.appbase.R.id.item_log) {
WinBollActivityManager.getInstance(this).startLogActivity(this);
GlobalApplication.getWinBollActivityManager().startLogActivity(this);
return true;
} else if(item.getItemId() == cc.winboll.studio.appbase.R.id.item_minimal) {
moveTaskToBack(true);
}
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);

View File

@@ -39,28 +39,41 @@
android:layout_height="wrap_content"
android:text="Android版本10的代号是“Q”API级别是29。 Android 10开始谷歌不再公开使用甜品作为版本代号但内部仍保留了大量与“Q”相关的元素。Android 10本身并没有严格对应某个特定的Java版本但在开发Android 10应用时通常可以使用Java 8或更高版本。 Java 8为Android开发带来了诸如Lambda表达式、方法引用等新特性能提高开发效率和代码可读性与Android 10开发适配良好。Java 9及更高版本也可用于Android 10开发能使用一些新的语言特性和API但可能需要注意兼容性和配置问题。"/>
<LinearLayout
android:orientation="horizontal"
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right|center_vertical">
android:layout_height="wrap_content">
<CheckBox
android:layout_width="wrap_content"
<LinearLayout
android:orientation="horizontal"
android:layout_height="wrap_content"
android:text="Debug Mode"
android:layout_weight="1.0"
android:onClick="onSwitchDebugMode"
android:id="@+id/activitymainCheckBox1"/>
android:gravity="right|center_vertical"
android:layout_width="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test Application CrashReport"
android:textAllCaps="false"
android:onClick="onTestApplicationCrashReport"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Debug Mode"
android:layout_weight="1.0"
android:onClick="onSwitchDebugMode"
android:id="@+id/activitymainCheckBox1"/>
</LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test Application CrashReport"
android:textAllCaps="false"
android:onClick="onTestApplicationCrashReport"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PreviewGlobalCrashActivity"
android:textAllCaps="false"
android:onClick="onPreviewGlobalCrashActivity"/>
</LinearLayout>
</HorizontalScrollView>
<ScrollView
android:layout_width="match_parent"
@@ -72,26 +85,27 @@
android:layout_height="wrap_content"
android:gravity="right">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StartCenter"
android:textAllCaps="false"
android:onClick="onStartCenter"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StartCenter"
android:textAllCaps="false"
android:onClick="onStartCenter"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StopCenter"
android:textAllCaps="false"
android:onClick="onStopCenter"/>
</LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StopCenter"
android:textAllCaps="false"
android:onClick="onStopCenter"/>
</LinearLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
@@ -160,7 +174,6 @@
</HorizontalScrollView>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -181,14 +194,14 @@
android:text="TestDemoServiceSOS"
android:textAllCaps="false"
android:onClick="onTestDemoServiceSOS"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TestOpenNewActivity"
android:textAllCaps="false"
android:onClick="onTestOpenNewActivity"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TestOpenNewActivity"
android:textAllCaps="false"
android:onClick="onTestOpenNewActivity"/>
</LinearLayout>
</ScrollView>
@@ -198,6 +211,6 @@
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -24,9 +24,13 @@ dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
// Android 类库
api 'com.android.support:appcompat-v7:28.0.0' // 包含 AppCompatActivity
//api 'com.android.support:appcompat-v7:28.0.0'
api('com.android.support:appcompat-v7:28.0.0'){
//exclude group: "com.android.support", module: "support-vector-drawable"
exclude group: "com.android.support:animated-vector-drawable:28.0.0"
}
// https://mvnrepository.com/artifact/com.android.support/support-compat
api 'com.android.support:support-compat:28.0.0' // 保留原有依赖(可选)
//api 'com.android.support:support-compat:28.0.0' // 保留原有依赖(可选)
// https://mvnrepository.com/artifact/com.android.support/support-v4
api 'com.android.support:support-v4:28.0.0'
// https://mvnrepository.com/artifact/com.android.support/support-media-compat

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Thu Mar 27 10:59:31 GMT 2025
stageCount=1
#Fri Mar 28 18:20:22 HKT 2025
stageCount=4
libraryProject=libappbase
baseVersion=15.1
publishVersion=15.1.0
buildCount=130
baseBetaVersion=15.1.1
publishVersion=15.1.3
buildCount=0
baseBetaVersion=15.1.4

View File

@@ -9,12 +9,16 @@
<!-- 发送持久广播 -->
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<!-- 对正在运行的应用重新排序 -->
<uses-permission android:name="android.permission.REORDER_TASKS"/>
<application>
<activity
android:name=".CrashHandler$CrashActivity"
android:label="CrashActivity"
android:launchMode="standard"/>
android:launchMode="singleInstance"
android:process=":CrashActivity"/>
<activity
android:name=".GlobalCrashActivity"
@@ -23,7 +27,8 @@
android:process=":GlobalCrashActivity"/>
<activity
android:name="cc.winboll.studio.libappbase.winboll.LogActivity"
android:name=".winboll.LogActivity"
android:label="LogActivity"
android:resizeableActivity="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:exported="true"
@@ -42,7 +47,8 @@
android:name=".services.TestService"
android:exported="true"/>
<receiver android:name=".receiver.MyBroadcastReceiver"
<receiver
android:name=".receiver.MyBroadcastReceiver"
android:exported="true">
<intent-filter>
@@ -85,7 +91,8 @@
<service android:name="cc.winboll.studio.libappbase.sos.SOSCenter"/>
<receiver android:name="cc.winboll.studio.libappbase.sos.SOSCenterServiceReceiver"
<receiver
android:name="cc.winboll.studio.libappbase.sos.SOSCenterServiceReceiver"
android:exported="true">
<intent-filter>

View File

@@ -339,6 +339,7 @@ public final class CrashHandler {
int padding = dp2px(16);
message.setPadding(padding, padding, padding, padding);
message.setText(mLog);
message.setTextColor(Color.BLACK);
message.setTextIsSelectable(true);
}
hw.addView(message);

View File

@@ -19,11 +19,6 @@ public class GlobalApplication extends Application {
public static final String TAG = "GlobalApplication";
final static String PREFS = GlobalApplication.class.getName() + "PREFS";
final static String PREFS_ISDEBUGING = "PREFS_ISDEBUGING";
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
volatile static GlobalApplication _GlobalApplication;
// 是否处于调试状态
volatile static boolean isDebuging = false;
@@ -33,14 +28,6 @@ public class GlobalApplication extends Application {
if (_GlobalApplication != null) {
GlobalApplication.isDebuging = isDebuging;
APPBaseModel.saveBeanToFile(getAPPBaseModelFilePath(), new APPBaseModel(isDebuging));
// 获取SharedPreferences实例
// SharedPreferences sharedPreferences = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
// // 获取编辑器
// SharedPreferences.Editor editor = sharedPreferences.edit();
// // 保存数据
// editor.putBoolean(PREFS_ISDEBUGING, GlobalApplication.isDebuging);
// // 提交更改
// editor.apply();
}
}
@@ -56,57 +43,38 @@ public class GlobalApplication extends Application {
return isDebuging;
}
@Override
public Context getApplicationContext() {
return super.getApplicationContext();
}
public Application getApplication() {
return this;
public static WinBollActivityManager getWinBollActivityManager() {
return WinBollActivityManager.getInstance(_GlobalApplication);
}
@Override
public void onCreate() {
super.onCreate();
// 保存初始实例
_GlobalApplication = this;
setIsDebuging(true);
// 添加日志模块
LogUtils.init(this);
//LogUtils.setLogLevel(LogUtils.LOG_LEVEL.Debug);
//LogUtils.setTAGListEnable(GlobalApplication.TAG, true);
//LogUtils.setALlTAGListEnable(true);
//LogUtils.d(TAG, "LogUtils init");
// 设置应用异常处理窗口
CrashHandler.init(this);
// 初始化 Toast 框架
ToastUtils.init(this);
// _GlobalApplication 取值调试部分
//
boolean is_GlobalApplicationNull = false;
if (_GlobalApplication == null) {
is_GlobalApplicationNull = true;
_GlobalApplication = this;
} else {
is_GlobalApplicationNull = false;
_GlobalApplication = this;
}
// 设置应用调试标志
// 应用保存的调试标志
APPBaseModel appBaseModel = APPBaseModel.loadBeanFromFile(getAPPBaseModelFilePath(), APPBaseModel.class);
if (appBaseModel == null) {
setIsDebuging(false);
} else {
setIsDebuging(appBaseModel.isDebuging());
}
LogUtils.init(this);
//LogUtils.setLogLevel(LogUtils.LOG_LEVEL.Debug);
//LogUtils.setTAGListEnable(GlobalApplication.TAG, true);
//LogUtils.setALlTAGListEnable(true);
//LogUtils.d(TAG, "LogUtils init");
// 设置应用异常处理窗口
CrashHandler.init(this);
// 初始化 Toast 框架
ToastUtils.init(this);
// _GlobalApplication 取值调试部分
//
LogUtils.d(TAG, String.format("is_GlobalApplicationNull is %s", is_GlobalApplicationNull));
WinBollActivityManager.getInstance(_GlobalApplication);
WinBollActivityManager.getInstance(_GlobalApplication).setWinBollUI_TYPE(WinBollActivityManager.WinBollUI_TYPE.Service);
// 注册回调
getWinBollActivityManager().setWinBollUI_TYPE(WinBollActivityManager.WinBollUI_TYPE.Service);
// 注册窗口回调监听
mMyActivityLifecycleCallbacks = new MyActivityLifecycleCallbacks();
registerActivityLifecycleCallbacks(mMyActivityLifecycleCallbacks);
}
@@ -115,7 +83,6 @@ public class GlobalApplication extends Application {
@Override
public void onTerminate() {
super.onTerminate();
_GlobalApplication = null;
// 注销回调(非必须,但建议释放资源)
unregisterActivityLifecycleCallbacks(mMyActivityLifecycleCallbacks);
}

View File

@@ -4,26 +4,20 @@ package cc.winboll.studio.libappbase;
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/11 00:14:05
*/
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import cc.winboll.studio.libappbase.R;
public final class GlobalCrashActivity extends Activity implements MenuItem.OnMenuItemClickListener {
public final class GlobalCrashActivity extends AppCompatActivity implements MenuItem.OnMenuItemClickListener {
private static final int MENUITEM_COPY = 0;
private static final int MENUITEM_RESTART = 1;
@@ -45,10 +39,10 @@ public final class GlobalCrashActivity extends Activity implements MenuItem.OnMe
setContentView(R.layout.activity_globalcrash);
mGlobalCrashReportView = findViewById(R.id.activityglobalcrashGlobalCrashReportView1);
mGlobalCrashReportView.setReport(mLog);
setActionBar(mGlobalCrashReportView.getToolbar());
setSupportActionBar(mGlobalCrashReportView.getToolbar());
getActionBar().setTitle(CrashHandler.TITTLE);
getActionBar().setSubtitle(GlobalApplication.getAppName(getApplicationContext()));
getSupportActionBar().setTitle(CrashHandler.TITTLE);
getSupportActionBar().setSubtitle(GlobalApplication.getAppName(getApplicationContext()));
}
@Override
@@ -98,20 +92,4 @@ public final class GlobalCrashActivity extends Activity implements MenuItem.OnMe
mGlobalCrashReportView.updateMenuStyle();
return true;
}
void joinQQGroup(String key) {
// 创建Intent
Intent intent = new Intent();
// 设置动作
intent.setAction("android.intent.action.VIEW");
// 设置数据为网址的URI
Uri content_url = Uri.parse("https://www.winboll.cc");
intent.setData(content_url);
// 添加标志
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 设置类名和活动名
intent.setClassName("com.android.browser", "com.android.browser.BrowserActivity");
// 启动Activity
startActivity(intent);
}
}

View File

@@ -8,6 +8,7 @@ package cc.winboll.studio.libappbase;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.v7.widget.Toolbar;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
@@ -16,7 +17,6 @@ import android.view.MenuItem;
import android.widget.LinearLayout;
import android.widget.TextView;
import cc.winboll.studio.libappbase.R;
import android.widget.Toolbar;
public class GlobalCrashReportView extends LinearLayout {

View File

@@ -0,0 +1,60 @@
package cc.winboll.studio.libappbase.dialogs;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/28 17:40:47
* @Date 2024/08/12 14:46:25
* @Describe 询问用户确定与否的选择框
*/
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
public class YesNoAlertDialog {
public static final String TAG = "YesNoAlertDialog";
public static void show(Context context, String szTitle, String szMessage, final OnDialogResultListener listener) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
context);
// set title
alertDialogBuilder.setTitle(szTitle);
// set dialog message
alertDialogBuilder
.setMessage(szMessage)
.setCancelable(true)
.setOnCancelListener(new DialogInterface.OnCancelListener(){
@Override
public void onCancel(DialogInterface dialog) {
listener.onNo();
}
})
.setPositiveButton("YES", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// if this button is clicked, close
// current activity
listener.onYes();
}
})
.setNegativeButton("NO", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// if this button is clicked, just close
// the dialog box and do nothing
dialog.cancel();
}
});
// create alert dialog
AlertDialog alertDialog = alertDialogBuilder.create();
// show it
alertDialog.show();
}
public interface OnDialogResultListener {
abstract void onYes();
abstract void onNo();
}
}

View File

@@ -8,24 +8,17 @@ import android.app.Activity;
import android.app.Application;
import android.content.Intent;
import android.os.Bundle;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.utils.ToastUtils;
import cc.winboll.studio.libappbase.winboll.IWinBollActivity;
import cc.winboll.studio.libappbase.utils.ToastUtils;
public class MyActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
public static final String TAG = "MyActivityLifecycleCallbacks";
public String mInfo = "";
public MyActivityLifecycleCallbacks() {
}
WinBollActivityManager getWinBollActivityManager() {
return WinBollActivityManager.getInstance(GlobalApplication.getInstance());
}
void createActivityeInfo(Activity activity) {
@@ -64,7 +57,6 @@ public class MyActivityLifecycleCallbacks implements Application.ActivityLifecyc
//System.out.println(activity.getLocalClassName() + " was created");
LogUtils.d(TAG, activity.getLocalClassName() + " was created");
createActivityeInfo(activity);
getWinBollActivityManager().add((IWinBollActivity)activity);
}
@Override
@@ -102,6 +94,5 @@ public class MyActivityLifecycleCallbacks implements Application.ActivityLifecyc
public void onActivityDestroyed(Activity activity) {
//System.out.println(activity.getLocalClassName() + " was destroyed");
LogUtils.d(TAG, activity.getLocalClassName() + " was destroyed");
getWinBollActivityManager().registeRemove((IWinBollActivity)activity);
}
}

View File

@@ -12,6 +12,7 @@ import android.app.ActivityManager;
import android.app.TaskStackBuilder;
import android.content.Context;
import android.content.Intent;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.utils.ToastUtils;
import java.util.HashMap;
@@ -29,27 +30,25 @@ public class WinBollActivityManager {
};
// 应用类型标志
volatile static WinBollUI_TYPE _mWinBollUI_TYPE = WinBollUI_TYPE.Service;
static volatile WinBollUI_TYPE _mWinBollUI_TYPE = WinBollUI_TYPE.Service;
Context mContext;
MyActivityLifecycleCallbacks mMyActivityLifecycleCallbacks;
static volatile WinBollActivityManager _mWinBollActivityManager;
GlobalApplication mGlobalApplication;
static volatile WinBollActivityManager _Instance;
static volatile Map<String, IWinBollActivity> _mapIWinBollList;
static volatile IWinBollActivity firstIWinBollActivity;
volatile IWinBollActivity mFirstIWinBollActivity;
public WinBollActivityManager(Context context) {
mContext = context;
LogUtils.d(TAG, "WinBollActivityManager()");
WinBollActivityManager(GlobalApplication application) {
mGlobalApplication = application;
_mapIWinBollList = new HashMap<String, IWinBollActivity>();
}
public static synchronized WinBollActivityManager getInstance(Context context) {
public static synchronized WinBollActivityManager getInstance(GlobalApplication application) {
LogUtils.d(TAG, "getInstance");
if (_mWinBollActivityManager == null) {
LogUtils.d(TAG, "_mWinBollActivityManager == null");
_mWinBollActivityManager = new WinBollActivityManager(context);
if (_Instance == null) {
LogUtils.d(TAG, "_Instance == null");
_Instance = new WinBollActivityManager(application);
}
return _mWinBollActivityManager;
return _Instance;
}
//
@@ -73,11 +72,12 @@ public class WinBollActivityManager {
String tag = ((IWinBollActivity)iWinBoll).getTag();
LogUtils.d(TAG, String.format("add(T iWinBoll) tag is %s", tag));
if (isActive(tag)) {
LogUtils.d(TAG, String.format("add(...) %s is active.", iWinBoll.getTag()));
LogUtils.d(TAG, String.format("isActive(tag) is true, tag : %s.", tag));
} else {
// 设置起始活动窗口,以便最后退出时提问
if (firstIWinBollActivity == null && _mapIWinBollList.size() == 0) {
firstIWinBollActivity = iWinBoll;
if (mFirstIWinBollActivity == null && _mapIWinBollList.size() == 0) {
LogUtils.d(TAG, "Set firstIWinBollActivity, iWinBoll.getTag() is %s" + iWinBoll.getTag());
mFirstIWinBollActivity = iWinBoll;
}
// 添加到活动窗口列表
@@ -168,7 +168,7 @@ public class WinBollActivityManager {
}
public boolean isFirstIWinBollActivity(IWinBollActivity iWinBollActivity) {
return firstIWinBollActivity != null && firstIWinBollActivity == iWinBollActivity;
return mFirstIWinBollActivity != null && mFirstIWinBollActivity == iWinBollActivity;
}
//
@@ -220,10 +220,10 @@ public class WinBollActivityManager {
//
public <T extends IWinBollActivity> void resumeActivity(Context context, T iWinBoll) {
LogUtils.d(TAG, "resumeActivity(Context context, T iWinBoll)");
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager am = (ActivityManager) mGlobalApplication.getSystemService(Context.ACTIVITY_SERVICE);
//返回启动它的根任务home 或者 MainActivity
Intent intent = new Intent(mContext, iWinBoll.getClass());
TaskStackBuilder stackBuilder = TaskStackBuilder.create(mContext);
Intent intent = new Intent(mGlobalApplication, iWinBoll.getClass());
TaskStackBuilder stackBuilder = TaskStackBuilder.create(mGlobalApplication);
stackBuilder.addNextIntentWithParentStack(intent);
stackBuilder.startActivities();
//moveTaskToFront(YourTaskId, 0);
@@ -287,7 +287,7 @@ public class WinBollActivityManager {
IWinBollActivity preIWinBoll = getPreIWinBoll(iWinBoll);
iWinBoll.getActivity().finish();
if (preIWinBoll != null) {
resumeActivity(mContext, preIWinBoll);
resumeActivity(mGlobalApplication, preIWinBoll);
}
}

View File

@@ -7,9 +7,9 @@
android:layout_height="match_parent"
android:id="@+id/viewglobalcrashreportLinearLayout1">
<android.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/viewglobalcrashreportToolbar1"/>
<ScrollView

1
ollama/.gitignore vendored
View File

@@ -1 +0,0 @@
/build

View File

@@ -1,73 +0,0 @@
apply plugin: 'com.android.application'
apply from: '../.winboll/winboll_app_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle'
def genVersionName(def versionName){
// 检查编译标志位配置
assert (winbollBuildProps['stageCount'] != null)
assert (winbollBuildProps['baseVersion'] != null)
// 保存基础版本号
winbollBuildProps.setProperty("baseVersion", "${versionName}");
//保存编译标志配置
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
fos.close();
// 返回编译版本号
return "${versionName}." + winbollBuildProps['stageCount']
}
android {
compileSdkVersion 32
buildToolsVersion "32.0.0"
defaultConfig {
applicationId "cc.winboll.studio.ollama"
minSdkVersion 26
targetSdkVersion 29
versionCode 1
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.0"
if(true) {
versionName = genVersionName("${versionName}")
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
api 'com.squareup.okhttp3:okhttp:4.4.1'
// 吐司类库
api 'com.github.getActivity:ToastUtils:10.5'
// Android 类库
api 'com.android.support:appcompat-v7:28.0.0' // 包含 AppCompatActivity
// https://mvnrepository.com/artifact/com.android.support/support-compat
api 'com.android.support:support-compat:28.0.0' // 保留原有依赖(可选)
// https://mvnrepository.com/artifact/com.android.support/support-v4
api 'com.android.support:support-v4:28.0.0'
// https://mvnrepository.com/artifact/com.android.support/support-media-compat
api 'com.android.support:support-media-compat:28.0.0'
// https://mvnrepository.com/artifact/com.android.support/support-core-utils
api 'com.android.support:support-core-utils:28.0.0'
// https://mvnrepository.com/artifact/com.android.support/support-core-ui
api 'com.android.support:support-core-ui:28.0.0'
// https://mvnrepository.com/artifact/com.android.support/support-fragment
api 'com.android.support:support-fragment:28.0.0'
// https://mvnrepository.com/artifact/com.android.support/recyclerview-v7
api 'com.android.support:recyclerview-v7:28.0.0'
api 'cc.winboll.studio:libappbase:15.0.9'
api 'cc.winboll.studio:libapputils:15.0.11'
}

View File

@@ -1,8 +0,0 @@
#Created by .winboll/winboll_app_build.gradle
#Fri Mar 28 05:09:21 HKT 2025
stageCount=1
libraryProject=
baseVersion=15.0
publishVersion=15.0.0
buildCount=0
baseBetaVersion=15.0.1

View File

@@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application>
<!-- Put flavor specific code here -->
</application>
</manifest>

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Ollama +</string>
</resources>

View File

@@ -1,41 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="cc.winboll.studio.ollama">
<!-- 拥有完全的网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/MyAppTheme"
android:resizeableActivity="true"
android:name=".App"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="android.max_aspect"
android:value="4.0"/>
<activity android:name=".GlobalApplication$CrashActivity"/>
</application>
</manifest>

View File

@@ -1,334 +0,0 @@
package cc.winboll.studio.ollama;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import cc.winboll.studio.libappbase.GlobalApplication;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
public class App extends GlobalApplication {
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
@Override
public void onCreate() {
super.onCreate();
//CrashHandler.getInstance().registerGlobal(this);
//CrashHandler.getInstance().registerPart(this);
}
public static void write(InputStream input, OutputStream output) throws IOException {
byte[] buf = new byte[1024 * 8];
int len;
while ((len = input.read(buf)) != -1) {
output.write(buf, 0, len);
}
}
public static void write(File file, byte[] data) throws IOException {
File parent = file.getParentFile();
if (parent != null && !parent.exists()) parent.mkdirs();
ByteArrayInputStream input = new ByteArrayInputStream(data);
FileOutputStream output = new FileOutputStream(file);
try {
write(input, output);
} finally {
closeIO(input, output);
}
}
public static String toString(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
write(input, output);
try {
return output.toString("UTF-8");
} finally {
closeIO(input, output);
}
}
public static void closeIO(Closeable... closeables) {
for (Closeable closeable : closeables) {
try {
if (closeable != null) closeable.close();
} catch (IOException ignored) {}
}
}
public static class CrashHandler {
public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
private static CrashHandler sInstance;
private PartCrashHandler mPartCrashHandler;
public static CrashHandler getInstance() {
if (sInstance == null) {
sInstance = new CrashHandler();
}
return sInstance;
}
public void registerGlobal(Context context) {
registerGlobal(context, null);
}
public void registerGlobal(Context context, String crashDir) {
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir));
}
public void unregister() {
Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER);
}
public void registerPart(Context context) {
unregisterPart(context);
mPartCrashHandler = new PartCrashHandler(context.getApplicationContext());
MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler);
}
public void unregisterPart(Context context) {
if (mPartCrashHandler != null) {
mPartCrashHandler.isRunning.set(false);
mPartCrashHandler = null;
}
}
private static class PartCrashHandler implements Runnable {
private final Context mContext;
public AtomicBoolean isRunning = new AtomicBoolean(true);
public PartCrashHandler(Context context) {
this.mContext = context;
}
@Override
public void run() {
while (isRunning.get()) {
try {
Looper.loop();
} catch (final Throwable e) {
e.printStackTrace();
if (isRunning.get()) {
MAIN_HANDLER.post(new Runnable(){
@Override
public void run() {
Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show();
}
});
} else {
if (e instanceof RuntimeException) {
throw (RuntimeException)e;
} else {
throw new RuntimeException(e);
}
}
}
}
}
}
private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
private final Context mContext;
private final File mCrashDir;
public UncaughtExceptionHandlerImpl(Context context, String crashDir) {
this.mContext = context;
this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash") : new File(crashDir);
}
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
try {
String log = buildLog(throwable);
writeLog(log);
try {
Intent intent = new Intent(mContext, CrashActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Intent.EXTRA_TEXT, log);
mContext.startActivity(intent);
} catch (Throwable e) {
e.printStackTrace();
writeLog(e.toString());
}
throwable.printStackTrace();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
} catch (Throwable e) {
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
}
}
private String buildLog(Throwable throwable) {
String time = DATE_FORMAT.format(new Date());
String versionName = "unknown";
long versionCode = 0;
try {
PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
versionName = packageInfo.versionName;
versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode;
} catch (Throwable ignored) {}
LinkedHashMap<String, String> head = new LinkedHashMap<String, String>();
head.put("Time Of Crash", time);
head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL));
head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
head.put("App Version", String.format("%s (%d)", versionName, versionCode));
head.put("Kernel", getKernel());
head.put("Support Abis", Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null ? Arrays.toString(Build.SUPPORTED_ABIS): "unknown");
head.put("Fingerprint", Build.FINGERPRINT);
StringBuilder builder = new StringBuilder();
for (String key : head.keySet()) {
if (builder.length() != 0) builder.append("\n");
builder.append(key);
builder.append(" : ");
builder.append(head.get(key));
}
builder.append("\n\n");
builder.append(Log.getStackTraceString(throwable));
return builder.toString();
}
private void writeLog(String log) {
String time = DATE_FORMAT.format(new Date());
File file = new File(mCrashDir, "crash_" + time + ".txt");
try {
write(file, log.getBytes("UTF-8"));
} catch (Throwable e) {
e.printStackTrace();
}
}
private static String getKernel() {
try {
return App.toString(new FileInputStream("/proc/version")).trim();
} catch (Throwable e) {
return e.getMessage();
}
}
}
}
public static final class CrashActivity extends Activity {
private String mLog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(android.R.style.Theme_DeviceDefault);
setTitle("App Crash");
mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT);
ScrollView contentView = new ScrollView(this);
contentView.setFillViewport(true);
HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this);
TextView textView = new TextView(this);
int padding = dp2px(16);
textView.setPadding(padding, padding, padding, padding);
textView.setText(mLog);
textView.setTextIsSelectable(true);
textView.setTypeface(Typeface.DEFAULT);
textView.setLinksClickable(true);
horizontalScrollView.addView(textView);
contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
setContentView(contentView);
}
private void restart() {
Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
finish();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
private static int dp2px(float dpValue) {
final float scale = Resources.getSystem().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, android.R.id.copy, 0, android.R.string.copy)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.copy:
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
restart();
}
}
}

View File

@@ -1,88 +0,0 @@
package cc.winboll.studio.ollama;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
public final static int MSG_APPEND = 0;
private Handler _Handler = new Handler(Looper.getMainLooper());
private TextView mtvMessage;
private EditText metAsk;
private Button mbtSend;
private ScrollView msvMessage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mtvMessage = (TextView) findViewById(R.id.message_tv);
metAsk = (EditText) findViewById(R.id.ask_et);
mbtSend = (Button) findViewById(R.id.send_bt);
msvMessage = findViewById(R.id.message_sv);
mbtSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendQuestion();
}
});
}
//
// 设置输入框获得焦点的类
//
// static class MyHandler extends Handler {
// WeakReference<MainActivity> mActivity;
// MyHandler(MainActivity activity) {
// mActivity = new WeakReference<MainActivity>(activity);
// }
// public void handleMessage(Message msg) {
// MainActivity theActivity = mActivity.get();
// switch (msg.what) {
// case MSG_APPEND:
// theActivity.mtvMessage.append((String)msg.obj);
// break;
// default:
// break;
// }
// super.handleMessage(msg);
// }
// }
private void sendQuestion() {
final String question = metAsk.getText().toString().trim();
if (!question.equals("")) {
mtvMessage.append("\n\nI " + metAsk.getText().toString() + "\nOllama : ");
metAsk.setText("");
new OllamaClient.SyncAskThread(question, new OllamaClient.OnAnswerCallback() {
@Override
public void onAnswer(final String answer) {
_Handler.post(new Runnable() {
@Override
public void run() {
mtvMessage.append(answer);
msvMessage.post(new Runnable(){
@Override
public void run() {
msvMessage.fullScroll(View.FOCUS_DOWN);
}
});
}
});
}
}).start();
}
}
}

View File

@@ -1,223 +0,0 @@
package cc.winboll.studio.ollama;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/27 19:55:28
* @Describe 简单Http协议访问客户端
*/
import cc.winboll.studio.libappbase.LogUtils;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.Source;
import org.json.JSONException;
import org.json.JSONObject;
public class OllamaClient {
public static final String TAG = "OllamaClient";
private static final String API_BASE_URL = "https://ollama-api.winboll.cc";
//private static final String API_BASE_URL = "http://10.8.0.10:11434";
//private static final OkHttpClient client = new OkHttpClient();
private static final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build();
// 1. 生成文本示例
// static void generateText(String prompt, String model) {
// String url = API_BASE_URL + "/api/generate";
// try {
// JSONObject payload = new JSONObject()
// .put("model", model)
// .put("prompt", prompt)
// .put("temperature", 0.7)
// .put("max_tokens", 200);
//
// Request request = new Request.Builder()
// .url(url)
// .post(RequestBody.create(payload.toString(), MediaType.get("application/json")))
// .build();
//
// Response response = client.newCall(request).execute();
// if (response.isSuccessful()) {
// String result = response.body().string();
// String formattedStream = OllamaResponseFormatter.formatStreamingResponse(result);
//
// // 输出示例:
//// [2025-03-27T19:34:29.274955439Z] [llama3.1:8b] It looks like you might have miss
//// [2025-03-27T19:34:30.482553089Z] [llama3.1:8b] pelled the word "Ollama" or perhaps said something that is not a standard word in the English language. However, I'm here to provide information and assistance on various topics, so please let me know what you meant by "Ollama." Was it related to a name, place, movie, game, or something else?
//
// LogUtils.d(TAG, formattedStream);
//// JSONObject json = new JSONObject(result);
//// LogUtils.d(TAG, "生成结果: " + json.getString("response"));
// //System.out.println("生成结果: " + json.getString("response"));
// } else {
// LogUtils.d(TAG, "请求失败: " + response.code());
// //System.out.println("请求失败: " + response.code() + " " + response.message());
// }
// } catch (JSONException|IOException e) {
// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
// }
// }
// 实时输出流式响应的函数
static void generateTextStream(String prompt, String model, final OnAnswerCallback callback) {
String url = API_BASE_URL + "/api/generate";
try {
JSONObject payload = new JSONObject()
.put("model", model)
.put("prompt", prompt)
.put("temperature", 0.7)
.put("max_tokens", 200)
.put("stream", true); // 启用流式响应
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(payload.toString(), MediaType.get("application/json")))
.build();
LogUtils.d(TAG, "Request request");
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
LogUtils.d(TAG, "请求失败: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
LogUtils.d(TAG, "请求失败: " + response.code());
return;
}
try (ResponseBody body = response.body()) {
if (body == null) return;
// 实时流式解析
LogUtils.d(TAG, "实时流式解析");
Source source = body.source();
Buffer buffer = new Buffer();
StringBuilder fullResponse = new StringBuilder();
boolean isDone = false;
while (!isDone && source.read(buffer, 1024) != -1) {
//LogUtils.d(TAG, "!isDone");
String chunk = buffer.readUtf8();
String[] lines = chunk.split("\n");
for (String line : lines) {
LogUtils.d(TAG, line);
if (line.trim().startsWith("{\"model\":")) {
LogUtils.d(TAG, line);
String jsonStr = line;
if (jsonStr.equals("[DONE]")) {
isDone = true;
LogUtils.d(TAG, "流式生成完成");
break;
}
try {
//LogUtils.d(TAG, jsonStr);
JSONObject json = new JSONObject(jsonStr);
//LogUtils.d(TAG, json.toString());
String responseText = json.getString("response");
//LogUtils.d(TAG, responseText);
fullResponse.append(responseText);
// 实时输出
callback.onAnswer(responseText);
LogUtils.d(TAG, "实时响应: " + responseText);
// 处理完成状态
if (json.getBoolean("done")) {
isDone = true;
String doneReason = json.optString("done_reason", "unknown");
LogUtils.d(TAG, "生成完成 (原因: " + doneReason + ")");
LogUtils.d(TAG, "完整回答: " + fullResponse.toString());
}
} catch (JSONException e) {
LogUtils.d(TAG, "JSON解析错误: " + e.getMessage());
}
}
}
}
}
}
});
} catch (JSONException e) {
LogUtils.d(TAG, "JSON格式错误: " + e.getMessage());
}
}
// 2. 获取模型列表示例
static void getModelList() {
String url = API_BASE_URL + "/v1/models";
LogUtils.d(TAG, "url : " + url);
Request request = new Request.Builder().url(url).build();
try {
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
LogUtils.d(TAG, response.body().string());
// JSONArray models = new JSONArray(response.body().string());
// //System.out.println("可用模型列表:");
// LogUtils.d(TAG, "可用模型列表:");
// for (int i = 0; i < models.length(); i++) {
// JSONObject model = models.getJSONObject(i);
// LogUtils.d(TAG, "- " + model.getString("name") + " (" + model.getString("size") + ")");
// //System.out.println("- " + model.getString("name") + " (" + model.getString("size") + ")");
// }
} else {
LogUtils.d(TAG, "获取模型列表失败: " + response.code());
//System.out.println("获取模型列表失败: " + response.code());
}
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
static void unittest(String ask, OnAnswerCallback callback) {
// 获取模型列表
getModelList();
// 生成文本
generateTextStream(ask, "llama3.1:8b", callback);
}
public static class SyncAskThread extends Thread {
private String ask;
private OnAnswerCallback callback;
public SyncAskThread(String ask, OnAnswerCallback callback) {
this.ask = ask;
this.callback = callback;
}
@Override
public void run() {
super.run();
LogUtils.d(TAG, "run() start.");
unittest(ask, callback);
LogUtils.d(TAG, "run() end.");
}
}
public interface OnAnswerCallback {
void onAnswer(String answer);
}
}

View File

@@ -1,72 +0,0 @@
package cc.winboll.studio.ollama;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/28 03:38:50
*/
import org.json.JSONArray;
import org.json.JSONObject;
public class OllamaResponseFormatter {
public static final String TAG = "OllamaResponseFormatter";
// 处理模型列表响应
public static String formatModelList(String jsonResponse) {
try {
JSONObject json = new JSONObject(jsonResponse);
JSONArray models = json.getJSONArray("data");
StringBuilder sb = new StringBuilder();
sb.append("可用模型列表:\n");
for (int i = 0; i < models.length(); i++) {
JSONObject model = models.getJSONObject(i);
String modelId = model.getString("id");
sb.append(String.format("-%d. %s\n", i + 1, modelId));
}
return sb.toString();
} catch (Exception e) {
return "格式解析错误: " + e.getMessage();
}
}
// 处理流式生成文本响应
public static String formatStreamingResponse(String jsonResponse) {
try {
JSONObject json = new JSONObject(jsonResponse);
StringBuilder sb = new StringBuilder();
String responseText = json.getString("response");
boolean isDone = json.getBoolean("done");
// 添加时间戳和模型标识
String timestamp = json.getString("created_at");
String modelName = json.getString("model");
sb.append(String.format("[%s] [%s] ", timestamp, modelName));
// 处理响应内容
if (responseText.isEmpty() && isDone) {
sb.append("生成完成\n");
} else {
sb.append(responseText);
if (isDone) {
String doneReason = json.optString("done_reason", "unknown");
sb.append(String.format(" (完成原因: %s)\n", doneReason));
}
}
return sb.toString();
} catch (Exception e) {
return "格式解析错误: " + e.getMessage();
}
}
// 使用示例
public static void main(String[] args) {
// 模型列表测试
String modelListJson = "{\"object\":\"list\",\"data\":[{...}]}";
System.out.println(formatModelList(modelListJson));
// 流式响应测试
String streamingJson = "{\"model\":\"llama3.1:8b\",\"created_at\":\"2025-03-27T19:34:29.274955439Z\",\"response\":\"It\",\"done\":false}";
System.out.println(formatStreamingResponse(streamingJson));
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -1,69 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center_vertical|center_horizontal"
android:layout_weight="1.0"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:id="@+id/message_sv">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Asking Ollama ..."
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/message_tv"/>
</ScrollView>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<EditText
android:layout_width="0dp"
android:ems="10"
android:layout_height="wrap_content"
android:id="@+id/ask_et"
android:layout_weight="1.0"
android:text="Hello, World!"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send"
android:id="@+id/send_bt"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<cc.winboll.studio.libappbase.LogView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Text"
android:id="@+id/logview"/>
</LinearLayout>
</LinearLayout>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar">
<item name="android:colorPrimary">@color/colorPrimary</item>
<item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="android:colorAccent">@color/colorAccent</item>
<item name="android:navigationBarColor">?android:colorPrimary</item>
</style>
</resources>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#009688</color>
<color name="colorPrimaryDark">#00796B</color>
<color name="colorAccent">#FF9800</color>
</resources>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Ollama</string>
</resources>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
</style>
</resources>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">10.8.0.10</domain>
</domain-config>
</network-security-config>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application>
<!-- Put flavor specific code here -->
</application>
</manifest>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Put flavor specific strings here -->
</resources>

View File

@@ -57,7 +57,3 @@
// AndroidXDemo 项目编译设置
//include ':androidxdemo'
//rootProject.name = "androidxdemo"
// Ollama 项目编译设置
//include ':ollama'
//rootProject.name = "ollama"